Merge pull request #153 from arpiagariu/master

doc for new Validation Rules
diff --git a/views/ngXosViews/diagnostic/mocks/data/cordsubscriber.json b/views/ngXosViews/diagnostic/mocks/data/cordsubscriber.json
index e2409a7..9f5919a 100644
--- a/views/ngXosViews/diagnostic/mocks/data/cordsubscriber.json
+++ b/views/ngXosViews/diagnostic/mocks/data/cordsubscriber.json
@@ -3,7 +3,6 @@
           "humanReadableName": "cordSubscriber-1", 
           "id": 1, 
           "service_specific_id": "123", 
-          "vlan_id": "432", 
           "s_tag": "222", 
           "c_tag": "432", 
           "vcpe_id": 4, 
diff --git a/xos/configurations/cord-pod/Makefile b/xos/configurations/cord-pod/Makefile
index 878797c..6d5ddac 100644
--- a/xos/configurations/cord-pod/Makefile
+++ b/xos/configurations/cord-pod/Makefile
@@ -18,6 +18,7 @@
 cord: vsg_custom_images
 	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/mgmt-net.yaml
 	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/cord-vtn-vsg.yaml
+	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/cord-volt-devices.yaml
 
 exampleservice:
 	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/pod-exampleservice.yaml
diff --git a/xos/configurations/cord-pod/cord-volt-devices.yaml b/xos/configurations/cord-pod/cord-volt-devices.yaml
new file mode 100644
index 0000000..8b41623
--- /dev/null
+++ b/xos/configurations/cord-pod/cord-volt-devices.yaml
@@ -0,0 +1,47 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Just enough Tosca to get the vSG slice running on the CORD POD
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    service#volt:
+      type: tosca.nodes.VOLTService
+      properties:
+          no-create: True
+          no-delete: True
+          no-update: True
+
+    voltdev-1:
+      type: tosca.nodes.VOLTDevice
+      properties:
+            driver: pmc-olt
+            openflow_id: of:1000000000000001
+            access_devices: >
+              2 222,
+              3 223,
+              4 224
+      requirements:
+          - volt_service:
+              node: service#volt
+              relationship: tosca.relationships.MemberOfService
+          - access_agent:
+              node: agent-1
+              relationship: tosca.relationships.UsesAgent
+
+    agent-1:
+      type: tosca.nodes.AccessAgent
+      properties:
+          mac: AA:BB:CC:DD:EE:FF
+          port_mappings: >
+            of:0000000000000002/2 DE:AD:BE:EF:BA:11,
+            of:0000000000000002/3 BE:EF:DE:AD:BE:EF
+      requirements:
+          - volt_service:
+              node: service#volt
+              relationship: tosca.relationships.MemberOfService
+
+
+
diff --git a/xos/configurations/cord-pod/cord-vtn-vsg.yaml b/xos/configurations/cord-pod/cord-vtn-vsg.yaml
index bb603fe..8c73799 100644
--- a/xos/configurations/cord-pod/cord-vtn-vsg.yaml
+++ b/xos/configurations/cord-pod/cord-vtn-vsg.yaml
@@ -16,7 +16,7 @@
           replaces: service_vtr
 
     service#volt:
-      type: tosca.nodes.Service
+      type: tosca.nodes.VOLTService
       requirements:
           - vsg_tenant:
               node: service#vsg
@@ -75,6 +75,50 @@
           view_url: /admin/fabric/fabricservice/$id$/
           replaces: service_fabric
 
+    service#ONOS_Fabric:
+      type: tosca.nodes.ONOSService
+      requirements:
+      properties:
+          kind: onos
+          view_url: /admin/onos/onosservice/$id$/
+          no_container: true
+          rest_hostname: onos-fabric
+          replaces: service_ONOS_Fabric
+
+    service#ONOS_CORD:
+      type: tosca.nodes.ONOSService
+      properties:
+          no-delete: true
+          no-create: true
+          no-update: true
+
+    vOLT_ONOS_app:
+      type: tosca.nodes.ONOSvOLTApp
+      requirements:
+          - onos_tenant:
+              node: service#ONOS_CORD
+              relationship: tosca.relationships.TenantOfService
+          - volt_service:
+              node: service#volt
+              relationship: tosca.relationships.UsedByService
+      properties:
+          install_dependencies: onos-ext-notifier-1.0-SNAPSHOT.oar, onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar
+          dependencies: org.onosproject.openflow-base, org.onosproject.olt, org.ciena.onos.ext_notifier, org.ciena.onos.volt_event_publisher
+          autogenerate: volt-network-cfg
+
+    vRouter_ONOS_app:
+      type: tosca.nodes.ONOSvRouterApp
+      requirements:
+          - onos_tenant:
+              node: service#ONOS_Fabric
+              relationship: tosca.relationships.TenantOfService
+          - vrouter_service:
+              node: service#vrouter
+              relationship: tosca.relationships.UsedByService
+      properties:
+          dependencies: org.onosproject.vrouter
+          autogenerate: vrouter-network-cfg
+
     Private:
       type: tosca.nodes.NetworkTemplate
 
