Merge branch 'master' of github.com:open-cloud/xos
diff --git a/.gitignore b/.gitignore
index 9fbdd02..b03d344 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
-*orig
-*pyc
+*.orig
+*.pyc
+*.swp
 profile
 *.moved-aside
diff --git a/xos/core/admin.py b/xos/core/admin.py
index a83ec7d..919952a 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -916,7 +916,7 @@
 
 class SiteAdmin(XOSBaseAdmin):
     #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
-    fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']
+    fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'login_base', 'location', 'is_public', 'hosts_nodes', 'hosts_users']
     fieldsets = [
         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
@@ -925,7 +925,7 @@
     readonly_fields = ['backend_status_text']
 
     #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
-    user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base']
+    user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'hosts_nodes', 'hosts_users']
 
     list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
     list_display_links = ('backend_status_icon', 'name', )
diff --git a/xos/core/models/site.py b/xos/core/models/site.py
index 1e8c7ca..e3c275f 100644
--- a/xos/core/models/site.py
+++ b/xos/core/models/site.py
@@ -99,6 +99,8 @@
     name = StrippedCharField(max_length=200, help_text="Name for this Site")
     site_url = models.URLField(null=True, blank=True, max_length=512, help_text="Site's Home URL Page")
     enabled = models.BooleanField(default=True, help_text="Status for this Site")
+    hosts_nodes = models.BooleanField(default=True, help_text="Indicates whether or not the site host nodes")
+    hosts_users = models.BooleanField(default=True, help_text="Indicates whether or not the site manages user accounts")
     location = GeopositionField()
     longitude = models.FloatField(null=True, blank=True)
     latitude = models.FloatField(null=True, blank=True)
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 795aa92..fc195d2 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -344,8 +344,94 @@
        readable_objects = []
        for model in models:
            readable_objects.extend(model.select_by_user(self))
-       return readable_objects      
+       return readable_objects
 
+    def get_permissions(self, filter=None):
+        """ Return a list of objects for which the user has read or read/write 
+        access. The object will be an instance of a django model object. 
+        Permissions will be either 'r' or 'rw'.
+         
+        e.g.
+        [{'object': django_object_instance, 'permissions': 'rw'}, ...]
+
+        Returns:
+          list of dicts  
+       
+        """
+        from core.models import *
+        READ = 'r'
+        READWRITE = 'rw'
+
+        deployment_priv_objs = [Image, NetworkTemplate, Flavor]
+        site_priv_objs = [Node, Slice, User]
+        slice_priv_objs = [Sliver, Network] 
+        
+        # maps the set of objects a paticular role has write access
+        write_map = {
+            DeploymentPrivilege : {
+                'admin': deployment_priv_objects,
+            },
+            SitePrivilege : {
+                'admin' : site_priv_objs,
+                'pi' : [Slice, User],
+                'tech': [Node],
+            },     
+            SlicePrivilege : {
+                'admin': slice_priv_objs, 
+            }, 
+        }
+            
+        privilege_map = {
+            DeploymentPrivilege : (Deployment, deployment_priv_objs),
+            SitePrivilege : (Site, site_priv_objs),
+            SlicePrivilege : (Slice, slice_priv_objs)
+        }
+        permissions = []
+        permission_dict = lambda x,y: {'object': x, 'permission': y}
+        for privilege_model, (model, affected_models) in privileg_map.items():
+            # get the objects affected by this privilege model   
+            affected_objects = []
+            for affected_model in affected_models:
+                affected_objects.extend(affected_model.select_by_user(self))
+
+            if self.is_admin:
+                # assume admin users have read/write access to all objects
+                for affected_object in affected_objects:
+                    permissions.append(permission_dict(affected_object, READWRITE))
+            else:
+                # create a dict of the user's per object privileges
+                # ex:  {princeton_tmack : ['admin']  
+                privileges = privilege_model.objects.filter(user=self)
+                for privilege in privileges:
+                    object_roles = defaultdict(list)
+                    obj = None
+                    roles = []
+                    for field in dir(privilege):
+                        if field == model.__name__.lower():
+                            obj = getattr(privilege, field)
+                    if obj:
+                        object_roles[obj].append(privilege.role.role)
+                        
+                # loop through all objects the user has access to and determine
+                # if they also have write access
+                for affected_object in affected_objects:
+                    if affected_object not in objects_roles:
+                        permissions.append(permission_dict(affected_object, READ))
+                    else:
+                        has_write_permission = False
+                        for write_role, models in write_dict.items():
+                            if affected_object._meta.model in models and \
+                                write_role in object_roles[affected_object]:
+                                    has_write_permission = True
+                                    break
+                        if has_write_permission:
+                            permissions.append(permission_dict(affected_object, WRITE))
+                        else:
+                            permissions.append(permission_dict(affected_object, READ))
+                                
+        return permissions                          
+                     
+    
     @staticmethod
     def select_by_user(user):
         if user.is_admin:
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 87c96b4..b1f6873 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -3,7 +3,10 @@
 node_types:
     tosca.nodes.Service:
         derived_from: tosca.nodes.Root
