CORD-1250 Update to new Service/Tenancy models

Change-Id: I2e5fa0dd7d62a6451a6726eeaba2c3aaf1b83bc9
diff --git a/xos/admin.py b/xos/admin.py
index 78e00fd..15fe786 100644
--- a/xos/admin.py
+++ b/xos/admin.py
@@ -10,7 +10,7 @@
 from django.utils import timezone
 from django.contrib.contenttypes import generic
 from suit.widgets import LinkedSelect
-from core.admin import ServiceAppAdmin,SliceInline,ServiceAttrAsTabInline, ReadOnlyAwareAdmin, XOSTabularInline, ServicePrivilegeInline, TenantRootTenantInline, TenantRootPrivilegeInline
+from core.admin import ServiceAppAdmin,SliceInline,ServiceAttrAsTabInline, ReadOnlyAwareAdmin, XOSTabularInline, ServicePrivilegeInline, SubscriberLinkInline, ProviderLinkInline, ProviderDependencyInline,SubscriberDependencyInline
 from core.middleware import get_request
 
 from functools import update_wrapper
@@ -30,7 +30,7 @@
     list_display_links = ('backend_status_icon', 'name', )
     fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description',"view_url","icon_url" ], 'classes':['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', )
-    inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
+    inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline,ProviderDependencyInline,SubscriberDependencyInline]
 
     extracontext_registered_admins = True
 
@@ -41,6 +41,7 @@
         #('tools', 'Tools'),
         ('slices','Slices'),
         ('serviceattrs','Additional Attributes'),
+        ('servicetenants', 'Dependencies'),
         ('serviceprivileges','Privileges'),
     )
 
@@ -54,8 +55,7 @@
 
     def __init__(self,*args,**kwargs):
         super (VOLTTenantForm,self ).__init__(*args,**kwargs)
-        self.fields['kind'].widget.attrs['readonly'] = True
-        self.fields['provider_service'].queryset = VOLTService.objects.all()
+        self.fields['owner'].queryset = VOLTService.objects.all()
         if self.instance:
             # fields for the attributes
             self.fields['c_tag'].initial = self.instance.c_tag
@@ -63,10 +63,9 @@
             self.fields['creator'].initial = self.instance.creator
         if (not self.instance) or (not self.instance.pk):
             # default fields for an 'add' form
-            self.fields['kind'].initial = VOLT_KIND
             self.fields['creator'].initial = get_request().user
             if VOLTService.objects.exists():
-               self.fields["provider_service"].initial = VOLTService.objects.all()[0]
+               self.fields["owner"].initial = VOLTService.objects.all()[0]
 
     def save(self, commit=True):
         self.instance.s_tag = self.cleaned_data.get("s_tag")
@@ -80,15 +79,16 @@
 
 
 class VOLTTenantAdmin(ReadOnlyAwareAdmin):
-    list_display = ('backend_status_icon', 'id', 'service_specific_id', 's_tag', 'c_tag', 'subscriber_root' )
+    list_display = ('backend_status_icon', 'id', 'service_specific_id', 's_tag', 'c_tag', )
     list_display_links = ('backend_status_icon', 'id')