diff --git a/xos/configurations/frontend/Makefile b/xos/configurations/frontend/Makefile
index 9e18aec..562578e 100644
--- a/xos/configurations/frontend/Makefile
+++ b/xos/configurations/frontend/Makefile
@@ -36,6 +36,8 @@
 mock-cord-pod:
 	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/fixtures.yaml
 	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord-pod/mgmt-net.yaml
+	sudo docker-compose run xos bash -c "echo somekey > /opt/xos/synchronizers/vcpe/vcpe_public_key; python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord-pod/cord-vtn-vsg.yaml"
+	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord-pod/cord-volt-devices.yaml
 	sudo docker exec frontend_xos_1 cp /opt/xos/configurations/cord-pod/xos_cord_config /opt/xos/xos_configuration/
 	sudo docker exec frontend_xos_1 touch /opt/xos/xos/settings.py
 
diff --git a/xos/configurations/test-standalone/docker-compose.yml b/xos/configurations/test-standalone/docker-compose.yml
index 5039f08..a0b87ed 100644
--- a/xos/configurations/test-standalone/docker-compose.yml
+++ b/xos/configurations/test-standalone/docker-compose.yml
@@ -11,7 +11,7 @@
 #        org.xosproject.target: swarm
 
 xos:
-    image: xosproject/xos
+    image: xosproject/xos-test
     command: python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure --makemigrations
     #command: sleep 86400    # For interactive session
     ports:
diff --git a/xos/core/xoslib/methods/cordsubscriber.py b/xos/core/xoslib/methods/cordsubscriber.py
index 0be9b33..b69b09d 100644
--- a/xos/core/xoslib/methods/cordsubscriber.py
+++ b/xos/core/xoslib/methods/cordsubscriber.py
@@ -28,7 +28,6 @@
 class CordSubscriberIdSerializer(serializers.ModelSerializer, PlusSerializerMixin):
         id = ReadOnlyField()
         service_specific_id = ReadOnlyField()
-        vlan_id = ReadOnlyField()      # XXX remove this
         c_tag = ReadOnlyField()
         s_tag = ReadOnlyField()
         vcpe_id = ReadOnlyField()
