Merge branch 'master' of github.com:open-cloud/xos
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 02137ec..ea1425a 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -108,6 +108,21 @@
           kind: onos
           view_url: /admin/onos/onosservice/$id$/
           public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+#          rest_onos/v1/network/configuration/: >
+#            {
+#              "devices" : {
+#                "of:0000000000000001" : {
+#                  "accessDevice" : {
+#                    "uplink" : "2",
+#                    "vlan"   : "222",
+#                    "defaultVlan" : "1"
+#                  },
+#                  "basic" : {
+#                    "driver" : "default"
+#                  }
+#                }
+#              }
+#            }
       artifacts:
           pubkey: /opt/xos/observers/onos/onos_key.pub
 
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index ba54a33..953ea97 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -83,6 +83,13 @@
             service_ids = [sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
             return cls.objects.filter(id__in=service_ids)
 
+    @property
+    def serviceattribute_dict(self):
+        attrs = {}
+        for attr in self.serviceattributes.all():
+            attrs[attr.name] = attr.value
+        return attrs
+
     def __unicode__(self): return u'%s' % (self.name)
 
     def can_update(self, user):
@@ -162,7 +169,7 @@
                 # print "add instance", s
 
 class ServiceAttribute(PlCoreBase):
-    name = models.SlugField(help_text="Attribute Name", max_length=128)
+    name = models.CharField(help_text="Attribute Name", max_length=128)
     value = StrippedCharField(help_text="Attribute Value", max_length=1024)
     service = models.ForeignKey(Service, related_name='serviceattributes', help_text="The Service this attribute is associated with")
 
@@ -310,6 +317,13 @@
     def get_deleted_tenant_objects(cls):
         return cls.deleted_objects.filter(kind = cls.KIND)
 
+    @property
+    def tenantattribute_dict(self):
+        attrs = {}
+        for attr in self.tenantattributes.all():
+            attrs[attr.name] = attr.value
+        return attrs
+
     # helper function to be used in subclasses that want to ensure service_specific_id is unique
     def validate_unique_service_specific_id(self):
         if self.pk is None:
@@ -361,6 +375,7 @@
     def pick(self):
         from core.models import Node
         nodes = list(Node.objects.all())
+
         # TODO: logic to filter nodes by which nodes are up, and which
         #   nodes the slice can instantiate on.
         nodes = sorted(nodes, key=lambda node: node.instances.all().count())
diff --git a/xos/observers/onos/steps/sync_onosapp.py b/xos/observers/onos/steps/sync_onosapp.py
index 9b32298..97bb8a6 100644
--- a/xos/observers/onos/steps/sync_onosapp.py
+++ b/xos/observers/onos/steps/sync_onosapp.py
@@ -81,20 +81,25 @@
         if not os.path.exists(o.files_dir):
             os.makedirs(o.files_dir)
 
-        for attr in o.tenantattributes.all():
-            if attr.name.startswith("config_"):
-                fn = attr.name[7:] # .replace("_json",".json")
+        # Combine the service attributes with the tenant attributes. Tenant
+        # attribute can override service attributes.
+        attrs = o.provider_service.serviceattribute_dict
+        attrs.update(o.tenantattribute_dict)
+
+        for (name, value) in attrs.items():
+            if name.startswith("config_"):
+                fn = name[7:] # .replace("_json",".json")
                 o.config_fns.append(fn)
-                file(os.path.join(o.files_dir, fn),"w").write(attr.value)
-            if attr.name.startswith("rest_"):
-                fn = attr.name[5:].replace("/","_")
-                endpoint = attr.name[5:]
+                file(os.path.join(o.files_dir, fn),"w").write(value)
+            if name.startswith("rest_"):
+                fn = name[5:].replace("/","_")
+                endpoint = name[5:]
                 # Ansible goes out of it's way to make our life difficult. If
                 # 'lookup' sees a file that it thinks contains json, then it'll
                 # insist on parsing and return a json object. We just want
                 # a string, so prepend a space and then strip the space off
                 # later.
-                file(os.path.join(o.files_dir, fn),"w").write(" " +attr.value)
+                file(os.path.join(o.files_dir, fn),"w").write(" " +value)
                 o.rest_configs.append( {"endpoint": endpoint, "fn": fn} )
 
     def prepare_record(self, o):
diff --git a/xos/observers/onos/steps/sync_onosapp.yaml b/xos/observers/onos/steps/sync_onosapp.yaml
index 9ee2513..9105a2e 100644
--- a/xos/observers/onos/steps/sync_onosapp.yaml
+++ b/xos/observers/onos/steps/sync_onosapp.yaml
@@ -45,18 +45,6 @@
         {% endfor %}
 {% endif %}
 
-{% if rest_configs %}
-  - name: Add ONOS configuration values
-    uri:
-      url: http://localhost:8181/{{ '{{' }} item.endpoint {{ '}}' }} #http://localhost:8181/onos/v1/network/configuration/
-      body: "{{ '{{' }} item.body {{ '}}' }}"
-      body_format: raw
-      method: POST
-      user: karaf
-      password: karaf
-    with_items: "rest_configs"
-{% endif %}
-
   # Don't know how to check for this condition, just wait
   - name: Wait for ONOS to install the apps
     wait_for: timeout=15
@@ -71,3 +59,17 @@
         {% for dependency in dependencies %}
         - {{ dependency }}
         {% endfor %}
+
+{% if rest_configs %}
+# Do this after services have been activated, or it will cause an exception.
+# vOLT will re-read its net config; vbng may not.
+  - name: Add ONOS configuration values
+    uri:
+      url: http://localhost:8181/{{ '{{' }} item.endpoint {{ '}}' }} #http://localhost:8181/onos/v1/network/configuration/
+      body: "{{ '{{' }} item.body {{ '}}' }}"
+      body_format: raw
+      method: POST
+      user: karaf
+      password: karaf
+    with_items: "rest_configs"
+{% endif %}
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index d4e96ce..a806327 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -101,6 +101,10 @@
             xos_base_service_caps
         properties:
             xos_base_service_props
+            rest_onos/v1/network/configuration/:
+                type: string
+                required: false
+
 
     tosca.nodes.ONOSApp:
         derived_from: tosca.nodes.Root
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 0c20211..3339fdf 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -102,6 +102,10 @@
                 type: string
                 required: false
                 description: Version number of Service.
+            rest_onos/v1/network/configuration/:
+                type: string
+                required: false
+
 
     tosca.nodes.ONOSApp:
         derived_from: tosca.nodes.Root
diff --git a/xos/tosca/resources/onosservice.py b/xos/tosca/resources/onosservice.py
index 1275fab..b742ebb 100644
--- a/xos/tosca/resources/onosservice.py
+++ b/xos/tosca/resources/onosservice.py
@@ -5,6 +5,7 @@
 sys.path.append("/opt/tosca")
 from translator.toscalib.tosca_template import ToscaTemplate
 
+from core.models import ServiceAttribute
 from services.onos.models import ONOSService
 
 from service import XOSService
@@ -14,3 +15,27 @@
     xos_model = ONOSService
     copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "versionNumber"]
 
+    def set_service_attr(self, obj, prop_name, value):
+        value = self.try_intrinsic_function(value)
+        if value:
+            attrs = ServiceAttribute.objects.filter(service=obj, name=prop_name)
+            if attrs:
+                attr = attrs[0]
+                if attr.value != value:
+                    self.info("updating attribute %s" % prop_name)
+                    attr.value = value
+                    attr.save()
+            else:
+                self.info("adding attribute %s" % prop_name)
+                ta = ServiceAttribute(service=obj, name=prop_name, value=value)
+                ta.save()
+
+    def postprocess(self, obj):
+        props = self.nodetemplate.get_properties()
+        for (k,d) in props.items():
+            v = d.value
+            if k.startswith("config_"):
+                self.set_service_attr(obj, k, v)
+            elif k.startswith("rest_"):
+                self.set_service_attr(obj, k, v)
+