-   
+
+    tosca.nodes.Site:
+        derived_from: tosca.nodes.Root
+
     tosca.nodes.Slice:
         derived_from: tosca.nodes.Root
         properties:
@@ -17,5 +20,8 @@
     tosca.relationships.MemberOfService:
         derived_from: tosca.relationships.Root
 
+    tosca.relationships.MemberOfSite:
+        derived_from: tosca.relationships.Root
+
     tosca.relationships.TenantOfService:
         derived_from: tosca.relationships.Root
diff --git a/xos/tosca/engine.py b/xos/tosca/engine.py
index 4f74d28..2ecf165 100644
--- a/xos/tosca/engine.py
+++ b/xos/tosca/engine.py
@@ -9,6 +9,8 @@
 from nodeselect import XOSNodeSelector
 from imageselect import XOSImageSelector
 
+import resources
+
 class XOSTosca(object):
     def __init__(self, tosca_yaml, parent_dir=None):
         # TOSCA will look for imports using a relative path from where the
@@ -32,75 +34,12 @@
         for nodetemplate in self.template.nodetemplates:
             self.execute_nodetemplate(user, nodetemplate)
 
-    def select_compute_node(self, user, v):
-        mem_size = v.get_property_value("mem_size")
-        num_cpus = v.get_property_value("num_cpus")
-        disk_size = v.get_property_value("disk_size")
-
-        # TODO: pick flavor based on parameters
-        flavor = Flavor.objects.get(name="m1.small")
-
-        compute_node = XOSNodeSelector(user, mem_size=mem_size, num_cpus=num_cpus, disk_size=disk_size).get_nodes(1)[0]
-
-        return (compute_node, flavor)
-
-    def select_image(self, user, v):
-        distribution = v.get_property_value("distribution")
-        version = v.get_property_value("version")
-        type = v.get_property_value("type")
-        architecture = v.get_property_value("architecture")
-
-        return XOSImageSelector(user, distribution=distribution, version=version, type=type, architecture=architecture).get_image()
-
     def execute_nodetemplate(self, user, nodetemplate):
-        if (nodetemplate.type == "tosca.nodes.Slice"):
-            return
+        if nodetemplate.type in resources.resources:
+            cls = resources.resources[nodetemplate.type]
+            obj = cls(user, nodetemplate)
 
-        if (nodetemplate.type == "tosca.nodes.Service"):
-            return
 
-        if (nodetemplate.type != "tosca.nodes.Compute"):
-            raise Exception("I Don't know how to deal with %s" % nodetemplate.type)
-
-        host=None
-        flavor=None
-        image=None
-
-        sliceName  = None
-        for reqs in nodetemplate.requirements:
-            for (k,v) in reqs.items():
-                print v
-                if (v["relationship"] == "tosca.relationships.MemberOfSlice"):
-                    sliceName = v["node"]
-        if not sliceName:
-             raise Exception("No slice requirement for node %s" % nodetemplate.name)
-
-        slice = Slice.objects.filter(name=sliceName)
-        if not slice:
-             raise Exception("Could not find slice %s" % sliceName)
-        slice = slice[0]
-
-        capabilities = nodetemplate.get_capabilities()
-        for (k,v) in capabilities.items():
-            if (k=="host"):
-                (compute_node, flavor) = self.select_compute_node(user, v)
-            elif (k=="os"):
-                image = self.select_image(user, v)
-
-        if not compute_node:
-            raise Exception("Failed to pick a host")
-        if not image:
-            raise Exception("Failed to pick an image")
-        if not flavor:
-            raise Exception("Failed to pick a flavor")
-
-        sliver = Sliver(deployment = compute_node.site_deployment.deployment,
-                        node = compute_node,
-                        flavor = flavor,
-                        slice = slice,
-                        image = image)
-        sliver.caller = user
-        print "XXX save sliver" #sliver.save()
 
 
 