@@ -67,7 +66,7 @@
         class Meta:
             model = CordSubscriber
             fields = ('humanReadableName', 'id',
-                      'service_specific_id', 'vlan_id', 's_tag', 'c_tag',
+                      'service_specific_id', 's_tag', 'c_tag',
                       'vcpe_id', 'instance', 'instance_name', 'image', 'image_name',
                       'firewall_enable', 'firewall_rules',
                       'url_filter_enable', 'url_filter_rules', 'url_filter_level',
diff --git a/xos/core/xoslib/methods/volttenant.py b/xos/core/xoslib/methods/volttenant.py
index 229e105..25559a0 100644
--- a/xos/core/xoslib/methods/volttenant.py
+++ b/xos/core/xoslib/methods/volttenant.py
@@ -64,11 +64,6 @@
         if service_specific_id is not None:
             queryset = queryset.filter(service_specific_id=service_specific_id)
 
-#        vlan_id = self.request.query_params.get('vlan_id', None)
-#        if vlan_id is not None:
-#            ids = [x.id for x in queryset if x.get_attribute("vlan_id", None)==vlan_id]
-#            queryset = queryset.filter(id__in=ids)
-
         c_tag = self.request.query_params.get('c_tag', None)
         if c_tag is not None:
             ids = [x.id for x in queryset if x.get_attribute("c_tag", None)==c_tag]
diff --git a/xos/core/xoslib/objects/cordsubscriber.py b/xos/core/xoslib/objects/cordsubscriber.py
index 27596b7..681b769 100644
--- a/xos/core/xoslib/objects/cordsubscriber.py
+++ b/xos/core/xoslib/objects/cordsubscriber.py
@@ -39,7 +39,6 @@
                      # ("cdn_enable", "vcpe.cdn_enable"),
                      # uplink_speed, downlink_speed, status, enable_uverse
 
-                     ("vlan_id", "volt.vlan_id"),      # XXX remove this
                      ("c_tag", "volt.c_tag"),
                      ("s_tag", "volt.s_tag"),
 
diff --git a/xos/core/xoslib/static/js/xoslib/xos-backbone.js b/xos/core/xoslib/static/js/xoslib/xos-backbone.js
index 04d5fb7..d22d0ec 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-backbone.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-backbone.js
@@ -827,7 +827,7 @@
         define_model(this, {urlRoot: CORDSUBSCRIBER_API,
                             modelName: "cordSubscriber",
                             relatedCollections: {"cordUsers": "subscriber"},
-                            listFields: ["id", "service_specific_id", "vlan_id", "routeable_subnet"],
+                            listFields: ["id", "service_specific_id", "routeable_subnet"],
                             detailFields: ["id", "service_specific_id", "vcpe_id", "image_name", "instance_name",
                                            "firewall_enable", "firewall_rules", "url_filter_enable", "url_filter_rules", "cdn_enable",
                                            "nat_ip", "lan_ip", "wan_ip", "private_ip",
diff --git a/xos/services/cord/admin.py b/xos/services/cord/admin.py
index e7704d3..18f1e81 100644
--- a/xos/services/cord/admin.py
+++ b/xos/services/cord/admin.py
@@ -94,6 +94,59 @@
     def queryset(self, request):
         return VOLTTenant.get_tenant_objects_by_user(request.user)
 
+class AccessDeviceInline(XOSTabularInline):
+    model = AccessDevice
+    fields = ['volt_device','uplink','vlan']
+    readonly_fields = []
+    extra = 0
+#    max_num = 0
+    suit_classes = 'suit-tab suit-tab-accessdevices'
+
+#    @property
+#    def selflink_reverse_path(self):
+#        return "admin:cord_volttenant_change"
+
+class VOLTDeviceAdmin(ReadOnlyAwareAdmin):
+    list_display = ('backend_status_icon', 'name', 'openflow_id', 'driver' )
+    list_display_links = ('backend_status_icon', 'name', 'openflow_id')
+    fieldsets = [ (None, {'fields': ['backend_status_text','name','volt_service','openflow_id','driver','access_agent'],
+                          'classes':['suit-tab suit-tab-general']})]
+    readonly_fields = ('backend_status_text',)
+    inlines = [AccessDeviceInline]
+
+    suit_form_tabs = (('general','Details'), ('accessdevices','Access Devices'))
+
+class AccessDeviceAdmin(ReadOnlyAwareAdmin):
+    list_display = ('backend_status_icon', 'id', 'volt_device', 'uplink', 'vlan' )
+    list_display_links = ('backend_status_icon', 'id')
+    fieldsets = [ (None, {'fields': ['backend_status_text','volt_device','uplink','vlan'],
+                          'classes':['suit-tab suit-tab-general']})]
+    readonly_fields = ('backend_status_text',)
+
+    suit_form_tabs = (('general','Details'),)
+
+class AgentPortMappingInline(XOSTabularInline):
+    model = AgentPortMapping
+    fields = ['access_agent', 'mac', 'port']
+    readonly_fields = []
+    extra = 0
+#    max_num = 0
+    suit_classes = 'suit-tab suit-tab-accessportmaps'
+
+#    @property
+#    def selflink_reverse_path(self):
+#        return "admin:cord_volttenant_change"
+
+class AccessAgentAdmin(ReadOnlyAwareAdmin):
+    list_display = ('backend_status_icon', 'name', 'mac' )
+    list_display_links = ('backend_status_icon', 'name')
+    fieldsets = [ (None, {'fields': ['backend_status_text','name','volt_service','mac'],
+                          'classes':['suit-tab suit-tab-general']})]
+    readonly_fields = ('backend_status_text',)
+    inlines= [AgentPortMappingInline]
+
+    suit_form_tabs = (('general','Details'), ('accessportmaps', 'Port Mappings'))
+
 #-----------------------------------------------------------------------------
 # vCPE
 #-----------------------------------------------------------------------------
@@ -398,6 +451,10 @@
 
 admin.site.register(VOLTService, VOLTServiceAdmin)
 admin.site.register(VOLTTenant, VOLTTenantAdmin)
+admin.site.register(VOLTDevice, VOLTDeviceAdmin)
+admin.site.register(AccessDevice, AccessDeviceAdmin)
+admin.site.register(AccessAgent, AccessAgentAdmin)
+
 admin.site.register(VSGService, VSGServiceAdmin)
 admin.site.register(VSGTenant, VSGTenantAdmin)
 admin.site.register(VBNGService, VBNGServiceAdmin)
diff --git a/xos/services/cord/models.py b/xos/services/cord/models.py
index 48c9597..19b3ba6 100644
--- a/xos/services/cord/models.py
+++ b/xos/services/cord/models.py
@@ -1,5 +1,5 @@
 from django.db import models
-from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, Port, AddressPool
+from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, Port, AddressPool, User
 from core.models.plcorebase import StrippedCharField
 import os
 from django.db import models, transaction
@@ -188,15 +188,20 @@
     class Meta:
         app_label = "cord"
         verbose_name = "vOLT Service"
-        proxy = True
 
 class VOLTTenant(Tenant):
-    class Meta:
-        proxy = True
-
     KIND = VOLT_KIND
 
-    default_attributes = {"vlan_id": None, "s_tag": None, "c_tag": None}
+    class Meta:
+        app_label = "cord"
+        verbose_name = "vOLT Tenant"
+
+    s_tag = models.IntegerField(null=True, blank=True, help_text="s-tag")
+    c_tag = models.IntegerField(null=True, blank=True, help_text="c-tag")
+
+    # at some point, this should probably end up part of Tenant.
+    creator = models.ForeignKey(User, related_name='created_volts', blank=True, null=True)
+
     def __init__(self, *args, **kwargs):
         volt_services = VOLTService.get_service_objects().all()
         if volt_services:
@@ -205,32 +210,6 @@
         self.cached_vcpe = None
 
     @property
-    def s_tag(self):
-        return self.get_attribute("s_tag", self.default_attributes["s_tag"])
-
-    @s_tag.setter
-    def s_tag(self, value):
-        self.set_attribute("s_tag", value)
-
-    @property
-    def c_tag(self):
-        return self.get_attribute("c_tag", self.default_attributes["c_tag"])
-
-    @c_tag.setter
-    def c_tag(self, value):
-        self.set_attribute("c_tag", value)
-
-    # for now, vlan_id is a synonym for c_tag
-
-    @property
-    def vlan_id(self):
-        return self.c_tag
-
-    @vlan_id.setter
-    def vlan_id(self, value):
-        self.c_tag = value
-
-    @property
     def vcpe(self):
         vcpe = self.get_newest_subscribed_tenant(VSGTenant)
         if not vcpe:
@@ -257,28 +236,6 @@
             return None
         return subs[0]
 
-    @property
-    def creator(self):
-        if getattr(self, "cached_creator", None):
-            return self.cached_creator
-        creator_id=self.get_attribute("creator_id")
-        if not creator_id:
-            return None
-        users=User.objects.filter(id=creator_id)
-        if not users:
-            return None
-        user=users[0]
-        self.cached_creator = users[0]
-        return user
-
-    @creator.setter
-    def creator(self, value):
-        if value:
-            value = value.id
-        if (value != self.get_attribute("creator_id", None)):
-            self.cached_creator=None
-        self.set_attribute("creator_id", value)
-
     def manage_vcpe(self):
         # Each VOLT object owns exactly one VCPE object
 
@@ -347,9 +304,6 @@
 
         super(VOLTTenant, self).save(*args, **kwargs)
         model_policy_volt(self.pk)
-        #self.manage_vcpe()
-        #self.manage_subscriber()
-        #self.cleanup_orphans()
 
     def delete(self, *args, **kwargs):
         self.cleanup_vcpe()
@@ -366,6 +320,49 @@
         volt.manage_subscriber()
         volt.cleanup_orphans()
 
+class VOLTDevice(PlCoreBase):
+    class Meta:
+        app_label = "cord"
+
+    name = models.CharField(max_length=254, help_text="name of device", null=False, blank=False)
+    volt_service = models.ForeignKey(VOLTService, related_name='volt_devices')
+    openflow_id = models.CharField(max_length=254, help_text="OpenFlow ID", null=True, blank=True)
+    driver = models.CharField(max_length=254, help_text="driver", null=True, blank=True)
+    access_agent = models.ForeignKey("AccessAgent", related_name='volt_devices', blank=True, null=True)
+
+    def __unicode__(self): return u'%s' % (self.name)
+
+class AccessDevice(PlCoreBase):
+    class Meta:
+        app_label = "cord"
+
+    volt_device = models.ForeignKey(VOLTDevice, related_name='access_devices')
+    uplink = models.IntegerField(null=True, blank=True)
+    vlan = models.IntegerField(null=True, blank=True)
+
+    def __unicode__(self): return u'%s-%d:%d' % (self.volt_device.name,self.uplink,self.vlan)
+
+class AccessAgent(PlCoreBase):
+    class Meta:
+        app_label = "cord"
+
+    name = models.CharField(max_length=254, help_text="name of agent", null=False, blank=False)
+    volt_service = models.ForeignKey(VOLTService, related_name='access_agents')
+    mac = models.CharField(max_length=32, help_text="MAC Address or Access Agent", null=True, blank=True)
+
+    def __unicode__(self): return u'%s' % (self.name)
+
+class AgentPortMapping(PlCoreBase):
+    class Meta:
+        app_label = "cord"
+
+    access_agent = models.ForeignKey(AccessAgent, related_name='port_mappings')
+    mac = models.CharField(max_length=32, help_text="MAC Address", null=True, blank=True)
+    port = models.CharField(max_length=32, help_text="Openflow port ID", null=True, blank=True)
+
+    def __unicode__(self): return u'%s-%s-%s' % (self.access_agent.name, self.port, self.mac)
+
+
 # -------------------------------------------
 # VCPE
 # -------------------------------------------
diff --git a/xos/services/cord/templates/voltadmin.html b/xos/services/cord/templates/voltadmin.html
index e6887c5..807ab2c 100644
--- a/xos/services/cord/templates/voltadmin.html
+++ b/xos/services/cord/templates/voltadmin.html
@@ -1,6 +1,10 @@
 <div class = "row text-center">
     <div class="col-xs-12">
         <a href="/admin/cord/volttenant/">vOLT Tenants</a>
+    </div><div class="col-xs-12">
+        <a href="/admin/cord/voltdevice/">vOLT Devices</a>
+    </div><div class="col-xs-12">
+        <a href="/admin/cord/accessagent/">vOLT Access Agents</a>
     </div>
 </div>
 
diff --git a/xos/synchronizers/onos/steps/sync_onosapp.py b/xos/synchronizers/onos/steps/sync_onosapp.py
index 64c9452..3a9abfc 100644
--- a/xos/synchronizers/onos/steps/sync_onosapp.py
+++ b/xos/synchronizers/onos/steps/sync_onosapp.py
@@ -18,6 +18,7 @@
 from xos.logger import Logger, logging
 from services.vrouter.models import VRouterService
 from services.vtn.models import VTNService
+from services.cord.models import VOLTService, VOLTDevice, AccessDevice
 
 # hpclibrary will be in steps/..
 parentdir = os.path.join(os.path.dirname(__file__),"..")
@@ -243,18 +244,44 @@
         return json.dumps(data, indent=4, sort_keys=True)
 
     def get_volt_network_config(self, o, attrs):
-        data = {
-            "devices" : {
-                "of:1000000000000001" : {
-                    "accessDevice" : {
-                        "uplink" : "2",
-                        "vlan"   : "222",
-                    },
-                    "basic" : {
-                        "driver" : "pmc-olt"
-                    }
+        try:
+            volt = VOLTService.get_service_objects().all()[0]
+        except:
+            return None
+
+        devices = []
+        for voltdev in volt.volt_devices.all():
+            access_devices = []
+            for access in voltdev.access_devices.all():
+                access_device = {
+                    "uplink" : access.uplink,
+                    "vlan" : access.vlan
+                }
+                access_devices.append(access_device)
+
+            if voltdev.access_agent:
+                agent = voltdev.access_agent
+                olts = {}
+                for port_mapping in agent.port_mappings.all():
+                    olts[port_mapping.port] = port_mapping.mac
+                agent_config = {
+                    "olts" : olts,
+                    "mac" : agent.mac
+                }
+
+            device = {
+                voltdev.openflow_id : {
+                    "accessDevice" : access_devices,
+                    "accessAgent" : agent_config
+                },
+                "basic" : {
+                    "driver" : voltdev.driver
                 }
             }
+            devices.append(device)
+
+        data = {
+            "devices" : devices
         }
         return json.dumps(data, indent=4, sort_keys=True)
 
diff --git a/xos/synchronizers/vcpe/steps/sync_vcpetenant.py b/xos/synchronizers/vcpe/steps/sync_vcpetenant.py
index 9e3dfac..d8bc525 100644
--- a/xos/synchronizers/vcpe/steps/sync_vcpetenant.py
+++ b/xos/synchronizers/vcpe/steps/sync_vcpetenant.py
@@ -130,11 +130,9 @@
         else:
             logger.info("neither bbs_slice nor bbs_server is configured in the vCPE",extra=o.tologdict())
 
-        vlan_ids = []
         s_tags = []
         c_tags = []
         if o.volt:
-            vlan_ids.append(o.volt.vlan_id)  # XXX remove this
             s_tags.append(o.volt.s_tag)
             c_tags.append(o.volt.c_tag)
 
@@ -146,15 +144,14 @@
         safe_macs=[]
         if vcpe_service.url_filter_kind == "safebrowsing":
             if o.volt and o.volt.subscriber:
-                for user in o.volt.subscriber.users:
+                for user in o.volt.subscriber.devices:
                     level = user.get("level",None)
                     mac = user.get("mac",None)
                     if level in ["G", "PG"]:
                         if mac:
                             safe_macs.append(mac)
 
-        fields = {"vlan_ids": vlan_ids,   # XXX remove this
-                "s_tags": s_tags,
+        fields = {"s_tags": s_tags,
                 "c_tags": c_tags,
                 "dnsdemux_ip": dnsdemux_ip,
                 "cdn_prefixes": cdn_prefixes,
@@ -199,7 +196,7 @@
         if o.volt and o.volt.subscriber:
             url_filter_enable = o.volt.subscriber.url_filter_enable
             url_filter_level = o.volt.subscriber.url_filter_level
-            url_filter_users = o.volt.subscriber.users
+            url_filter_users = o.volt.subscriber.devices
 
         if service.url_filter_kind == "broadbandshield":
             # disable url_filter if there are no bbs_addrs
diff --git a/xos/synchronizers/vcpe/steps/sync_vcpetenant.yaml b/xos/synchronizers/vcpe/steps/sync_vcpetenant.yaml
index 3823328..9be0f98 100644
--- a/xos/synchronizers/vcpe/steps/sync_vcpetenant.yaml
+++ b/xos/synchronizers/vcpe/steps/sync_vcpetenant.yaml
@@ -9,10 +9,6 @@
       dnsdemux_ip: {{ dnsdemux_ip }}
       firewall_enable: {{ firewall_enable }}
       url_filter_enable: {{ url_filter_enable }}
-      vlan_ids:
-        {% for vlan_id in vlan_ids %}
-        - {{ vlan_id }}
-        {% endfor %}
       c_tags:
         {% for c_tag in c_tags %}
         - {{ c_tag }}
diff --git a/xos/synchronizers/vcpe/steps/sync_vcpetenant_new.yaml b/xos/synchronizers/vcpe/steps/sync_vcpetenant_new.yaml
index 324e274..435b721 100644
--- a/xos/synchronizers/vcpe/steps/sync_vcpetenant_new.yaml
+++ b/xos/synchronizers/vcpe/steps/sync_vcpetenant_new.yaml
@@ -10,10 +10,6 @@
       dnsdemux_ip: {{ dnsdemux_ip }}
       firewall_enable: {{ firewall_enable }}
       url_filter_enable: {{ url_filter_enable }}
-      vlan_ids:
-        {% for vlan_id in vlan_ids %}
-        - {{ vlan_id }}
-        {% endfor %}
       c_tags:
         {% for c_tag in c_tags %}
         - {{ c_tag }}
diff --git a/xos/synchronizers/vcpe/steps/sync_vcpetenant_vtn.yaml b/xos/synchronizers/vcpe/steps/sync_vcpetenant_vtn.yaml
index 09e4d23..b24d15a 100644
--- a/xos/synchronizers/vcpe/steps/sync_vcpetenant_vtn.yaml
+++ b/xos/synchronizers/vcpe/steps/sync_vcpetenant_vtn.yaml
@@ -10,10 +10,6 @@
       dnsdemux_ip: {{ dnsdemux_ip }}
       firewall_enable: {{ firewall_enable }}
       url_filter_enable: {{ url_filter_enable }}
-      vlan_ids:
-        {% for vlan_id in vlan_ids %}
-        - {{ vlan_id }}
-        {% endfor %}
       c_tags:
         {% for c_tag in c_tags %}
         - {{ c_tag }}
diff --git a/xos/synchronizers/vcpe/templates/vlan_sample.j2 b/xos/synchronizers/vcpe/templates/vlan_sample.j2
index a26c840..b73954b 100644
--- a/xos/synchronizers/vcpe/templates/vlan_sample.j2
+++ b/xos/synchronizers/vcpe/templates/vlan_sample.j2
@@ -1,5 +1,5 @@
 # below is a list of all vlan_ids associated with this vcpe
 
-{% for vlan_id in vlan_ids %}
+{% for vlan_id in c_tags %}
 {{ vlan_id }}
 {% endfor %}
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 473e313..109fc1d 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -309,6 +309,16 @@
                 required: true
                 description: MAC address for this device.
 
+    tosca.nodes.VOLTService:
+        derived_from: tosca.nodes.Root
+        description: >
+            CORD: The vOLT Service
+        capabilities:
+            xos_base_service_caps
+        properties:
+            xos_base_props
+            xos_base_service_props
+
     tosca.nodes.VOLTTenant:
         derived_from: tosca.nodes.Root
         description: >
@@ -325,6 +335,58 @@
                 required: false
                 description: c_tag, identifies which subscriber within s_tag
 
+    tosca.nodes.VOLTDevice:
+        derived_from: tosca.nodes.Root
+        description: >
+            CORD: A vOLT Device.
+        properties:
+            xos_base_props
+            openflow_id:
+                type: string
+                required: false
+                description: openflow id
+            driver:
+                type: string
+                required: false
+                description: driver name
+            access_devices:
+                type: string
+                required: false
+                description: list of access devices, in format "uplink vlan", multiple entries separated by commas
+
+# XXX - uncomment if we want access device to be specified as separate Tosca
+# objects, rather than encoding them into VOLTDevice.access_devices
+#    tosca.nodes.AccessDevice:
+#        derived_from: tosca.nodes.Root
+#        description: >
+#            CORD: A vOLT Access Device.
+#        properties:
+#            xos_base_props
+#            uplink:
+#               type: integer
+#               required: false
+#               description: uplink
+#            vlan:
+#               type: integer
+#               required: false
+#               description: vlan
+
+    tosca.nodes.AccessAgent:
+        derived_from: tosca.nodes.Root
+        description: >
+            CORD: A vOLT Access Agent.
+        properties:
+            xos_base_props
+            mac:
+                type: string
+                required: false
+                description: mac address
+            port_mappings:
+                type: string
+                required: false
+                description: list of port mappings, in format "port mac", multiple entries separated by commas
+
+
     tosca.nodes.User:
         derived_from: tosca.nodes.Root
 
@@ -966,6 +1028,12 @@
     tosca.relationships.TagsObject:
         derived_from: tosca.relationships.Root
 
+    tosca.relationships.MemberOfDevice:
+        derived_from: tosca.relationships.Root
+
+    tosca.relationships.UsesAgent:
+        derived_from: tosca.relationships.Root
+
     tosca.capabilities.xos.Service:
         derived_from: tosca.capabilities.Root
         description: An XOS Service
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 67d5367..8b4c669 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -766,6 +766,64 @@
                 required: true
                 description: MAC address for this device.
 
+    tosca.nodes.VOLTService:
+        derived_from: tosca.nodes.Root
+        description: >
+            CORD: The vOLT Service
+        capabilities:
+            scalable:
+                type: tosca.capabilities.Scalable
+            service:
+                type: tosca.capabilities.xos.Service
+        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
+            no-update:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to update this object
+            replaces:
+                type: string
+                required: false
+                descrption: Replaces/renames this object
+            kind:
+                type: string
+                default: generic
+                description: Type of service.
+            view_url:
+                type: string
+                required: false
+                description: URL to follow when icon is clicked in the Service Directory.
+            icon_url:
+                type: string
+                required: false
+                description: ICON to display in the Service Directory.
+            enabled:
+                type: boolean
+                default: true
+            published:
+                type: boolean
+                default: true
+                description: If True then display this Service in the Service Directory.
+            public_key:
+                type: string
+                required: false
+                description: Public key to install into Instances to allows Services to SSH into them.
+            private_key_fn:
+                type: string
+                required: false
+                description: Location of private key file
+            versionNumber:
+                type: string
+                required: false
+                description: Version number of Service.
+
     tosca.nodes.VOLTTenant:
         derived_from: tosca.nodes.Root
         description: >
@@ -789,6 +847,86 @@
                 required: false
                 description: c_tag, identifies which subscriber within s_tag
 
+    tosca.nodes.VOLTDevice:
+        derived_from: tosca.nodes.Root
+        description: >
+            CORD: A vOLT Device.
+        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
+            no-update:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to update this object
+            replaces:
+                type: string
+                required: false
+                descrption: Replaces/renames this object
+            openflow_id:
+                type: string
+                required: false
+                description: openflow id
+            driver:
+                type: string
+                required: false
+                description: driver name
+            access_devices:
+                type: string
+                required: false
+                description: list of access devices, in format "uplink vlan", multiple entries separated by commas
+
+#    tosca.nodes.AccessDevice:
+#        derived_from: tosca.nodes.Root
+#        description: >
+#            CORD: A vOLT Access Device.
+#        properties:
+#            xos_base_props
+#            uplink:
+#               type: integer
+#               required: false
+#               description: uplink
+#            vlan:
+#               type: integer
+#               required: false
+#               description: vlan
+
+    tosca.nodes.AccessAgent:
+        derived_from: tosca.nodes.Root
+        description: >
+            CORD: A vOLT Access Agent.
+        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
+            no-update:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to update this object
+            replaces:
+                type: string
+                required: false
+                descrption: Replaces/renames this object
+            mac:
+                type: string
+                required: false
+                description: mac address
+            port_mappings:
+                type: string
+                required: false
+                description: list of port mappings, in format "port mac", multiple entries separated by commas
+
+
     tosca.nodes.User:
         derived_from: tosca.nodes.Root
 
@@ -1715,6 +1853,9 @@
     tosca.relationships.TagsObject:
         derived_from: tosca.relationships.Root
 
+    tosca.relationships.MemberOfDevice:
+        derived_from: tosca.relationships.Root
+
     tosca.capabilities.xos.Service:
         derived_from: tosca.capabilities.Root
         description: An XOS Service
diff --git a/xos/tosca/resources/accessagent.py b/xos/tosca/resources/accessagent.py
new file mode 100644
index 0000000..368ce55
--- /dev/null
+++ b/xos/tosca/resources/accessagent.py
@@ -0,0 +1,56 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from services.cord.models import AccessAgent, VOLTDevice, VOLTService, AgentPortMapping
+from xosresource import XOSResource
+
+class XOSAccessAgent(XOSResource):
+    provides = "tosca.nodes.AccessAgent"
+    xos_model = AccessAgent
+    copyin_props = ["mac"]
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSAccessAgent, self).get_xos_args()
+
+        volt_service_name = self.get_requirement("tosca.relationships.MemberOfService", throw_exception=throw_exception)
+        if volt_service_name:
+            args["volt_service"] = self.get_xos_object(VOLTService, throw_exception=throw_exception, name=volt_service_name)
+
+        return args
+
+    def postprocess(self, obj):
+        # For convenient, allow the port mappings to be specified by a Tosca
+        # string with commas between lines.
+        #      <port> <mac>,
+        #      <port> <mac>,
+        #      ...
+        #      <port> <mac>
+
+        port_mappings_str = self.get_property("port_mappings")
+        port_mappings = []
+        if port_mappings_str:
+            lines = [x.strip() for x in port_mappings_str.split(",")]
+            for line in lines:
+                if not (" " in line):
+                    raise "Malformed port mapping `%s`", line
+                (port, mac) = line.split(" ")
+                port=port.strip()
+                mac=mac.strip()
+                port_mappings.append( (port, mac) )
+
+            for apm in list(AgentPortMapping.objects.filter(access_agent=obj)):
+                if (apm.port, apm.mac) not in port_mappings:
+                    print "Deleting AgentPortMapping '%s'" % apm
+                    apm.delete()
+
+            for port_mapping in port_mappings:
+                existing_objs = AgentPortMapping.objects.filter(access_agent=obj, port=port_mapping[0], mac=port_mapping[1])
+                if not existing_objs:
+                    apm = AgentPortMapping(access_agent=obj, port=port_mapping[0], mac=port_mapping[1])
+                    apm.save()
+                    print "Created AgentPortMapping '%s'" % apm
+
diff --git a/xos/tosca/resources/accessdevice.py b/xos/tosca/resources/accessdevice.py
new file mode 100644
index 0000000..94deb86
--- /dev/null
+++ b/xos/tosca/resources/accessdevice.py
@@ -0,0 +1,40 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from services.cord.models import AccessDevice, VOLTDevice
+from xosresource import XOSResource
+
+class XOSAccessDevice(XOSResource):
+    provides = "tosca.nodes.AccessDevice"
+    xos_model = AccessDevice
+    copyin_props = ["uplink", "vlan"]
+    name_field = None
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSAccessDevice, self).get_xos_args()
+
+        volt_device_name = self.get_requirement("tosca.relationships.MemberOfDevice", throw_exception=throw_exception)
+        if volt_device_name:
+            args["volt_device"] = self.get_xos_object(VOLTDevice, throw_exception=throw_exception, name=volt_device_name)
+
+        return args
+
+    # AccessDevice has no name field, so we rely on matching the keys. We assume
+    # the for a given VOLTDevice, there is only one AccessDevice per (uplink, vlan)
+    # pair.
+
+    def get_existing_objs(self):
+        args = self.get_xos_args(throw_exception=False)
+        volt_device = args.get("volt_device", None)
+        uplink = args.get("uplink", None)
+        vlan = args.get("vlan", None)
+        if (volt_device is not None) and (uplink is not None) and (vlan is not None):
+            existing_obj = self.get_xos_object(AccessDevice, volt_device=volt_device, uplink=uplink, vlan=vlan, throw_exception=False)
+            if existing_obj:
+                return [ existing_obj ]
+        return []
+
diff --git a/xos/tosca/resources/voltdevice.py b/xos/tosca/resources/voltdevice.py
new file mode 100644
index 0000000..f1c6830
--- /dev/null
+++ b/xos/tosca/resources/voltdevice.py
@@ -0,0 +1,52 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from services.cord.models import VOLTDevice, VOLTService, AccessDevice, AccessAgent
+from xosresource import XOSResource
+
+class XOSVOLTDevice(XOSResource):
+    provides = "tosca.nodes.VOLTDevice"
+    xos_model = VOLTDevice
+    copyin_props = ["openflow_id", "driver"]
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSVOLTDevice, self).get_xos_args()
+
+        volt_service_name = self.get_requirement("tosca.relationships.MemberOfService", throw_exception=throw_exception)
+        if volt_service_name:
+            args["volt_service"] = self.get_xos_object(VOLTService, throw_exception=throw_exception, name=volt_service_name)
+
+        agent_name = self.get_requirement("tosca.relationships.UsesAgent", throw_exception=throw_exception)
+        if agent_name:
+            args["access_agent"] = self.get_xos_object(AccessAgent, throw_exception=throw_exception, name=agent_name)
+
+        return args
+
+    def postprocess(self, obj):
+        access_devices_str = self.get_property("access_devices")
+        access_devices = []
+        if access_devices_str:
+            lines = [x.strip() for x in access_devices_str.split(",")]
+            for line in lines:
+                if not (" " in line):
+                    raise "Malformed access device `%s`", line
+                (uplink, vlan) = line.split(" ")
+                uplink=int(uplink.strip())
+                vlan=int(vlan.strip())
+                access_devices.append( (uplink, vlan) )
+
+            for ad in list(AccessDevice.objects.filter(volt_device=obj)):
+                if (ad.uplink, ad.vlan) not in access_devices:
+                    print "Deleting AccessDevice '%s'" % ad
+                    ad.delete()
+
+            for access_device in access_devices:
+                existing_objs = AccessDevice.objects.filter(volt_device=obj, uplink=access_device[0], vlan=access_device[1])
+                if not existing_objs:
+                    ad = AccessDevice(volt_device=obj, uplink=access_device[0], vlan=access_device[1])
+                    ad.save()
+                    print "Created AccessDevice '%s'" % ad
diff --git a/xos/tosca/resources/voltservice.py b/xos/tosca/resources/voltservice.py
new file mode 100644
index 0000000..57cf846
--- /dev/null
+++ b/xos/tosca/resources/voltservice.py
@@ -0,0 +1,15 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from services.cord.models import VOLTService
+
+from service import XOSService
+
+class XOSVOLTService(XOSService):
+    provides = "tosca.nodes.VOLTService"
+    xos_model = VOLTService
+    copyin_props = ["view_url", "icon_url", "kind", "enabled", "published", "public_key", "private_key_fn", "versionNumber"]
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index 2f8fa27..7835689 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -243,6 +243,8 @@
             'handlers': ['mail_admins'],
             'level': 'ERROR',
             'propagate': True,
+        },'django.db.backends': {
+            'level': 'WARNING',
         },
     }
 }