Merge branch 'master' of github.com:open-cloud/xos
diff --git a/xos/tosca/all_samples.sh b/xos/tosca/all_samples.sh
new file mode 100755
index 0000000..9b8f189
--- /dev/null
+++ b/xos/tosca/all_samples.sh
@@ -0,0 +1,14 @@
+# cleanup phase
+for f in samples/*.yaml; do
+   echo --------------------------------------------------
+   echo destroy $f
+   python ./destroy.py scott@onlab.us $f
+done
+
+for f in samples/*.yaml; do
+   echo --------------------------------------------------
+   echo run $f
+   python ./run.py scott@onlab.us $f
+   echo destroy $f
+   python ./destroy.py scott@onlab.us $f
+done
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index ebe1e05..044f281 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -1,6 +1,20 @@
 tosca_definitions_version: tosca_simple_yaml_1_0
 
 node_types:
+    # I wanted to make this the base of all XOS node types, but doing so throws
+    # InvalidTypeError: Type "tosca.nodes.XOS" is not a valid type
+    tosca.nodes.XOS:
+        derived_from: tosca.nodes.Root
+        properties:
+            no-delete:
+                type: boolean
+                default: false
+                description: do not allow Tosca to delete this object
+            no-create:
+                type: boolean
+                default: false
+                description: do not allow Tosca to create this object
+
     tosca.nodes.Service:
         derived_from: tosca.nodes.Root
         capabilities:
@@ -15,6 +29,42 @@
 #                default: false
 #                description: prevent this resource from being deleted
 
+    tosca.nodes.User:
+        derived_from: tosca.nodes.Root
+
+        capabilities:
+            user:
+                type: tosca.capabilities.xos.User
+
+        properties:
+            password:
+                type: string
+                required: true
+            firstname:
+                type: string
+                required: true
+            lastname:
+                type: string
+                required: true
+            phone:
+                type: string
+                required: false
+            user_url:
+                type: string
+                required: false
+            public_key:
+                type: string
+                required: false
+            is_active:
+                type: boolean
+                default: true
+            is_admin:
+                type: boolean
+                default: false
+            login_page:
+                type: string
+                required: false
+
     tosca.nodes.NetworkTemplate:
         derived_from: tosca.nodes.Root
 
@@ -160,15 +210,31 @@
 
     tosca.relationships.ConnectsToNetwork:
         derived_from: tosca.relationships.Root
-        valid_target_types: [ tosca.capabilitys.xos.Network ]
+        valid_target_types: [ tosca.capabilities.xos.Network ]
 
 #    tosca.relationships.OwnsNetwork:
 #        derived_from: tosca.relationships.Root
-#        valid_target_types: [ tosca.capabilitys.xos.Network ]
+#        valid_target_types: [ tosca.capabilities.xos.Network ]
 
     tosca.relationships.UsesNetworkTemplate:
         derived_from: tosca.relationships.Root
-        valid_target_types: [ tosca.capabilitys.xos.NetworkTemplate ]
+        valid_target_types: [ tosca.capabilities.xos.NetworkTemplate ]
+
+    tosca.relationships.AdminPrivilege:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabilities.xos.Slice, tosca.capabiltys.xos.Site ]
+
+    tosca.relationships.AccessPrivilege:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabilities.xos.Slice, tosca.capabiltys.xos.Site ]
+
+    tosca.relationships.PIPrivilege:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabiltys.xos.Site ]
+
+    tosca.relationships.TechPrivilege:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabiltys.xos.Site ]
 
     tosca.capabilities.xos.Service:
         derived_from: tosca.capabilities.Root
@@ -197,3 +263,7 @@
     tosca.capabilities.xos.Network:
         derived_from: tosca.capabilities.Root
         description: An XOS network
+
+    tosca.capabilities.xos.User:
+        derived_from: tosca.capabilities.Root
+        description: An XOS user
diff --git a/xos/tosca/engine.py b/xos/tosca/engine.py
index 5417cbc..a0d917d 100644
--- a/xos/tosca/engine.py
+++ b/xos/tosca/engine.py
@@ -122,7 +122,7 @@
         if nodetemplate.type in resources.resources:
             cls = resources.resources[nodetemplate.type]
             #print "work on", cls.__name__, nodetemplate.name
-            obj = cls(user, nodetemplate)
+            obj = cls(user, nodetemplate, self)
             obj.create_or_update()
 
     def destroy(self, user):
@@ -131,7 +131,7 @@
         for nodetemplate in nodetemplates:
             if nodetemplate.type in resources.resources:
                 cls = resources.resources[nodetemplate.type]
-                obj = cls(user, nodetemplate)
+                obj = cls(user, nodetemplate, self)
                 for model in obj.get_existing_objs():
                     models.append( (obj, model) )
         models.reverse()
@@ -139,3 +139,22 @@
             print "destroying", model
             resource.delete(model)
 
+    def name_to_xos_class(self, user, name):
+        nt = self.nodetemplates_by_name.get(name)
+        if not nt:
+            raise Exception("failed to find nodetemplate %s" % name)
+
+        cls = resources.resources.get(nt.type)
+        if not cls:
+            raise Exception("nodetemplate %s's type does not resolve to a known resource type" % name)
+
+        return (nt, cls, cls.xos_model)
+
+    def name_to_xos_model(self, user, name):
+        (nt, cls, model_class) = self.name_to_xos_class(user, name)
+        obj = cls(user, nt, self)
+        existing_objs = obj.get_existing_objs()
+        if not existing_objs:
+            raise Exception("failed to find xos %s %s" % (cls.__name__, name))
+        return existing_objs[0]
+
diff --git a/xos/tosca/resources/slice.py b/xos/tosca/resources/slice.py
index 054111c..073e205 100644
--- a/xos/tosca/resources/slice.py
+++ b/xos/tosca/resources/slice.py
@@ -5,7 +5,7 @@
 sys.path.append("/opt/tosca")
 from translator.toscalib.tosca_template import ToscaTemplate
 
-from core.models import Slice,User,Site,Network,NetworkSlice
+from core.models import Slice,User,Site,Network,NetworkSlice,SliceRole,SlicePrivilege
 
 from xosresource import XOSResource
 
@@ -27,6 +27,17 @@
                 ns.save()
                 self.info("Added network connection from '%s' to '%s'" % (str(obj), str(net)))
 
+        rolemap = ( ("tosca.relationships.AdminPrivilege", "admin"), ("tosca.relationships.AccessPrivilege", "access"),
+                    ("tosca.relationships.PIPrivilege", "pi"), ("tosca.relationships.TechPrivilege", "tech") )
+        for (rel, role) in rolemap:
+            for email in self.get_requirements(rel):
+                role = self.get_xos_object(SliceRole, role=role)
+                user = self.get_xos_object(User, email=email)
+                if not SlicePrivilege.objects.filter(user=user, role=role, slice=obj):
+                    sp = SlicePrivilege(user=user, role=role, slice=obj)
+                    sp.save()
+                    self.info("Added slice privilege on %s role %s for %s" % (str(obj), str(role), str(user)))
+
     def create(self):
         nodetemplate = self.nodetemplate
         sliceName = nodetemplate.name
diff --git a/xos/tosca/resources/user.py b/xos/tosca/resources/user.py
new file mode 100644
index 0000000..125df4f
--- /dev/null
+++ b/xos/tosca/resources/user.py
@@ -0,0 +1,75 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import User, Site, SiteRole, SliceRole, SlicePrivilege, SitePrivilege
+
+from xosresource import XOSResource
+
+class XOSUser(XOSResource):
+    provides = "tosca.nodes.User"
+    xos_model = User
+
+    def get_xos_args(self):
+        args = {"email": self.nodetemplate.name}
+
+        # copy simple string properties from the template into the arguments
+        for prop in ["password", "firstname", "lastname", "phone", "user_url", "public_key", "is_active", "is_admin", "login_page"]:
+            v = self.get_property(prop)
+            if v:
+                args[prop] = v
+
+        site_name = self.get_requirement("tosca.relationships.MemberOfSite")
+        if site_name:
+            args["site"] = self.get_xos_object(Site, login_base=site_name)
+
+        return args
+
+    def get_existing_objs(self):
+        return self.xos_model.objects.filter(email = self.nodetemplate.name)
+
+    def postprocess(self, obj):
+        rolemap = ( ("tosca.relationships.AdminPrivilege", "admin"), ("tosca.relationships.AccessPrivilege", "access"),
+                    ("tosca.relationships.PIPrivilege", "pi"), ("tosca.relationships.TechPrivilege", "tech") )
+        for (rel, role) in rolemap:
+            for obj_name in self.get_requirements(rel):
+                dest = self.engine.name_to_xos_model(self.user, obj_name)
+                if dest.__class__.__name__ == "Slice":
+                    role_obj = self.get_xos_object(SliceRole, role=role)
+                    if not SlicePrivilege.objects.filter(user=user, role=role_obj, slice=dest):
+                        sp = SlicePrivilege(user=obj, role=role_obj, slice=dest)
+                        sp.save()
+                        self.info("Added slice privilege on %s role %s for %s" % (str(dest), str(role), str(obj)))
+                elif dest.__class__.__name__ == "Site":
+                    role_obj = self.get_xos_object(SiteRole, role=role)
+                    if not SitePrivilege.objects.filter(user=obj, role=role_obj, site=dest):
+                        sp = SitePrivilege(user=obj, role=role_obj, site=dest)
+                        sp.save()
+                        self.info("Added site privilege on %s role %s for %s" % (str(dest), str(role), str(obj)))
+
+    def create(self):
+        nodetemplate = self.nodetemplate
+
+        xos_args = self.get_xos_args()
+
+        if not xos_args.get("site",None):
+             raise Exception("Site name must be specified when creating user")
+
+        user = User(**xos_args)
+        user.save()
+
+        self.postprocess(user)
+
+        self.info("Created User '%s'" % (str(user), ))
+
+    def delete(self, obj):
+        if obj.slices.exists():
+            self.info("User %s has active slices; skipping delete" % obj.name)
+            return
+        super(XOSUser, self).delete(obj)
+
+
+
diff --git a/xos/tosca/resources/xosresource.py b/xos/tosca/resources/xosresource.py
index 756aaf8..a0426cf 100644
--- a/xos/tosca/resources/xosresource.py
+++ b/xos/tosca/resources/xosresource.py
@@ -5,10 +5,11 @@
     xos_model = None
     provides = None
 
-    def __init__(self, user, nodetemplate):
+    def __init__(self, user, nodetemplate, engine):
         self.dirty = False
         self.user = user
         self.nodetemplate = nodetemplate
+        self.engine = engine
 
     def get_all_required_node_names(self):
         results = []
diff --git a/xos/tosca/resources/xossite.py b/xos/tosca/resources/xossite.py
index 4265338..fe0e256 100644
--- a/xos/tosca/resources/xossite.py
+++ b/xos/tosca/resources/xossite.py
@@ -19,7 +19,7 @@
     def get_xos_args(self):
         display_name = self.get_property("display_name")
         if not display_name:
-            display_name = nodetemplate.name
+            display_name = self.nodetemplate.name
 
         args = {"login_base": self.nodetemplate.name,
                 "name": display_name}
diff --git a/xos/tosca/samples/privileges.yaml b/xos/tosca/samples/privileges.yaml
new file mode 100644
index 0000000..d15f343
--- /dev/null
+++ b/xos/tosca/samples/privileges.yaml
@@ -0,0 +1,60 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Make some network templates
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    mysite:
+      type: tosca.nodes.Site
+
+    johndoe@foo.bar:
+      type: tosca.nodes.User
+      properties:
+          password: letmein
+          firstname: john
+          lastname: doe
+      requirements:
+          - site:
+              node: mysite
+              relationship: tosca.relationships.MemberOfSite
+          # Site privilege must always be specified in user objects, since
+          # user depends on site.
+          - privilege:
+              node: mysite
+              relationship: tosca.relationships.PIPrivilege
+
+    janedoe@foo.bar:
+      type: tosca.nodes.User
+      properties:
+          password: letmein
+          firstname: john
+          lastname: doe
+      requirements:
+          - site:
+              node: mysite
+              relationship: tosca.relationships.MemberOfSite
+          - privilege:
+              node: mysite
+              relationship: tosca.relationships.TechPrivilege
+
+    privsite:
+      type: tosca.nodes.Site
+
+    privsite_slice1:
+      type: tosca.nodes.Slice
+      requirements:
+          - slice:
+                node: privsite
+                relationship: tosca.relationships.MemberOfSite
+          # Slice privileges must always be specified in slice objects, since
+          # slice depends on user.
+          - privilege:
+                node: johndoe@foo.bar
+                relationship: tosca.relationships.AdminPrivilege
+          - privilege:
+                node: janedoe@foo.bar
+                relationship: tosca.relationships.AccessPrivilege
+
diff --git a/xos/tosca/samples/users.yaml b/xos/tosca/samples/users.yaml
new file mode 100644
index 0000000..20da611
--- /dev/null
+++ b/xos/tosca/samples/users.yaml
@@ -0,0 +1,39 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Make some network templates
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    mysite:
+      type: tosca.nodes.Site
+
+    johndoe@foo.bar:
+      type: tosca.nodes.User
+      properties:
+          password: letmein
+          firstname: john
+          lastname: doe
+      requirements:
+          - site:
+              node: mysite
+              relationship: tosca.relationships.MemberOfSite
+
+    janedoe@foo.bar:
+      type: tosca.nodes.User
+      properties:
+          password: letmeintoo
+          firstname: jane
+          lastname: doe
+          phone: 111-222-3333
+          user_url: http://janedoe/
+          public_key: asdlfkjasldkfjasldkjfhaslkdjfhglaskdjfhlaksjdhfkasdfasdf
+          is_active: true
+          is_admin: true
+      requirements:
+          - site:
+              node: mysite
+              relationship: tosca.relationships.MemberOfSite
+