diff --git a/xos/tosca/resources/__init__.py b/xos/tosca/resources/__init__.py
new file mode 100644
index 0000000..fb0a695
--- /dev/null
+++ b/xos/tosca/resources/__init__.py
@@ -0,0 +1,37 @@
+from xosresource import XOSResource
+from django.conf.urls import patterns, url
+from rest_framework.routers import DefaultRouter
+import os, sys
+import inspect
+import importlib
+
+# XXX based on core/dashboard/views/__init__.py
+
+# Find all modules in the current directory that have descendents of the XOSResource
+# object, and add them as globals to this module. Also, build up a list of urls
+# based on the "url" field of the view classes.
+
+resources = {}
+
+sys_path_save = sys.path
+try:
+    # __import__() and importlib.import_module() both import modules from
+    # sys.path. So we make sure that the path where we can find the views is
+    # the first thing in sys.path.
+    view_dir = os.path.dirname(os.path.abspath(__file__))
+    sys.path = [view_dir] + sys.path
+    view_urls = []
+    for fn in os.listdir(view_dir):
+        pathname = os.path.join(view_dir,fn)
+        if os.path.isfile(pathname) and fn.endswith(".py") and (fn!="__init__.py"):
+            module = __import__(fn[:-3])
+            for classname in dir(module):
+                c = getattr(module, classname, None)
+
+                if inspect.isclass(c) and (getattr(c,"xos_base_class",None)=="XOSResource") and (classname not in globals()):
+                    provides = getattr(c, "provides", None)
+                    if provides:
+                        globals()[classname] = c
+                        resources[provides] = c
+finally:
+    sys.path = sys_path_save
diff --git a/xos/tosca/resources/compute.py b/xos/tosca/resources/compute.py
new file mode 100644
index 0000000..db65406
--- /dev/null
+++ b/xos/tosca/resources/compute.py
@@ -0,0 +1,76 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import Slice,Sliver,User,Flavor,Node,Image
+from nodeselect import XOSNodeSelector
+from imageselect import XOSImageSelector
+
+from xosresource import XOSResource
+
+class XOSCompute(XOSResource):
+    provides = "tosca.nodes.Compute"
+
+    def select_compute_node(self, user, v):
+        mem_size = v.get_property_value("mem_size")
+        num_cpus = v.get_property_value("num_cpus")
+        disk_size = v.get_property_value("disk_size")
+
+        # TODO: pick flavor based on parameters
+        flavor = Flavor.objects.get(name="m1.small")
+
+        compute_node = XOSNodeSelector(user, mem_size=mem_size, num_cpus=num_cpus, disk_size=disk_size).get_nodes(1)[0]
+
+        return (compute_node, flavor)
+
+    def select_image(self, user, v):
+        distribution = v.get_property_value("distribution")
+        version = v.get_property_value("version")
+        type = v.get_property_value("type")
+        architecture = v.get_property_value("architecture")
+
+        return XOSImageSelector(user, distribution=distribution, version=version, type=type, architecture=architecture).get_image()
+
+    def process_nodetemplate(self):
+        nodetemplate = self.nodetemplate
+
+        host=None
+        flavor=None
+        image=None
+
+        sliceName = self.get_requirement("tosca.relationships.MemberOfSlice", throw_exception=True)
+        slice = self.get_xos_object(Slice, name=sliceName)
+
+        capabilities = nodetemplate.get_capabilities()
+        for (k,v) in capabilities.items():
+            if (k=="host"):
+                (compute_node, flavor) = self.select_compute_node(self.user, v)
+            elif (k=="os"):
+                image = self.select_image(self.user, v)
+
+        if not compute_node:
+            raise Exception("Failed to pick a host")
+        if not image:
+            raise Exception("Failed to pick an image")
+        if not flavor:
+            raise Exception("Failed to pick a flavor")
+
+        sliver = Sliver(deployment = compute_node.site_deployment.deployment,
+                        node = compute_node,
+                        flavor = flavor,
+                        slice = slice,
+                        image = image)
+        sliver.caller = self.user
+        sliver.save()
+
+        self.resource = sliver
+
+        self.info("Created Sliver '%s' on node '%s' using flavor '%s' and image '%s'" %
+                  (str(sliver), str(compute_node), str(flavor), str(image)))
+
+    def save(self):
+        self.resource.save()
+
diff --git a/xos/tosca/resources/service.py b/xos/tosca/resources/service.py
new file mode 100644
index 0000000..0ef64ba
--- /dev/null
+++ b/xos/tosca/resources/service.py
@@ -0,0 +1,47 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import Service,User,CoarseTenant
+
+from xosresource import XOSResource
+
+class XOSService(XOSResource):
+    provides = "tosca.nodes.Service"
+
+    def process_nodetemplate(self):
+        nodetemplate = self.nodetemplate
+        serviceName = nodetemplate.name
+
+        existing_services = Service.objects.filter(name=serviceName)
+        if existing_services:
+            self.info("Service %s already exists" % serviceName)
+            service = existing_services[0]
+        else:
+            service = Service(name = serviceName)
+            service.caller = self.user
+            service.save()
+
+            self.info("Created Service '%s'" % (str(service), ))
+
+        for provider_service_name in self.get_requirements("tosca.relationships.TenantOfService"):
+            provider_service = self.get_xos_object(Service, name=provider_service_name)
+
+            existing_tenancy = CoarseTenant.get_tenant_objects().filter(provider_service = provider_service, subscriber_service = service)
+            if existing_tenancy:
+                self.info("Tenancy relationship from %s to %s already exists" % (str(service), str(provider_service)))
+            else:
+                tenancy = CoarseTenant(provider_service = provider_service,
+                                       subscriber_service = service)
+                tenancy.save()
+
+                self.info("Created Tenancy relationship  from %s to %s" % (str(service), str(provider_service)))
+
+        self.resource = service
+
+    def save(self):
+        self.resource.save()
+
diff --git a/xos/tosca/resources/slice.py b/xos/tosca/resources/slice.py
new file mode 100644
index 0000000..bbcd861
--- /dev/null
+++ b/xos/tosca/resources/slice.py
@@ -0,0 +1,39 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import Slice,User,Site
+
+from xosresource import XOSResource
+
+class XOSSlice(XOSResource):
+    provides = "tosca.nodes.Slice"
+
+    def process_nodetemplate(self):
+        nodetemplate = self.nodetemplate
+        sliceName = nodetemplate.name
+
+        existing_slices = Slice.objects.filter(name=sliceName)
+        if existing_slices:
+            self.info("Slice %s already exists" % sliceName)
+            slice = existing_slices[0]
+        else:
+            site_name = self.get_requirement("tosca.relationships.MemberOfSite", throw_exception=True)
+            site = self.get_xos_object(Site, login_base=site_name)
+
+            slice = Slice(name = sliceName,
+                          site = site)
+            slice.caller = self.user
+            slice.save()
+
+            self.info("Created Slice '%s' on Site '%s'" % (str(slice), str(site)))
+
+        self.resource = slice
+
+
+    def save(self):
+        self.resource.save()
+
diff --git a/xos/tosca/resources/xosresource.py b/xos/tosca/resources/xosresource.py
new file mode 100644
index 0000000..1bb555c
--- /dev/null
+++ b/xos/tosca/resources/xosresource.py
@@ -0,0 +1,44 @@
+class XOSResource(object):
+    xos_base_class = "XOSResource"
+    provides = None
+
+    def __init__(self, user, nodetemplate):
+        self.dirty = False
+        self.user = user
+        self.nodetemplate = nodetemplate
+        self.process_nodetemplate()
+
+    def get_requirements(self, relationship_name, throw_exception=False):
+        """ helper to search the list of requirements for a particular relationship
+            type.
+        """
+
+        results = []
+        for reqs in self.nodetemplate.requirements:
+            for (k,v) in reqs.items():
+                if (v["relationship"] == relationship_name):
+                    results.append(v["node"])
+
+        if (not results) and throw_exception:
+            raise Exception("Failed to find requirement in %s using relationship %s" % (self.nodetemplate.name, relationship_name))
+
+        return results
+
+    def get_requirement(self, relationship_name, throw_exception=False):
+        reqs = self.get_requirements(relationship_name, throw_exception)
+        if not reqs:
+            return None
+        return reqs[0]
+
+    def get_xos_object(self, cls, **kwargs):
+        objs = cls.objects.filter(**kwargs)
+        if not objs:
+            raise Exception("Failed to find %s filtered by %s" % (cls.__name__, str(kwargs)))
+        return objs[0]
+
+    def process_nodetemplate(self):
+        pass
+
+    def info(self, s):
+        print s
+
diff --git a/xos/tosca/samples/composition.yaml b/xos/tosca/samples/cord.yaml
similarity index 100%
rename from xos/tosca/samples/composition.yaml
rename to xos/tosca/samples/cord.yaml
diff --git a/xos/tosca/samples/one_instance.yaml b/xos/tosca/samples/one_instance.yaml
index 968c999..f8919ed 100644
--- a/xos/tosca/samples/one_instance.yaml
+++ b/xos/tosca/samples/one_instance.yaml
@@ -7,8 +7,15 @@
 
 topology_template:
   node_templates:
+    mysite:
+      type: tosca.nodes.Site
+
     mysite_tosca:
       type: tosca.nodes.Slice
+      requirements:
+          - slice:
+                node: mysite
+                relationship: tosca.relationships.MemberOfSite
 
     my_server:
       type: tosca.nodes.Compute