-    fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'provider_service', 'subscriber_root', 'service_specific_id', # 'service_specific_attribute',
+    fieldsets = [ (None, {'fields': ['backend_status_text', 'owner', 'service_specific_id', # 'service_specific_attribute',
                                      's_tag', 'c_tag', 'creator'],
                           'classes':['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', 'service_specific_attribute')
+    inlines = (ProviderLinkInline, SubscriberLinkInline)
     form = VOLTTenantForm
 
-    suit_form_tabs = (('general','Details'),)
+    suit_form_tabs = (('general','Details'), ('servicelinks','Links'),)
 
     def get_queryset(self, request):
         return VOLTTenant.select_by_user(request.user)
diff --git a/xos/api/tenant/cord/subscriber.py b/xos/api/tenant/cord/subscriber.py
deleted file mode 100644
index 036e4cc..0000000
--- a/xos/api/tenant/cord/subscriber.py
+++ /dev/null
@@ -1,385 +0,0 @@
-from rest_framework.decorators import api_view
-from rest_framework.response import Response
-from rest_framework.reverse import reverse
-from rest_framework import serializers
-from rest_framework import generics
-from rest_framework import viewsets
-from rest_framework import status
-from rest_framework.decorators import detail_route, list_route
-from rest_framework.views import APIView
-from core.models import *
-from django.forms import widgets
-from django.conf.urls import patterns, url
-from services.volt.models import VOLTTenant
-from services.rcord.models import CordSubscriberRoot
-from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
-from django.shortcuts import get_object_or_404
-from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
-from xos.exceptions import *
-import json
-import subprocess
-from django.views.decorators.csrf import ensure_csrf_cookie
-
-class CordSubscriberNew(CordSubscriberRoot):
-    class Meta:
-        proxy = True
-        app_label = "cord"
-
-    def __init__(self, *args, **kwargs):
-        super(CordSubscriberNew, self).__init__(*args, **kwargs)
-
-    def __unicode__(self):
-        return u"cordSubscriber-%s" % str(self.id)
-
-    @property
-    def features(self):
-        return {"cdn": self.cdn_enable,
-                "uplink_speed": self.uplink_speed,
-                "downlink_speed": self.downlink_speed,
-                "uverse": self.enable_uverse,
-                "status": self.status}
-
-    @features.setter
-    def features(self, value):
-        self.cdn_enable = value.get("cdn", self.get_default_attribute("cdn_enable"))
-        self.uplink_speed = value.get("uplink_speed", self.get_default_attribute("uplink_speed"))
-        self.downlink_speed = value.get("downlink_speed", self.get_default_attribute("downlink_speed"))
-        self.enable_uverse = value.get("uverse", self.get_default_attribute("enable_uverse"))
-        self.status = value.get("status", self.get_default_attribute("status"))
-
-
-    def update_features(self, value):
-        d=self.features
-        d.update(value)
-        self.features = d
-
-    @property
-    def identity(self):
-        return {"account_num": self.service_specific_id,
-                "name": self.name}
-
-    @identity.setter
-    def identity(self, value):
-        self.service_specific_id = value.get("account_num", self.service_specific_id)
-        self.name = value.get("name", self.name)
-
-    def update_identity(self, value):
-        d=self.identity
-        d.update(value)
-        self.identity = d
-
-    @property
-    def related(self):
-        related = {}
-        if self.volt:
-            related["volt_id"] = self.volt.id
-            related["s_tag"] = self.volt.s_tag
-            related["c_tag"] = self.volt.c_tag
-            if self.volt.vcpe:
-                related["vsg_id"] = self.volt.vcpe.id
-                if self.volt.vcpe.instance:
-                    related["instance_id"] = self.volt.vcpe.instance.id
-                    related["instance_name"] = self.volt.vcpe.instance.name
-                    related["wan_container_ip"] = self.volt.vcpe.wan_container_ip
-                    if self.volt.vcpe.instance.node:
-                         related["compute_node_name"] = self.volt.vcpe.instance.node.name
-        return related
-
-    def save(self, *args, **kwargs):
-        super(CordSubscriberNew, self).save(*args, **kwargs)
-
-class CordDevice(object):
-    def __init__(self, d={}, subscriber=None):
-        self.d = d
-        self.subscriber = subscriber
-
-    @property
-    def mac(self):
-        return self.d.get("mac", None)
-
-    @mac.setter
-    def mac(self, value):
-        self.d["mac"] = value
-
-    @property
-    def identity(self):
-        return {"name": self.d.get("name", None)}
-
-    @identity.setter
-    def identity(self, value):
-        self.d["name"] = value.get("name", None)
-
-    @property
-    def features(self):
-        return {"uplink_speed": self.d.get("uplink_speed", None),
-                "downlink_speed": self.d.get("downlink_speed", None)}
-
-    @features.setter
-    def features(self, value):
-        self.d["uplink_speed"] = value.get("uplink_speed", None)
-        self.d["downlink_speed"] = value.get("downlink_speed", None)
-
-    def update_features(self, value):
-        d=self.features
-        d.update(value)
-        self.features = d
-
-    def update_identity(self, value):
-        d=self.identity
-        d.update(value)
-        self.identity = d
-
-    def save(self):
-        if self.subscriber:
-            dev=self.subscriber.find_device(self.mac)
-            if dev:
-                self.subscriber.update_device(**self.d)
-            else:
-                self.subscriber.create_device(**self.d)
-
-# Add some structure to the REST API by subdividing the object into
-# features, identity, and related.
-
-class FeatureSerializer(serializers.Serializer):
-    cdn = serializers.BooleanField(required=False)
-    uplink_speed = serializers.IntegerField(required=False)
-    downlink_speed = serializers.IntegerField(required=False)
-    uverse = serializers.BooleanField(required=False)
-    status = serializers.CharField(required=False)
-
-class IdentitySerializer(serializers.Serializer):
-    account_num = serializers.CharField(required=False)
-    name = serializers.CharField(required=False)
-
-class DeviceFeatureSerializer(serializers.Serializer):
-    uplink_speed = serializers.IntegerField(required=False)
-    downlink_speed = serializers.IntegerField(required=False)
-
-class DeviceIdentitySerializer(serializers.Serializer):
-    name = serializers.CharField(required=False)
-
-class DeviceSerializer(serializers.Serializer):
-    mac = serializers.CharField(required=True)
-    identity = DeviceIdentitySerializer(required=False)
-    features = DeviceFeatureSerializer(required=False)
-
-    class Meta:
-        fields = ('mac', 'identity', 'features')
-
-class CordSubscriberSerializer(PlusModelSerializer):
-        id = ReadOnlyField()
-        humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
-        features = FeatureSerializer(required=False)
-        identity = IdentitySerializer(required=False)
-        related = serializers.DictField(required=False)
-
-        nested_fields = ["features", "identity"]
-
-        class Meta:
-            model = CordSubscriberNew
-            fields = ('humanReadableName',
-                      'id',
-                      'features',
-                      'identity',
-                      'related')
-
-        def getHumanReadableName(self, obj):
-            return obj.__unicode__()
-
-# @ensure_csrf_cookie
-class CordSubscriberViewSet(XOSViewSet):
-    base_name = "subscriber"
-    method_name = "subscriber"
-    method_kind = "viewset"
-    queryset = CordSubscriberNew.objects.select_related().all()
-    serializer_class = CordSubscriberSerializer
-
-    custom_serializers = {"set_features": FeatureSerializer,
-                          "set_feature": FeatureSerializer,
-                          "set_identities": IdentitySerializer,
-                          "set_identity": IdentitySerializer,
-                          "get_devices": DeviceSerializer,
-                          "add_device": DeviceSerializer,
-                          "get_device_feature": DeviceFeatureSerializer,
-                          "set_device_feature": DeviceFeatureSerializer}
-
-    @classmethod
-    def get_urlpatterns(self, api_path="^"):
-        patterns = super(CordSubscriberViewSet, self).get_urlpatterns(api_path=api_path)
-        patterns.append( self.detail_url("features/$", {"get": "get_features", "put": "set_features"}, "features") )
-        patterns.append( self.detail_url("features/(?P<feature>[a-zA-Z0-9\-_]+)/$", {"get": "get_feature", "put": "set_feature"}, "get_feature") )
-        patterns.append( self.detail_url("identity/$", {"get": "get_identities", "put": "set_identities"}, "identities") )
-        patterns.append( self.detail_url("identity/(?P<identity>[a-zA-Z0-9\-_]+)/$", {"get": "get_identity", "put": "set_identity"}, "get_identity") )
-
-        patterns.append( self.detail_url("devices/$", {"get": "get_devices", "post": "add_device"}, "devicees") )
-        patterns.append( self.detail_url("devices/(?P<mac>[a-zA-Z0-9\-_:]+)/$", {"get": "get_device", "delete": "delete_device"}, "getset_device") )
-        patterns.append( self.detail_url("devices/(?P<mac>[a-zA-Z0-9\-_:]+)/features/(?P<feature>[a-zA-Z0-9\-_]+)/$", {"get": "get_device_feature", "put": "set_device_feature"}, "getset_device_feature") )
-        patterns.append( self.detail_url("devices/(?P<mac>[a-zA-Z0-9\-_:]+)/identity/(?P<identity>[a-zA-Z0-9\-_]+)/$", {"get": "get_device_identity", "put": "set_device_identity"}, "getset_device_identity") )
-
-        patterns.append( url(self.api_path + "account_num_lookup/(?P<account_num>[0-9\-]+)/$", self.as_view({"get": "account_num_detail"}), name="account_num_detail") )
-
-        patterns.append( url(self.api_path + "ssidmap/(?P<ssid>[0-9\-]+)/$", self.as_view({"get": "ssiddetail"}), name="ssiddetail") )
-        patterns.append( url(self.api_path + "ssidmap/$", self.as_view({"get": "ssidlist"}), name="ssidlist") )
-
-        return patterns
-
-    def list(self, request):
-        object_list = self.filter_queryset(self.get_queryset())
-
-        serializer = self.get_serializer(object_list, many=True)
-
-        return Response(serializer.data)
-
-    def get_features(self, request, pk=None):
-        subscriber = self.get_object()
-        return Response(FeatureSerializer(subscriber.features).data)
-
-    def set_features(self, request, pk=None):
-        subscriber = self.get_object()
-        ser = FeatureSerializer(subscriber.features, data=request.data)
-        ser.is_valid(raise_exception = True)
-        subscriber.update_features(ser.validated_data)
-        subscriber.save()
-        return Response(FeatureSerializer(subscriber.features).data)
-
-    def get_feature(self, request, pk=None, feature=None):
-        subscriber = self.get_object()
-        return Response({feature: FeatureSerializer(subscriber.features).data[feature]})
-
-    def set_feature(self, request, pk=None, feature=None):
-        subscriber = self.get_object()
-        if [feature] != request.data.keys():
-             raise serializers.ValidationError("feature %s does not match keys in request body (%s)" % (feature, ",".join(request.data.keys())))
-        ser = FeatureSerializer(subscriber.features, data=request.data)
-        ser.is_valid(raise_exception = True)
-        subscriber.update_features(ser.validated_data)
-        subscriber.save()
-        return Response({feature: FeatureSerializer(subscriber.features).data[feature]})
-
-    def get_identities(self, request, pk=None):
-        subscriber = self.get_object()
-        return Response(IdentitySerializer(subscriber.identity).data)
-
-    def set_identities(self, request, pk=None):
-        subscriber = self.get_object()
-        ser = IdentitySerializer(subscriber.identity, data=request.data)
-        ser.is_valid(raise_exception = True)
-        subscriber.update_identity(ser.validated_data)
-        subscriber.save()
-        return Response(IdentitySerializer(subscriber.identity).data)
-
-    def get_identity(self, request, pk=None, identity=None):
-        subscriber = self.get_object()
-        return Response({identity: IdentitySerializer(subscriber.identity).data[identity]})
-
-    def set_identity(self, request, pk=None, identity=None):
-        subscriber = self.get_object()
-        if [identity] != request.data.keys():
-             raise serializers.ValidationError("identity %s does not match keys in request body (%s)" % (identity, ",".join(request.data.keys())))
-        ser = IdentitySerializer(subscriber.identity, data=request.data)
-        ser.is_valid(raise_exception = True)
-        subscriber.update_identity(ser.validated_data)
-        subscriber.save()
-        return Response({identity: IdentitySerializer(subscriber.identity).data[identity]})
-
-    def get_devices(self, request, pk=None):
-        subscriber = self.get_object()
-        result = []
-        for device in subscriber.devices:
-            device = CordDevice(device, subscriber)
-            result.append(DeviceSerializer(device).data)
-        return Response(result)
-
-    def add_device(self, request, pk=None):
-        subscriber = self.get_object()
-        ser = DeviceSerializer(subscriber.devices, data=request.data)
-        ser.is_valid(raise_exception = True)
-        newdevice = CordDevice(subscriber.create_device(**ser.validated_data), subscriber)
-        subscriber.save()
-        return Response(DeviceSerializer(newdevice).data)
-
-    def get_device(self, request, pk=None, mac=None):
-        subscriber = self.get_object()
-        device = subscriber.find_device(mac)
-        if not device:
-            return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
-        return Response(DeviceSerializer(CordDevice(device, subscriber)).data)
-
-    def delete_device(self, request, pk=None, mac=None):
-        subscriber = self.get_object()
-        device = subscriber.find_device(mac)
-        if not device:
-            return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
-        subscriber.delete_device(mac)
-        subscriber.save()
-        return Response("Okay")
-
-    def get_device_feature(self, request, pk=None, mac=None, feature=None):
-        subscriber = self.get_object()
-        device = subscriber.find_device(mac)
-        if not device:
-            return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
-        return Response({feature: DeviceFeatureSerializer(CordDevice(device, subscriber).features).data[feature]})
-
-    def set_device_feature(self, request, pk=None, mac=None, feature=None):
-        subscriber = self.get_object()
-        device = subscriber.find_device(mac)
-        if not device:
-            return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
-        if [feature] != request.data.keys():
-             raise serializers.ValidationError("feature %s does not match keys in request body (%s)" % (feature, ",".join(request.data.keys())))
-        device = CordDevice(device, subscriber)
-        ser = DeviceFeatureSerializer(device.features, data=request.data)
-        ser.is_valid(raise_exception = True)
-        device.update_features(ser.validated_data)
-        device.save()
-        subscriber.save()
-        return Response({feature: DeviceFeatureSerializer(device.features).data[feature]})
-
-    def get_device_identity(self, request, pk=None, mac=None, identity=None):
-        subscriber = self.get_object()
-        device = subscriber.find_device(mac)
-        if not device:
-            return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
-        return Response({identity: DeviceIdentitySerializer(CordDevice(device, subscriber).identity).data[identity]})
-
-    def set_device_identity(self, request, pk=None, mac=None, identity=None):
-        subscriber = self.get_object()
-        device = subscriber.find_device(mac)
-        if not device:
-            return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
-        if [identity] != request.data.keys():
-             raise serializers.ValidationError("identity %s does not match keys in request body (%s)" % (feature, ",".join(request.data.keys())))
-        device = CordDevice(device, subscriber)
-        ser = DeviceIdentitySerializer(device.identity, data=request.data)
-        ser.is_valid(raise_exception = True)
-        device.update_identity(ser.validated_data)
-        device.save()
-        subscriber.save()
-        return Response({identity: DeviceIdentitySerializer(device.identity).data[identity]})
-
-    def account_num_detail(self, pk=None, account_num=None):
-        object_list = CordSubscriberNew.objects.all()
-        object_list = [x for x in object_list if x.service_specific_id == account_num]
-        if not object_list:
-            return Response("Failed to find account_num %s" % account_num, status=status.HTTP_404_NOT_FOUND)
-
-        return Response( object_list[0].id )
-
-    def ssidlist(self, request):
-        object_list = CordSubscriberNew.objects.all()
-
-        ssidmap = [ {"service_specific_id": x.service_specific_id, "subscriber_id": x.id} for x in object_list ]
-
-        return Response({"ssidmap": ssidmap})
-
-    def ssiddetail(self, pk=None, ssid=None):
-        object_list = CordSubscriberNew.objects.all()
-
-        ssidmap = [ {"service_specific_id": x.service_specific_id, "subscriber_id": x.id} for x in object_list if str(x.service_specific_id)==str(ssid) ]
-
-        if len(ssidmap)==0:
-            raise XOSNotFound("didn't find ssid %s" % str(ssid))
-
-        return Response( ssidmap[0] )
-
diff --git a/xos/api/tenant/cord/volt.py b/xos/api/tenant/cord/volt.py
deleted file mode 100644
index 7da8e4f..0000000
--- a/xos/api/tenant/cord/volt.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from rest_framework.decorators import api_view
-from rest_framework.response import Response
-from rest_framework.reverse import reverse
-from rest_framework import serializers
-from rest_framework import generics
-from rest_framework import status
-from core.models import *
-from django.forms import widgets
-from services.volt.models import VOLTTenant, VOLTService
-from services.rcord.models import CordSubscriberRoot
-from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
-from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
-
-def get_default_volt_service():
-    volt_services = VOLTService.objects.all()
-    if volt_services:
-        return volt_services[0].id
-    return None
-
-class VOLTTenantForAPI(VOLTTenant):
-    class Meta:
-        proxy = True
-        app_label = "cord"
-
-    @property
-    def subscriber(self):
-        return self.subscriber_root.id
-
-    @subscriber.setter
-    def subscriber(self, value):
-        self.subscriber_root = value
-
-    @property
-    def related(self):
-        related = {}
-        if self.vcpe:
-            related["vsg_id"] = self.vcpe.id
-            if self.vcpe.instance:
-                related["instance_id"] = self.vcpe.instance.id
-                related["instance_name"] = self.vcpe.instance.name
-                related["wan_container_ip"] = self.vcpe.wan_container_ip
-                if self.vcpe.instance.node:
-                    related["compute_node_name"] = self.vcpe.instance.node.name
-        return related
-
-class VOLTTenantSerializer(PlusModelSerializer):
-    id = ReadOnlyField()
-    service_specific_id = serializers.CharField(required=False)
-    s_tag = serializers.CharField()
-    c_tag = serializers.CharField()
-    subscriber = serializers.PrimaryKeyRelatedField(queryset=CordSubscriberRoot.objects.all(), required=False)
-    related = serializers.DictField(required=False)
-
-    property_fields=["subscriber"]
-
-    humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
-    class Meta:
-        model = VOLTTenantForAPI
-        fields = ('humanReadableName', 'id', 'service_specific_id', 's_tag', 'c_tag', 'subscriber', 'related' )
-
-    def getHumanReadableName(self, obj):
-        return obj.__unicode__()
-
-class VOLTTenantViewSet(XOSViewSet):
-    base_name = "volt"
-    method_name = "volt"
-    method_kind = "viewset"
-    queryset = VOLTTenantForAPI.objects.all()
-    serializer_class = VOLTTenantSerializer
-
-    @classmethod
-    def get_urlpatterns(self, api_path="^"):
-        patterns = super(VOLTTenantViewSet, self).get_urlpatterns(api_path=api_path)
-
-        return patterns
-
-    def list(self, request):
-        queryset = self.filter_queryset(self.get_queryset())
-
-        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]
-            queryset = queryset.filter(id__in=ids)
-
-        s_tag = self.request.query_params.get('s_tag', None)
-        if s_tag is not None:
-            ids = [x.id for x in queryset if x.get_attribute("s_tag", None)==s_tag]
-            queryset = queryset.filter(id__in=ids)
-
-        serializer = self.get_serializer(queryset, many=True)
-
-        return Response(serializer.data)
-
-
-
-
-
diff --git a/xos/attic/header.py b/xos/attic/header.py
index e76566e..1762be8 100644
--- a/xos/attic/header.py
+++ b/xos/attic/header.py
@@ -1,6 +1,6 @@
 from django.db import models
 from django.db.models import *
-from core.models import Service, XOSBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, TenantRoot, NetworkParameter, NetworkParameterType, Port, AddressPool, User
+from core.models import Service, XOSBase, Slice, Instance, ServiceInstance, ServiceInstanceLink, Node, Image, User, Flavor, NetworkParameter, NetworkParameterType, Port, AddressPool, User
 from core.models.xosbase import StrippedCharField
 import os
 from django.db import models, transaction
diff --git a/xos/attic/volttenant_model.py b/xos/attic/volttenant_model.py
index bf15c30..54a6ec3 100644
--- a/xos/attic/volttenant_model.py
+++ b/xos/attic/volttenant_model.py
@@ -1,24 +1,32 @@
 def __init__(self, *args, **kwargs):
     volt_services = VOLTService.objects.all()
     if volt_services:
-        self._meta.get_field("provider_service").default = volt_services[0].id
+        self._meta.get_field("owner").default = volt_services[0].id
     super(VOLTTenant, self).__init__(*args, **kwargs)
     self.cached_vcpe = None
 
 @property
 def vcpe(self):
+    # TODO: hardcoded service dependency
     from services.vsg.models import VSGTenant
-    vcpe = self.get_newest_subscribed_tenant(VSGTenant)
-    if not vcpe:
+
+    vsg = None
+    for link in self.subscribed_links:
+        # cast from base class to derived class
+        vsgs = VSGTenant.objects.filter(serviceinstance_ptr=link.provider_service_instance)
+        if vsgs:
+            vsg = vsgs[0]
+
+    if not vsg:
         return None
 
     # always return the same object when possible
-    if (self.cached_vcpe) and (self.cached_vcpe.id == vcpe.id):
+    if (self.cached_vcpe) and (self.cached_vcpe.id == vsg.id):
         return self.cached_vcpe
 
-    vcpe.caller = self.creator
-    self.cached_vcpe = vcpe
-    return vcpe
+    vsg.caller = self.creator
+    self.cached_vcpe = vsg
+    return vsg
 
 @vcpe.setter
 def vcpe(self, value):
@@ -26,23 +34,14 @@
 
 @property
 def subscriber(self):
-    if not self.subscriber_root:
-        return None
-    subs = CordSubscriberRoot.objects.filter(id=self.subscriber_root.id)
-    if not subs:
-        return None
-    return subs[0]
+    for link in self.provided_links:
+        # cast from base class to derived class
+        roots = CordSubscriberRoot.objects.filter(serviceinstance_ptr=link.subscriber_service_instance)
+        if roots:
+            return roots[0]
+    return None
 
 def save(self, *args, **kwargs):
-    # VOLTTenant probably doesn't need a SSID anymore; that will be handled
-    # by CORDSubscriberRoot...
-    # self.validate_unique_service_specific_id()
-
-    if (self.subscriber_root is not None):
-        subs = self.subscriber_root.get_subscribed_tenants(VOLTTenant)
-        if (subs) and (self not in subs):
-            raise XOSDuplicateKey("Subscriber should only be linked to one vOLT")
-
     if not self.creator:
         if not getattr(self, "caller", None):
             # caller must be set when creating a vCPE since it creates a slice
@@ -52,5 +51,3 @@
             raise XOSProgrammingError("VOLTTenant's self.creator was not set")
 
     super(VOLTTenant, self).save(*args, **kwargs)
-
-
diff --git a/xos/synchronizer/model_policies/model_policy_volttenant.py b/xos/synchronizer/model_policies/model_policy_volttenant.py
index b474263..8d02091 100644
--- a/xos/synchronizer/model_policies/model_policy_volttenant.py
+++ b/xos/synchronizer/model_policies/model_policy_volttenant.py
@@ -39,36 +39,40 @@
 
             self.logger.info("MODEL_POLICY: volttenant %s creating vsg" % tenant)
 
-            vcpe = VSGTenant(provider_service=vsgServices[0],
-                             subscriber_tenant=tenant)
+            vcpe = VSGTenant(owner=vsgServices[0])
             vcpe.creator = tenant.creator
             vcpe.save()
+            link = ServiceInstanceLink(provider_service_instance = vcpe, subscriber_service_instance = tenant)
+            link.save()
 
     def manage_subscriber(self, tenant):
-        if (tenant.subscriber_root is None):
-            # The vOLT is not connected to a Subscriber, so either find an
-            # existing subscriber with the same SSID, or autogenerate a new
-            # subscriber.
-            #
-            # TODO: This probably goes away when we rethink the ONOS-to-XOS
-            # vOLT API.
+        # check for existing link to a root
+        links = tenant.provided_links.all()
+        for link in links:
+            roots = CordSubscriberRoot.objects.filter(id = link.subscriber_service_instance.id)
+            if roots:
+                return
 
-            subs = CordSubscriberRoot.objects.filter(service_specific_id = tenant.service_specific_id)
-            if subs:
-                self.logger.info("MODEL_POLICY: volttenant %s using existing subscriber root" % tenant)
-                sub = subs[0]
-            else:
-                self.logger.info("MODEL_POLICY: volttenant %s creating new subscriber root" % tenant)
-                sub = CordSubscriberRoot(service_specific_id = tenant.service_specific_id,
-                                         name = "autogenerated-for-vOLT-%s" % tenant.id)
-                sub.save()
-            tenant.subscriber_root = sub
-            tenant.save()
+        subs = CordSubscriberRoot.objects.filter(service_specific_id = tenant.service_specific_id)
+        if subs:
+            self.logger.info("MODEL_POLICY: volttenant %s using existing subscriber root" % tenant)
+            sub = subs[0]
+        else:
+            self.logger.info("MODEL_POLICY: volttenant %s creating new subscriber root" % tenant)
+            sub = CordSubscriberRoot(service_specific_id = tenant.service_specific_id,
+                                     name = "autogenerated-for-vOLT-%s" % tenant.id)
+            sub.save()
+
+        link = ServiceInstanceLink(provider_service_instance = tenant, subscriber_service_instance = sub)
+        link.save()
 
     def cleanup_orphans(self, tenant):
         # ensure vOLT only has one vCPE
         cur_vcpe = tenant.vcpe
-        subscribed_vcpes = VSGTenant.objects.filter(subscriber_tenant_id = tenant.id)
-        for vcpe in subscribed_vcpes:
-            if (not cur_vcpe) or (vcpe.id != cur_vcpe.id):
-                vcpe.delete()
+
+        links = tenant.subscribed_links.all()
+        for link in links:
+            vsgs = VSGTenant.objects.filter(id = link.provider_service_instance.id)
+            for vsg in vsgs:
+                if (not cur_vcpe) or (vsg.id != cur_vcpe.id):
+                    vsg.delete()
diff --git a/xos/tosca/resources/CORDSubscriber.py b/xos/tosca/resources/CORDSubscriber.py
index a7c75c5..d91e5bb 100644
--- a/xos/tosca/resources/CORDSubscriber.py
+++ b/xos/tosca/resources/CORDSubscriber.py
@@ -1,4 +1,4 @@
-from core.models import User, TenantRootPrivilege, TenantRootRole
+#from core.models import User, TenantRootPrivilege, TenantRootRole
 from services.rcord.models import CordSubscriberRoot
 from xosresource import XOSResource
 
@@ -7,9 +7,9 @@
     xos_model = CordSubscriberRoot
     copyin_props = ["service_specific_id", "firewall_enable", "url_filter_enable", "cdn_enable", "url_filter_level"]
 
-    def postprocess(self, obj):
-        rolemap = ( ("tosca.relationships.AdminPrivilege", "admin"), ("tosca.relationships.AccessPrivilege", "access"), )
-        self.postprocess_privileges(TenantRootRole, 'TenantRoot', rolemap, obj)
+#    def postprocess(self, obj):
+#        rolemap = ( ("tosca.relationships.AdminPrivilege", "admin"), ("tosca.relationships.AccessPrivilege", "access"), )
+#        self.postprocess_privileges(TenantRootRole, TenantRootPrivilege, rolemap, obj, "tenant_root")
 
     def can_delete(self, obj):
         return super(XOSCORDSubscriber, self).can_delete(obj)
diff --git a/xos/tosca/resources/VOLTTenant.py b/xos/tosca/resources/VOLTTenant.py
index 82158a1..326cb92 100644
--- a/xos/tosca/resources/VOLTTenant.py
+++ b/xos/tosca/resources/VOLTTenant.py
@@ -1,4 +1,4 @@
-from core.models import User
+from core.models import User, ServiceInstanceLink
 from services.volt.models import VOLTTenant, VOLTService, VOLT_KIND
 from services.rcord.models import CordSubscriberRoot
 
@@ -15,26 +15,30 @@
 
         provider_name = self.get_requirement("tosca.relationships.MemberOfService", throw_exception=throw_exception)
         if provider_name:
-            args["provider_service"] = self.get_xos_object(VOLTService, throw_exception=throw_exception, name=provider_name)
-
-        subscriber_name = self.get_requirement("tosca.relationships.BelongsToSubscriber")
-        if subscriber_name:
-            args["subscriber_root"] = self.get_xos_object(CordSubscriberRoot, throw_exception=throw_exception, name=subscriber_name)
+            args["owner"] = self.get_xos_object(VOLTService, throw_exception=throw_exception, name=provider_name)
 
         return args
 
     def get_existing_objs(self):
         args = self.get_xos_args(throw_exception=False)
-        provider_service = args.get("provider_service", None)
+        provider_service = args.get("owner", None)
         service_specific_id = args.get("service_specific_id", None)
         if (provider_service) and (service_specific_id):
-            existing_obj = self.get_xos_object(VOLTTenant, kind=VOLT_KIND, provider_service=provider_service, service_specific_id=service_specific_id, throw_exception=False)
+            existing_obj = self.get_xos_object(VOLTTenant, owner=provider_service, service_specific_id=service_specific_id, throw_exception=False)
             if existing_obj:
                 return [ existing_obj ]
         return []
 
     def postprocess(self, obj):
-        pass
+        subscriber_name = self.get_requirement("tosca.relationships.BelongsToSubscriber")
+        if subscriber_name:
+            subscriber = self.get_xos_object(CordSubscriberRoot, throw_exception=True, name=subscriber_name)
+
+            links = ServiceInstanceLink.objects.filter(provider_service_instance = obj,
+                                                       subscriber_service_instance = subscriber)
+            if not links:
+                link = ServiceInstanceLink(provider_service_instance = obj, subscriber_service_instance = subscriber)
+                link.save()
 
     def can_delete(self, obj):
         return super(XOSVOLTTenant, self).can_delete(obj)
diff --git a/xos/volt-onboard.yaml b/xos/volt-onboard.yaml
index 7dd281b..a2a5619 100644
--- a/xos/volt-onboard.yaml
+++ b/xos/volt-onboard.yaml
@@ -18,7 +18,6 @@
           admin_template: templates/voltadmin.html
           #synchronizer: synchronizer/manifest
           tosca_resource: tosca/resources/voltdevice.py, tosca/resources/voltservice.py, tosca/resources/CORDSubscriber.py, tosca/resources/CORDUser.py, tosca/resources/VOLTTenant.py, tosca/resources/accessagent.py, tosca/resources/accessdevice.py
-          rest_tenant: subdirectory:cord api/tenant/cord/volt.py, subdirectory:cord api/tenant/cord/subscriber.py
           private_key: file:///opt/xos/key_import/volt_rsa
           public_key: file:///opt/xos/key_import/volt_rsa.pub
 
diff --git a/xos/volt.xproto b/xos/volt.xproto
index 3145408..e607a23 100644
--- a/xos/volt.xproto
+++ b/xos/volt.xproto
@@ -5,7 +5,7 @@
      option kind = "vOLT";
 }
 
-message VOLTTenant (Tenant){
+message VOLTTenant (ServiceInstance){
      option kind = "vOLT";
      option verbose_name = "vOLT Tenant";