CORD-2648 convert core attics to decl

Change-Id: I7146244bfa468bbd9c9bba77708262749842aa53
diff --git a/lib/xos-genx/xos-genx-tests/test_jinja2_base.py b/lib/xos-genx/xos-genx-tests/test_jinja2_base.py
new file mode 100644
index 0000000..0e7a2d4
--- /dev/null
+++ b/lib/xos-genx/xos-genx-tests/test_jinja2_base.py
@@ -0,0 +1,35 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import unittest
+from xosgenx.jinja2_extensions.base import *
+from helpers import FakeArgs, XProtoTestHelpers
+
+class Jinja2BaseTests(unittest.TestCase):
+    def test_xproto_is_true(self):
+        self.assertTrue(xproto_is_true(True))
+        self.assertTrue(xproto_is_true("True"))
+        self.assertTrue(xproto_is_true('"True"'))
+        self.assertFalse(xproto_is_true(False))
+        self.assertFalse(xproto_is_true("False"))
+        self.assertFalse(xproto_is_true('"False"'))
+        self.assertFalse(xproto_is_true(None))
+        self.assertFalse(xproto_is_true("something else"))
+
+if __name__ == '__main__':
+    unittest.main()
+
+
diff --git a/lib/xos-genx/xosgenx/generator.py b/lib/xos-genx/xosgenx/generator.py
old mode 100755
new mode 100644
index 33e96b1..cc00742
--- a/lib/xos-genx/xosgenx/generator.py
+++ b/lib/xos-genx/xosgenx/generator.py
@@ -111,10 +111,9 @@
             print "Saved: %s" % file_name
 
     @staticmethod
-    def _write_file_per_model(rendered, dir, extension, quiet):
+    def _write_file_per_model(rendered, dir, suffix, quiet):
         for m in rendered:
-
-            file_name = "%s/%s.%s" % (dir, m.lower(), extension)
+            file_name = "%s/%s%s" % (dir, m.lower(), suffix)
             if not rendered[m]:
                 if quiet == False:
                     print "Not saving %s as it is empty" % file_name
@@ -186,7 +185,7 @@
         # Validating
         if args.write_to_file == 'single' and args.dest_file is None:
             raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
-        if args.write_to_file == 'model' and args.dest_extension is None:
+        if args.write_to_file == 'model' and (args.dest_extension is None):
             raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
 
         if args.output is not None and not os.path.isabs(args.output):
@@ -262,7 +261,11 @@
                         "options": v.options
                     }
                 )
-            XOSProcessor._write_file_per_model(rendered, args.output, args.dest_extension, args.quiet)
+            if (str(v.options.get("legacy", "false")).strip('"').lower() == "true"):
+                suffix = "_decl." + args.dest_extension
+            else:
+                suffix = "." + args.dest_extension
+            XOSProcessor._write_file_per_model(rendered, args.output, suffix, args.quiet)
         else:
             rendered = template.render(
                 {"proto":
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/base.py b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
index f8224b2..dd19bd8 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/base.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
@@ -272,3 +272,9 @@
         return list
     else:
         return False
+
+def xproto_is_true(x):
+    # TODO: Audit xproto and make specification of trueness more uniform
+    if (x==True) or (x=="True") or (x=='"True"'):
+        return True
+    return False
diff --git a/lib/xos-genx/xosgenx/targets/django.xtarget b/lib/xos-genx/xosgenx/targets/django.xtarget
index ca81240..8ef8e5f 100644
--- a/lib/xos-genx/xosgenx/targets/django.xtarget
+++ b/lib/xos-genx/xosgenx/targets/django.xtarget
@@ -1,6 +1,23 @@
+{%- if options.legacy =='"True"' -%}
+{%- set legacy_tag = '_decl' -%}
+{%- set legacy = True -%}
+{%- else -%}
+{%- set legacy_tag = '' -%}
+{%- set legacy = False -%}
+{%- endif -%}
 {% for m in proto.messages %}{% if not m.options.skip_django -%}
+{% if legacy %}
+{# handle models that use custom headers rather than deriving from xosbase #}
+{% if m.options.custom_header %}
+from {{ m.options.custom_header|replace('"','') }} import *
+{% else %}
+from core.models.xosbase import *
+{% endif %}
+{% else %}
 {% if file_exists(xproto_base_name(m.name)|lower+'_header.py') -%}from {{xproto_base_name(m.name)|lower }}_header import *{%- else -%}from header import *{% endif %}
 {% if file_exists(xproto_base_name(m.name)|lower+'_top.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_top.py') }} {% endif %}
+{% endif %}
+
 {%- for l in m.links %}
 
 {% if l.peer.name != m.name %}
@@ -24,7 +41,7 @@
 {{ xproto_fol_to_python_validator(policy, proto.policies[policy], m, error) }}
 {% endfor %}
 
-class {{ m.name }}{{ xproto_base_def(m.name, m.bases) }}:
+class {{ m.name }}{{ legacy_tag }}{{ xproto_base_def(m.name, m.bases) }}:
   plural_name = "{{ xproto_pluralize(m) }}"
 
 {#  {% if m.options.no_sync or m.options.no_policy %}#}
@@ -53,11 +70,16 @@
   {%- endfor %}
 
   # Meta
+  class Meta:
   {%- set uniques = xproto_field_graph_components(m.fields) %}
   {%- if uniques %}
-  class Meta:
       unique_together = {{ xproto_tuplify(uniques) }}
   {%- endif %}
+  {%- if xproto_is_true(m.options.abstract) %}
+      abstract=True
+  {%- endif %}
+      pass
+
   {% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
   pass
 
@@ -75,7 +97,7 @@
       {% for policy,error in xproto_validations(m.options) %}
       policy_{{policy}}_validator(self, None)
       {% endfor %}
-      super({{ m.name }}, self).save(*args, **kwds)
+      super({{ m.name }}{{ legacy_tag }}, self).save(*args, **kwds)
 
   def can_access(self, ctx):
       {% if m.policy %}
diff --git a/xos/core/models/addresspool.py b/xos/core/models/addresspool.py
new file mode 100644
index 0000000..dd8df6b
--- /dev/null
+++ b/xos/core/models/addresspool.py
@@ -0,0 +1,105 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import socket
+import struct
+
+from xos.exceptions import *
+from addresspool_decl import *
+
+class AddressPool(AddressPool_decl):
+    class Meta:
+        proxy = True
+
+    def expand_cidr(self, cidr):
+        (network, bits) = cidr.split("/")
+        network = network.strip()
+        bits = int(bits.strip())
+
+        dest = []
+
+        netmask = (~(pow(2, 32 - bits) - 1) & 0xFFFFFFFF)
+
+        count = pow(2, 32 - bits)
+        for i in range(2, count - 1):
+            ip = struct.unpack("!L", socket.inet_aton(network))[0]
+            ip = ip & netmask | i
+            dest.append(socket.inet_ntoa(struct.pack("!L", ip)))
+
+        return (dest, bits)
+
+    def save(self, *args, **kwargs):
+        """
+        We need to convert subnets into lists of addresses before saving
+        """
+        if self.addresses and "/" in self.addresses:
+            original_addresses = self.addresses
+            (cidr_addrs, cidr_netbits) = self.expand_cidr(self.addresses)
+            self.addresses = " ".join(cidr_addrs)
+            if not self.cidr:
+                self.cidr = original_addresses
+
+        super(AddressPool, self).save(*args, **kwargs)
+
+    def get_address(self):
+        with transaction.atomic():
+            ap = AddressPool.objects.get(pk=self.pk)
+            if ap.addresses:
+                avail_ips = ap.addresses.split()
+            else:
+                avail_ips = []
+
+            if ap.inuse:
+                inuse_ips = ap.inuse.split()
+            else:
+                inuse_ips = []
+
+            while avail_ips:
+                addr = avail_ips.pop(0)
+
+                if addr in inuse_ips:
+                    # This may have happened if someone re-ran the tosca
+                    # recipe and 'refilled' the AddressPool while some addresses
+                    # were still in use.
+                    continue
+
+                inuse_ips.insert(0,addr)
+
+                ap.inuse = " ".join(inuse_ips)
+                ap.addresses = " ".join(avail_ips)
+                ap.save()
+                return addr
+
+            addr = None
+        return addr
+
+    def put_address(self, addr):
+        with transaction.atomic():
+            ap = AddressPool.objects.get(pk=self.pk)
+            addresses = ap.addresses or ""
+            parts = addresses.split()
+            if addr not in parts:
+                parts.insert(0,addr)
+                ap.addresses = " ".join(parts)
+
+            inuse = ap.inuse or ""
+            parts = inuse.split()
+            if addr in parts:
+                parts.remove(addr)
+                ap.inuse = " ".join(parts)
+
+            ap.save()
+
+
diff --git a/xos/core/models/attic/README.md b/xos/core/models/attic/README.md
deleted file mode 100644
index 784a8b6..0000000
--- a/xos/core/models/attic/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This directory contains legacy code that needs to be refactored and eliminated. Eventually, the attic will be empty.
diff --git a/xos/core/models/attic/address_top.py b/xos/core/models/attic/address_top.py
deleted file mode 100644
index bf71b64..0000000
--- a/xos/core/models/attic/address_top.py
+++ /dev/null
@@ -1,17 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import socket
-import struct
diff --git a/xos/core/models/attic/addresspool_model.py b/xos/core/models/attic/addresspool_model.py
deleted file mode 100644
index 2aad18c..0000000
--- a/xos/core/models/attic/addresspool_model.py
+++ /dev/null
@@ -1,97 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# imported in address_top.py
-# import socket
-# import struct
-
-def expand_cidr(self, cidr):
-    (network, bits) = cidr.split("/")
-    network = network.strip()
-    bits = int(bits.strip())
-
-    dest = []
-
-    netmask = (~(pow(2, 32 - bits) - 1) & 0xFFFFFFFF)
-
-    count = pow(2, 32 - bits)
-    for i in range(2, count - 1):
-        ip = struct.unpack("!L", socket.inet_aton(network))[0]
-        ip = ip & netmask | i
-        dest.append(socket.inet_ntoa(struct.pack("!L", ip)))
-
-    return (dest, bits)
-
-def __xos_save_base(self, *args, **kwds):
-    """
-    We need to convert subnets into lists of addresses before saving
-    """
-    if self.addresses and "/" in self.addresses:
-        original_addresses = self.addresses
-        (cidr_addrs, cidr_netbits) = self.expand_cidr(self.addresses)
-        self.addresses = " ".join(cidr_addrs)
-        if not self.cidr:
-            self.cidr = original_addresses
-
-def get_address(self):
-    with transaction.atomic():
-        ap = AddressPool.objects.get(pk=self.pk)
-        if ap.addresses:
-            avail_ips = ap.addresses.split()
-        else:
-            avail_ips = []
-
-        if ap.inuse:
-            inuse_ips = ap.inuse.split()
-        else:
-            inuse_ips = []
-
-        while avail_ips:
-            addr = avail_ips.pop(0)
-
-            if addr in inuse_ips:
-                # This may have happened if someone re-ran the tosca
-                # recipe and 'refilled' the AddressPool while some addresses
-                # were still in use.
-                continue
-
-            inuse_ips.insert(0,addr)
-
-            ap.inuse = " ".join(inuse_ips)
-            ap.addresses = " ".join(avail_ips)
-            ap.save()
-            return addr
-
-        addr = None
-    return addr
-
-def put_address(self, addr):
-    with transaction.atomic():
-        ap = AddressPool.objects.get(pk=self.pk)
-        addresses = ap.addresses or ""
-        parts = addresses.split()
-        if addr not in parts:
-            parts.insert(0,addr)
-            ap.addresses = " ".join(parts)
-
-        inuse = ap.inuse or ""
-        parts = inuse.split()
-        if addr in parts:
-            parts.remove(addr)
-            ap.inuse = " ".join(parts)
-
-        ap.save()
-
-
diff --git a/xos/core/models/attic/controller_model.py b/xos/core/models/attic/controller_model.py
deleted file mode 100644
index b0fb0b2..0000000
--- a/xos/core/models/attic/controller_model.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/xos/core/models/attic/controllerslice_model.py b/xos/core/models/attic/controllerslice_model.py
deleted file mode 100644
index 026fc88..0000000
--- a/xos/core/models/attic/controllerslice_model.py
+++ /dev/null
@@ -1,24 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def tologdict(self):
-    d=super(ControllerSlice,self).tologdict()
-    try:
-        d['slice_name']=self.slice.name
-        d['controller_name']=self.controller.name
-    except:
-        pass
-    return d
diff --git a/xos/core/models/attic/deployment_model.py b/xos/core/models/attic/deployment_model.py
deleted file mode 100644
index 4bf0f3b..0000000
--- a/xos/core/models/attic/deployment_model.py
+++ /dev/null
@@ -1,47 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def get_acl(self):
-    return AccessControlList(self.accessControl)
-
-def test_acl(self, slice=None, user=None):
-    potential_users=[]
-
-    if user:
-        potential_users.append(user)
-
-    if slice:
-        potential_users.append(slice.creator)
-        for priv in slice.sliceprivileges.all():
-            if priv.user not in potential_users:
-                potential_users.append(priv.user)
-
-    acl = self.get_acl()
-    for user in potential_users:
-        if acl.test(user) == "allow":
-            return True
-
-    return False
-
-@staticmethod
-def select_by_acl(user):
-    ids = []
-    for deployment in Deployment.objects.all():
-        acl = deployment.get_acl()
-        if acl.test(user) == "allow":
-            ids.append(deployment.id)
-
-    return Deployment.objects.filter(id__in=ids)
diff --git a/xos/core/models/attic/header.py b/xos/core/models/attic/header.py
deleted file mode 100644
index c65ca03..0000000
--- a/xos/core/models/attic/header.py
+++ /dev/null
@@ -1,87 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from __future__ import absolute_import
-
-import sys
-import json
-import operator
-from operator import attrgetter
-from core.models.xosbase import *
-from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
-from django.contrib.contenttypes.models import ContentType
-from django.utils.timezone import now
-from core.acl import AccessControlList
-from django.core.exceptions import ValidationError,PermissionDenied
-
-from distutils.version import LooseVersion
-from django.db import models,transaction
-from django.db.models import *
-from django.core.validators import URLValidator
-from xos.exceptions import *
-import urlparse
-
-def ParseNatList(ports):
-    """ Support a list of ports in the format "protocol:port, protocol:port, ..."
-        examples:
-            tcp 123
-            tcp 123:133
-            tcp 123, tcp 124, tcp 125, udp 201, udp 202
-
-        User can put either a "/" or a " " between protocol and ports
-        Port ranges can be specified with "-" or ":"
-    """
-    nats = []
-    if ports:
-        parts = ports.split(",")
-        for part in parts:
-            part = part.strip()
-            if "/" in part:
-                (protocol, ports) = part.split("/",1)
-            elif " " in part:
-                (protocol, ports) = part.split(None,1)
-            else:
-                raise TypeError('malformed port specifier %s, format example: "tcp 123, tcp 201:206, udp 333"' % part)
-
-            protocol = protocol.strip()
-            ports = ports.strip()
-
-            if not (protocol in ["udp", "tcp"]):
-                raise ValueError('unknown protocol %s' % protocol)
-
-            if "-" in ports:
-                (first, last) = ports.split("-")
-                first = int(first.strip())
-                last = int(last.strip())
-                portStr = "%d:%d" % (first, last)
-            elif ":" in ports:
-                (first, last) = ports.split(":")
-                first = int(first.strip())
-                last = int(last.strip())
-                portStr = "%d:%d" % (first, last)
-            else:
-                portStr = "%d" % int(ports)
-
-            nats.append( {"l4_protocol": protocol, "l4_port": portStr} )
-
-    return nats
-
-def ValidateNatList(ports):
-    try:
-        ParseNatList(ports)
-    except Exception,e:
-        raise ValidationError(str(e))
-
diff --git a/xos/core/models/attic/instance_model.py b/xos/core/models/attic/instance_model.py
deleted file mode 100644
index e29c26c..0000000
--- a/xos/core/models/attic/instance_model.py
+++ /dev/null
@@ -1,100 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def get_controller (self):
-    return self.node.site_deployment.controller
-
-def tologdict(self):
-    d=super(Instance,self).tologdict()
-    try:
-        d['slice_name']=self.slice.name
-        d['controller_name']=self.get_controller().name
-    except:
-        pass
-    return d
-
-def __xos_save_base(self, *args, **kwds):
-    if not self.name:
-        self.name = self.slice.name
-    if not self.creator and hasattr(self, 'caller'):
-        self.creator = self.caller
-
-def all_ips(self):
-    ips={}
-    for ns in self.ports.all():
-       if ns.ip:
-           ips[ns.network.name] = ns.ip
-    return ips
-
-def all_ips_string(self):
-    result = []
-    ips = self.all_ips()
-    for key in sorted(ips.keys()):
-        #result.append("%s = %s" % (key, ips[key]))
-        result.append(ips[key])
-    return ", ".join(result)
-all_ips_string.short_description = "addresses"
-
-def get_public_ip(self):
-    for ns in self.ports.all():
-        if (ns.ip) and (ns.network.template.visibility=="public") and (ns.network.template.translation=="none"):
-            return ns.ip
-    return None
-
-# return an address on nat-net
-def get_network_ip(self, pattern):
-    for ns in self.ports.all():
-        if pattern in ns.network.name.lower():
-            return ns.ip
-    return None
-
-# return an address that the synchronizer can use to SSH to the instance
-def get_ssh_ip(self):
-    # first look specifically for a management_local network
-    for ns in self.ports.all():
-        if ns.network.template and ns.network.template.vtn_kind=="MANAGEMENT_LOCAL":
-            return ns.ip
-
-    # for compatibility, now look for any management network
-    management=self.get_network_ip("management")
-    if management:
-        return management
-
-    # if all else fails, look for nat-net (for OpenCloud?)
-    return self.get_network_ip("nat")
-
-def get_ssh_command(self):
-    if (not self.instance_id) or (not self.node) or (not self.instance_name):
-        return None
-    else:
-        return 'ssh -o "ProxyCommand ssh -q %s@%s" ubuntu@%s' % (self.instance_id, self.node.name, self.instance_name)
-
-def get_public_keys(self):
-    from core.models.sliceprivilege import Privilege
-    slice_privileges = Privilege.objects.filter(object_id=self.slice.id, object_type='Slice', accessor_type='User')
-    slice_users = [User.objects.get(pk = priv.accessor_id) for priv in slice_privileges]
-    pubkeys = set([u.public_key for u in slice_users if u.public_key])
-
-    if self.creator.public_key:
-        pubkeys.add(self.creator.public_key)
-
-    if self.slice.creator.public_key:
-        pubkeys.add(self.slice.creator.public_key)
-
-    if self.slice.service and self.slice.service.public_key:
-        pubkeys.add(self.slice.service.public_key)
-
-    return pubkeys
diff --git a/xos/core/models/attic/nodelabel_model.py b/xos/core/models/attic/nodelabel_model.py
deleted file mode 100644
index 67df5f9..0000000
--- a/xos/core/models/attic/nodelabel_model.py
+++ /dev/null
@@ -1,29 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-def __xos_save_base(self, *args, **kwds):
-    """ Hack to allow the creation of NodeLabel objects from outside core 
-        until the ORM is extended with support for ManyToMany relations.
-    """
-
-    if self.name and '###' in self.name:
-        from core.models import Node
-
-        self.name, node_id_str = self.name.split('###')
-        node_ids = map(int, node_id_str.split(','))
-
-        for node_id in node_ids:
-            node = Node.get(node_id)
-            self.node.add(node)
diff --git a/xos/core/models/attic/service_header.py b/xos/core/models/attic/service_header.py
deleted file mode 100644
index 22ba19d..0000000
--- a/xos/core/models/attic/service_header.py
+++ /dev/null
@@ -1,202 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from __future__ import absolute_import
-
-from core.models.xos import XOS
-from core.models.xosbase import *
-from django.core.validators import URLValidator
-import urlparse
-from operator import attrgetter
-import json
-from distutils.version import LooseVersion
-from django.core.validators import URLValidator
-from xos.exceptions import *
-
-
-COARSE_KIND = "coarse"
-
-def get_xos():
-    xos = XOS.objects.all()
-
-    if xos:
-       return xos[0]
-    else:
-       return None
-
-class AttributeMixin(object):
-    # helper for extracting things from a json-encoded
-    # service_specific_attribute
-
-    def get_attribute(self, name, default=None):
-        if self.service_specific_attribute:
-            attributes = json.loads(self.service_specific_attribute)
-        else:
-            attributes = {}
-        return attributes.get(name, default)
-
-    def set_attribute(self, name, value):
-        if self.service_specific_attribute:
-            attributes = json.loads(self.service_specific_attribute)
-        else:
-            attributes = {}
-        attributes[name] = value
-        self.service_specific_attribute = json.dumps(attributes)
-
-    def get_initial_attribute(self, name, default=None):
-        if self._initial["service_specific_attribute"]:
-            attributes = json.loads(
-                self._initial["service_specific_attribute"])
-        else:
-            attributes = {}
-        return attributes.get(name, default)
-
-    @classmethod
-    def get_default_attribute(cls, name):
-        for (attrname, default) in cls.simple_attributes:
-            if attrname == name:
-                return default
-        if hasattr(cls, "default_attributes"):
-            if name in cls.default_attributes:
-                return cls.default_attributes[name]
-
-        return None
-
-    @classmethod
-    def setup_simple_attributes(cls):
-        for (attrname, default) in cls.simple_attributes:
-            setattr(cls, attrname, property(lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
-                                            lambda self, value, attrname=attrname: self.set_attribute(
-                                                attrname, value),
-                                            None,
-                                            attrname))
-
-
-class Scheduler(object):
-    # XOS Scheduler Abstract Base Class
-    # Used to implement schedulers that pick which node to put instances on
-
-    def __init__(self, slice):
-        self.slice = slice
-
-    def pick(self):
-        # this method should return a tuple (node, parent)
-        #    node is the node to instantiate on
-        #    parent is for container_vm instances only, and is the VM that will
-        #      hold the container
-
-        raise Exception("Abstract Base")
-
-
-class LeastLoadedNodeScheduler(Scheduler):
-    # This scheduler always return the node with the fewest number of
-    # instances.
-
-    def __init__(self, slice, label=None):
-        super(LeastLoadedNodeScheduler, self).__init__(slice)
-        self.label = label
-
-    def pick(self):
-        from core.models import Node
-
-        # start with all nodes
-        nodes = Node.objects.all()
-
-        # if a label is set, then filter by label
-        if self.label:
-            nodes = nodes.filter(nodelabels__name=self.label)
-
-        # if slice.default_node is set, then filter by default_node
-        if self.slice.default_node:
-            nodes = nodes.filter(name = self.slice.default_node)
-
-        # convert to list
-        nodes = list(nodes)
-
-        # sort so that we pick the least-loaded node
-        nodes = sorted(nodes, key=lambda node: node.instances.all().count())
-
-        if not nodes:
-            raise Exception(
-                "LeastLoadedNodeScheduler: No suitable nodes to pick from")
-
-        # 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())
-        return [nodes[0], None]
-
-
-class ContainerVmScheduler(Scheduler):
-    # This scheduler picks a VM in the slice with the fewest containers inside
-    # of it. If no VMs are suitable, then it creates a VM.
-
-    MAX_VM_PER_CONTAINER = 10
-
-    def __init__(self, slice):
-        super(ContainerVmScheduler, self).__init__(slice)
-
-    @property
-    def image(self):
-        from core.models import Image
-
-        # If slice has default_image set then use it
-        if self.slice.default_image:
-            return self.slice.default_image
-
-        raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
-
-    def make_new_instance(self):
-        from core.models import Instance, Flavor
-
-        flavors = Flavor.objects.filter(name="m1.small")
-        if not flavors:
-            raise XOSConfigurationError("No m1.small flavor")
-
-        (node, parent) = LeastLoadedNodeScheduler(self.slice).pick()
-
-        instance = Instance(slice=self.slice,
-                            node=node,
-                            image=self.image,
-                            creator=self.slice.creator,
-                            deployment=node.site_deployment.deployment,
-                            flavor=flavors[0],
-                            isolation="vm",
-                            parent=parent)
-        instance.save()
-        # We rely on a special naming convention to identify the VMs that will
-        # hole containers.
-        instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
-        instance.save()
-        return instance
-
-    def pick(self):
-        from core.models import Instance, Flavor
-
-        for vm in self.slice.instances.filter(isolation="vm"):
-            avail_vms = []
-            if (vm.name.startswith("%s-outer-" % self.slice.name)):
-                container_count = Instance.objects.filter(parent=vm).count()
-                if (container_count < self.MAX_VM_PER_CONTAINER):
-                    avail_vms.append((vm, container_count))
-            # sort by least containers-per-vm
-            avail_vms = sorted(avail_vms, key=lambda x: x[1])
-            print "XXX", avail_vms
-            if avail_vms:
-                instance = avail_vms[0][0]
-                return (instance.node, instance)
-
-        instance = self.make_new_instance()
-        return (instance.node, instance)
diff --git a/xos/core/models/attic/service_model.py b/xos/core/models/attic/service_model.py
deleted file mode 100644
index f7e8ca0..0000000
--- a/xos/core/models/attic/service_model.py
+++ /dev/null
@@ -1,185 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-KIND="generic"
-
-def __init__(self, *args, **kwargs):
-    # for subclasses, set the default kind appropriately
-    self._meta.get_field("kind").default = self.KIND
-    super(Service, self).__init__(*args, **kwargs)
-
-@property
-def serviceattribute_dict(self):
-    attrs = {}
-    for attr in self.serviceattributes.all():
-        attrs[attr.name] = attr.value
-    return attrs
-
-def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
-    """
-         Get a list of nodes that can be used to scale up a slice.
-
-            slice - slice to scale up
-            max_per_node - maximum numbers of instances that 'slice' can have on a single node
-            exclusive_slices - list of slices that must have no nodes in common with 'slice'.
-    """
-
-    # late import to get around order-of-imports constraint in __init__.py
-    from core.models import Node, Instance
-
-    nodes = list(Node.objects.all())
-
-    conflicting_instances = Instance.objects.filter(
-        slice__in=exclusive_slices)
-    conflicting_nodes = Node.objects.filter(
-        instances__in=conflicting_instances)
-
-    nodes = [x for x in nodes if x not in conflicting_nodes]
-
-    # If max_per_node is set, then limit the number of instances this slice
-    # can have on a single node.
-    if max_per_node:
-        acceptable_nodes = []
-        for node in nodes:
-            existing_count = node.instances.filter(slice=slice).count()
-            if existing_count < max_per_node:
-                acceptable_nodes.append(node)
-        nodes = acceptable_nodes
-
-    return nodes
-
-def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
-    # Pick the best node to scale up a slice.
-
-    nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
-    nodes = sorted(nodes, key=lambda node: node.instances.all().count())
-    if not nodes:
-        return None
-    return nodes[0]
-
-def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
-    # late import to get around order-of-imports constraint in __init__.py
-    from core.models import Instance
-
-    slices = [x for x in self.slices.all() if slice_hint in x.name]
-    for slice in slices:
-        while slice.instances.all().count() > scale:
-            s = slice.instances.all()[0]
-            # print "drop instance", s
-            s.delete()
-
-        while slice.instances.all().count() < scale:
-            node = self.pick_node(slice, max_per_node, exclusive_slices)
-            if not node:
-                # no more available nodes
-                break
-
-            image = slice.default_image
-            if not image:
-                raise XOSConfigurationError(
-                    "No default_image for slice %s" % slice.name)
-
-            flavor = slice.default_flavor
-            if not flavor:
-                raise XOSConfigurationError(
-                    "No default_flavor for slice %s" % slice.name)
-
-            s = Instance(slice=slice,
-                         node=node,
-                         creator=slice.creator,
-                         image=image,
-                         flavor=flavor,
-                         deployment=node.site_deployment.deployment)
-            s.save()
-
-            # print "add instance", s
-
-def get_vtn_src_nets(self):
-    nets = []
-    for slice in self.slices.all():
-        for ns in slice.networkslices.all():
-            if not ns.network:
-                continue
-#                if ns.network.template.access in ["direct", "indirect"]:
-#                    # skip access networks; we want to use the private network
-#                    continue
-            if "management" in ns.network.name:
-                # don't try to connect the management network to anything
-                continue
-            if ns.network.name in ["wan_network", "lan_network"]:
-                # we don't want to attach to the vCPE's lan or wan network
-                # we only want to attach to its private network
-                # TODO: fix hard-coding of network name
-                continue
-            for cn in ns.network.controllernetworks.all():
-                if cn.net_id:
-                    net = {"name": ns.network.name, "net_id": cn.net_id}
-                    nets.append(net)
-    return nets
-
-def get_vtn_nets(self):
-    nets = []
-    for slice in self.slices.all():
-        for ns in slice.networkslices.all():
-            if not ns.network:
-                continue
-            if ns.network.template.access not in ["direct", "indirect"]:
-                # skip anything that's not an access network
-                continue
-            for cn in ns.network.controllernetworks.all():
-                if cn.net_id:
-                    net = {"name": ns.network.name, "net_id": cn.net_id}
-                    nets.append(net)
-    return nets
-
-def get_vtn_dependencies_nets(self):
-    provider_nets = []
-    for tenant in self.subscribed_tenants.all():
-        if tenant.provider_service:
-            for net in tenant.provider_service.get_vtn_nets():
-                if not net in provider_nets:
-                    net["bidirectional"] = tenant.connect_method!="private-unidirectional"
-                    provider_nets.append(net)
-    return provider_nets
-
-def get_vtn_dependencies_ids(self):
-    return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
-
-def get_vtn_dependencies_names(self):
-    return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_dependencies_nets()]
-
-def get_vtn_src_ids(self):
-    return [x["net_id"] for x in self.get_vtn_src_nets()]
-
-def get_vtn_src_names(self):
-    return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_src_nets()]
-
-def get_composable_networks(self):
-    SUPPORTED_VTN_SERVCOMP_KINDS = ['VSG','PRIVATE']
-
-    nets = []
-    for slice in self.slices.all():
-        for net in slice.networks.all():
-            if (net.template.vtn_kind not in SUPPORTED_VTN_SERVCOMP_KINDS) or (net.owner != slice):
-                continue
-
-            if not net.controllernetworks.exists():
-                continue
-            nets.append(net)
-    return nets
-
-
-
diff --git a/xos/core/models/attic/serviceinstance_model.py b/xos/core/models/attic/serviceinstance_model.py
deleted file mode 100644
index f0006c1..0000000
--- a/xos/core/models/attic/serviceinstance_model.py
+++ /dev/null
@@ -1,59 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def __init__(self, *args, **kwargs):
-    super(ServiceInstance, self).__init__(*args, **kwargs)
-
-@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, none_okay=False):
-    if not none_okay and (self.service_specific_id is None):
-        raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
-                              "service_specific_id": "cannot be none"})
-
-    if self.service_specific_id:
-        conflicts = self.__class__.objects.filter(
-            service_specific_id=self.service_specific_id)
-        if self.pk:
-            conflicts = conflicts.exclude(pk=self.pk)
-        if conflicts:
-            raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
-                                  "service_specific_id": "duplicate key"})
-
-def get_subscribed_tenants(self, tenant_class):
-    """ Return all ServiceInstances of class tenant_class that have a link to this ServiceInstance """
-    results=[]
-    # TODO: Make query more efficient
-    for si in tenant_class.objects.all():
-        for link in si.subscribed_links.all():
-            if link.provider_service_instance == self:
-                results.append(si)
-    return results
-
-def get_newest_subscribed_tenant(self, kind):
-    st = list(self.get_subscribed_tenants(kind))
-    if not st:
-        return None
-    return sorted(st, key=attrgetter('id'))[0]
-
diff --git a/xos/core/models/attic/serviceinstancelink_model.py b/xos/core/models/attic/serviceinstancelink_model.py
deleted file mode 100644
index dd0c2a5..0000000
--- a/xos/core/models/attic/serviceinstancelink_model.py
+++ /dev/null
@@ -1,47 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def __xos_save_base(self, *args, **kwargs):
-    subCount = sum([1 for e in [self.subscriber_service, self.subscriber_service_instance, self.subscriber_network] if e is not None])
-    if (subCount > 1):
-        raise XOSConflictingField(
-            "Only one of subscriber_service, subscriber_service_instance, subscriber_network should be set")
-
-    try:
-        existing_instance = ServiceInstanceLink.objects.get(
-            provider_service_instance=self.provider_service_instance,
-            subscriber_service_instance=self.subscriber_service_instance,
-            subscriber_service=self.subscriber_service,
-            subscriber_network=self.subscriber_network
-        )
-
-        if (not self.pk and existing_instance) or (self.pk and self.pk != existing_instance.pk):
-            raise XOSValidationError("A ServiceInstanceLink with attributes 'provider_service_instance=%s, subscriber_service_instance=%s, subscriber_service=%s, subscriber_network=%s' already exists"
-                                     % (self.provider_service_instance, self.subscriber_service_instance, self.subscriber_service, self.subscriber_network))
-    except self.DoesNotExist:
-        # NOTE this is correct, no duplicated links
-        pass
-
-
-def delete(self, *args, **kwargs):
-    provider_service_instance = self.provider_service_instance
-    super(ServiceInstanceLink, self).delete(*args, **kwargs)
-
-    # This should be handled by a model_policy, but we don't currently have a
-    # model policy for core objects, so handle it during the save method.
-    if provider_service_instance and (not provider_service_instance.deleted):
-        provider_service_instance.link_deleted_count += 1
-        provider_service_instance.save(always_update_timestamp=True, update_fields=["updated", "link_deleted_count"])
diff --git a/xos/core/models/attic/slice_model.py b/xos/core/models/attic/slice_model.py
deleted file mode 100644
index c057f2a..0000000
--- a/xos/core/models/attic/slice_model.py
+++ /dev/null
@@ -1,43 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-NETWORK_CHOICES = ((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))
-
-@property
-def slicename(self):
-    return "%s_%s" % (self.site.login_base, self.name)
-
-def __xos_save_base(self, *args, **kwds):
-    # set creator on first save
-    if not self.creator and hasattr(self, 'caller'):
-        self.creator = self.caller
-
-    # only admins change a slice's creator
-    if 'creator' in self.changed_fields and \
-        (not hasattr(self, 'caller') or not self.caller.is_admin):
-
-        if (self._initial["creator"]==None) and (self.creator==getattr(self,"caller",None)):
-            # it's okay if the creator is being set by the caller to
-            # himeself on a new slice object.
-            pass
-        else:
-            raise PermissionDenied("Insufficient privileges to change slice creator",
-                                   {'creator': "Insufficient privileges to change slice creator"})
-    
-    if self.network=="Private Only":
-        # "Private Only" was the default from the old Tenant View
-        self.network=None
-    self.enforce_choices(self.network, self.NETWORK_CHOICES)
diff --git a/xos/core/models/attic/tenant_model.py b/xos/core/models/attic/tenant_model.py
deleted file mode 100644
index a01233d..0000000
--- a/xos/core/models/attic/tenant_model.py
+++ /dev/null
@@ -1,62 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-KIND="generic"
-
-def __init__(self, *args, **kwargs):
-    # for subclasses, set the default kind appropriately
-    self._meta.get_field("kind").default = self.KIND
-    super(Tenant, self).__init__(*args, **kwargs)
-
-@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:
-        if self.service_specific_id is None:
-            raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
-                                  "service_specific_id": "cannot be none"})
-
-        conflicts = self.__class__.objects.filter(
-            service_specific_id=self.service_specific_id)
-        if conflicts:
-            raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
-                                  "service_specific_id": "duplicate key"})
-
-def __xos_save_base(self, *args, **kwargs):
-    subCount = sum([1 for e in [self.subscriber_service, self.subscriber_tenant,
-                                self.subscriber_user, self.subscriber_root] if e is not None])
-    if (subCount > 1):
-        raise XOSConflictingField(
-            "Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
-
-
-def get_subscribed_tenants(self, tenant_class):
-    ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
-    return tenant_class.objects.filter(id__in=ids)
-
-def get_newest_subscribed_tenant(self, kind):
-    st = list(self.get_subscribed_tenants(kind))
-    if not st:
-        return None
-    return sorted(st, key=attrgetter('id'))[0]
-
diff --git a/xos/core/models/attic/tenantroot_model.py b/xos/core/models/attic/tenantroot_model.py
deleted file mode 100644
index 388fac8..0000000
--- a/xos/core/models/attic/tenantroot_model.py
+++ /dev/null
@@ -1,49 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-KIND="generic"
-
-def __init__(self, *args, **kwargs):
-    # for subclasses, set the default kind appropriately
-    self._meta.get_field("kind").default = self.KIND
-    super(TenantRoot, self).__init__(*args, **kwargs)
-
-def get_subscribed_tenants(self, tenant_class):
-    ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
-    return tenant_class.objects.filter(id__in=ids)
-
-def get_newest_subscribed_tenant(self, kind):
-    st = list(self.get_subscribed_tenants(kind))
-    if not st:
-        return None
-    return sorted(st, key=attrgetter('id'))[0]
-
-# helper function to be used in subclasses that want to ensure
-# service_specific_id is unique
-def validate_unique_service_specific_id(self, none_okay=False):
-    if not none_okay and (self.service_specific_id is None):
-        raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
-                              "service_specific_id": "cannot be none"})
-
-    if self.service_specific_id:
-        conflicts = self.__class__.objects.filter(
-            service_specific_id=self.service_specific_id)
-        if self.pk:
-            conflicts = conflicts.exclude(pk=self.pk)
-        if conflicts:
-            raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
-                                  "service_specific_id": "duplicate key"})
-
diff --git a/xos/core/models/attic/tenantwithcontainer_model.py b/xos/core/models/attic/tenantwithcontainer_model.py
deleted file mode 100644
index cebc461..0000000
--- a/xos/core/models/attic/tenantwithcontainer_model.py
+++ /dev/null
@@ -1,156 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def __init__(self, *args, **kwargs):
-    super(TenantWithContainer, self).__init__(*args, **kwargs)
-
-    # vSG service relies on knowing when instance id has changed
-    self.orig_instance_id = self.get_attribute("instance_id")
-
-# vSG service relies on instance_id attribute
-def get_attribute(self, name, default=None):
-    if name=="instance_id":
-        if self.instance:
-            return self.instance.id
-        else:
-            return None
-    else:
-        return super(TenantWithContainer, self).get_attribute(name, default)
-
-# Services may wish to override the image() function to return different
-# images based on criteria in the tenant object. For example,
-#    if (self.has_feature_A):
-#        return Instance.object.get(name="image_with_feature_a")
-#    elif (self.has_feature_B):
-#        return Instance.object.get(name="image_with_feature_b")
-#    else:
-#        return super(MyTenantClass,self).image()
-
-@property
-def image(self):
-    from core.models import Image
-    # Implement the logic here to pick the image that should be used when
-    # instantiating the VM that will hold the container.
-
-    slice = self.provider_service.slices.all()
-    if not slice:
-        raise XOSProgrammingError("provider service has no slice")
-    slice = slice[0]
-
-    # If slice has default_image set then use it
-    if slice.default_image:
-        return slice.default_image
-
-    raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
-
-def save_instance(self, instance):
-    # Override this function to do custom pre-save or post-save processing,
-    # such as creating ports for containers.
-    instance.save()
-
-def pick_least_loaded_instance_in_slice(self, slices, image):
-    for slice in slices:
-        if slice.instances.all().count() > 0:
-            for instance in slice.instances.all():
-                if instance.image != image:
-                    continue
-                # Pick the first instance that has lesser than 5 tenants
-                if self.count_of_tenants_of_an_instance(instance) < 5:
-                    return instance
-    return None
-
-# TODO: Ideally the tenant count for an instance should be maintained using a
-# many-to-one relationship attribute, however this model being proxy, it does
-# not permit any new attributes to be defined. Find if any better solutions
-def count_of_tenants_of_an_instance(self, instance):
-    tenant_count = 0
-    for tenant in self.__class__.objects.all():
-        if tenant.get_attribute("instance_id", None) == instance.id:
-            tenant_count += 1
-    return tenant_count
-
-def manage_container(self):
-    from core.models import Instance, Flavor
-
-    if self.deleted:
-        return
-
-    if (self.instance is not None) and (self.instance.image != self.image):
-        self.instance.delete()
-        self.instance = None
-
-    if self.instance is None:
-        if not self.provider_service.slices.count():
-            raise XOSConfigurationError("The service has no slices")
-
-        new_instance_created = False
-        instance = None
-        if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
-            # Find if any existing instances can be used for this tenant
-            slices = self.provider_service.slices.all()
-            instance = self.pick_least_loaded_instance_in_slice(slices, self.image)
-
-        if not instance:
-            slice = self.provider_service.slices.all()[0]
-
-            flavor = slice.default_flavor
-            if not flavor:
-                flavors = Flavor.objects.filter(name="m1.small")
-                if not flavors:
-                    raise XOSConfigurationError("No m1.small flavor")
-                flavor = flavors[0]
-
-            if slice.default_isolation == "container_vm":
-                (node, parent) = ContainerVmScheduler(slice).pick()
-            else:
-                (node, parent) = LeastLoadedNodeScheduler(slice).pick()
-
-            instance = Instance(slice=slice,
-                                node=node,
-                                image=self.image,
-                                creator=self.creator,
-                                deployment=node.site_deployment.deployment,
-                                flavor=flavor,
-                                isolation=slice.default_isolation,
-                                parent=parent)
-            self.save_instance(instance)
-            new_instance_created = True
-
-        try:
-            self.instance = instance
-            super(TenantWithContainer, self).save()
-        except:
-            if new_instance_created:
-                instance.delete()
-            raise
-
-def cleanup_container(self):
-    if self.instance:
-        if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
-            # Delete the instance only if this is last tenant in that
-            # instance
-            tenant_count = self.count_of_tenants_of_an_instance(
-                self.instance)
-            if tenant_count == 0:
-                self.instance.delete()
-        else:
-            self.instance.delete()
-        self.instance = None
-
-def __xos_save_base(self, *args, **kwargs):
-    if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
-        self.creator = self.caller
-
diff --git a/xos/core/models/attic/xosbase_model.py b/xos/core/models/attic/xosbase_model.py
deleted file mode 100644
index 5fea7c8..0000000
--- a/xos/core/models/attic/xosbase_model.py
+++ /dev/null
@@ -1,193 +0,0 @@
-
-# Copyright 2017-present Open Networking Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-objects = XOSBaseManager()
-deleted_objects = XOSBaseDeletionManager()
-
-class Meta:
-    # Changing abstract to False would require the managers of subclasses of
-    # XOSBase to be customized individually.
-    abstract = True
-    app_label = "core"
-
-def __init__(self, *args, **kwargs):
-    super(XOSBase, self).__init__(*args, **kwargs)
-    self._initial = self._dict # for PlModelMixIn
-    self.silent = False
-
-def get_controller(self):
-    return self.controller
-
-def delete(self, *args, **kwds):
-    # so we have something to give the observer
-    purge = kwds.get('purge',False)
-    if purge:
-        del kwds['purge']
-    silent = kwds.get('silent',False)
-    if silent:
-        del kwds['silent']
-    try:
-        purge = purge or observer_disabled
-    except NameError:
-        pass
-
-    if (purge):
-        pk = self.pk
-        super(XOSBase, self).delete(*args, **kwds)
-        self.push_redis_event(deleted=True, pk=pk)
-    else:
-        if (not self.write_protect ):
-            self.deleted = True
-            self.enacted=None
-            self.policed=None
-            self.save(update_fields=['enacted','deleted','policed'], silent=silent)
-
-            collector = XOSCollector(using=router.db_for_write(self.__class__, instance=self))
-            collector.collect([self])
-            with transaction.atomic():
-                for (k, models) in collector.data.items():
-                    for model in models:
-                        if not hasattr(model, "deleted"):
-                            # Automatically generated through relations from ManyToMany fields do not have soft-delete
-                            # capability.
-                            continue
-                        if model.deleted:
-                            # in case it's already been deleted, don't delete again
-                            continue
-                        model.deleted = True
-                        model.enacted=None
-                        model.policed=None
-                        model.save(update_fields=['enacted','deleted','policed'], silent=silent)
-
-def verify_live_keys(self, update_fields):
-    """ Check the fields to be updated, if they contain foreign keys, that the foreign keys only point
-        to live objects in the database.
-
-        This is to catch races between model policies where an object is being deleted while a model policy is
-        still operating on it.
-    """
-
-    if getattr(self, "deleted", False):
-        # If this model is already deleted, then no need to check anything. We only need to check for live
-        # models that point to dead models. If a dead model points to other dead models, then we could
-        # be updating something else in the dead model (backend_status, etc)
-        return
-
-    for field in self._meta.fields:
-        try:
-            f = getattr(self, field.name)
-        except Exception, e:
-            # Exception django.db.models.fields.related.RelatedObjectDoesNotExist
-            # is thrown by django when you're creating an object that has a base and the base doesn't exist yet
-            continue
-
-        if f is None:
-            # If field hold a null value, we don't care
-            continue
-
-        ftype = field.get_internal_type()
-        if (ftype != "ForeignKey"):
-            # If field isn't a foreign key, we don't care
-            continue
-
-        if (update_fields) and (field.name not in update_fields):
-            # If update_fields is nonempty, and field is not to be updated, we don't care.
-            continue
-
-        if getattr(f, "deleted", False):
-            raise Exception("Attempt to save object with deleted foreign key reference")
-
-def save(self, *args, **kwargs):
-
-    # let the user specify silence as either a kwarg or an instance varible
-    silent = self.silent
-    if "silent" in kwargs:
-        silent=silent or kwargs.pop("silent")
-
-    caller_kind = "unknown"
-
-    if ('synchronizer' in threading.current_thread().name):
-        caller_kind = "synchronizer"
-
-    if "caller_kind" in kwargs:
-        caller_kind = kwargs.pop("caller_kind")
-
-    always_update_timestamp = False
-    if "always_update_timestamp" in kwargs:
-        always_update_timestamp = always_update_timestamp or kwargs.pop("always_update_timestamp")
-
-    # SMBAKER: if an object is trying to delete itself, or if the observer
-    # is updating an object's backend_* fields, then let it slip past the
-    # composite key check.
-    ignore_composite_key_check=False
-    if "update_fields" in kwargs:
-        ignore_composite_key_check=True
-        for field in kwargs["update_fields"]:
-            if not (field in ["backend_register", "backend_status", "deleted", "enacted", "updated"]):
-                ignore_composite_key_check=False
-
-    if (caller_kind!="synchronizer") or always_update_timestamp:
-        self.updated = timezone.now()
-    else:
-        # We're not auto-setting timestamp, but let's check to make sure that the caller hasn't tried to set our
-        # timestamp backward...
-        if (self.updated != self._initial["updated"]) and ((not kwargs.get("update_fields")) or ("updated" in kwargs.get("update_fields"))):
-            log.info("Synchronizer tried to change `updated` timestamp on model %s from %s to %s. Ignored." % (self, self._initial["updated"], self.updated))
-            self.updated = self._initial["updated"]
-
-    with transaction.atomic():
-        self.verify_live_keys(update_fields = kwargs.get("update_fields"))
-        super(XOSBase, self).save(*args, **kwargs)
-
-    self.push_redis_event()
-
-    self._initial = self._dict
-
-def tologdict(self):
-    try:
-        d = {'model_name':self.__class__.__name__, 'pk': self.pk}
-    except:
-        d = {}
-
-    return d
-
-# for the old django admin UI
-def __unicode__(self):
-    if hasattr(self, "name") and self.name:
-        return u'%s' % self.name
-    elif hasattr(self, "id") and self.id:
-        if hasattr(self, "leaf_model_name") and self.leaf_model_name:
-            return u'%s-%s' % (self.leaf_model_name, self.id)
-        else:
-            return u'%s-%s' % (self.__class__.__name__, self.id)
-    else:
-        return u'%s-unsaved' % self.__class__.__name__
-
-def get_content_type_key(self):
-    ct = ContentType.objects.get_for_model(self.__class__)
-    return "%s.%s" % (ct.app_label, ct.model)
-
-@staticmethod
-def get_content_type_from_key(key):
-    (app_name, model_name) = key.split(".")
-    return ContentType.objects.get_by_natural_key(app_name, model_name)
-
-@staticmethod
-def get_content_object(content_type, object_id):
-    ct = XOSBase.get_content_type_from_key(content_type)
-    cls = ct.model_class()
-    return cls.objects.get(id=object_id)
-
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controller.py
similarity index 81%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controller.py
index 4cf8491..e0d20e7 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controller.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,5 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controller_decl import *
 
-from service_header import *
+class Controller(Controller_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllerimages.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllerimages.py
index 4cf8491..fdec3d4 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllerimages.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controllerimages_decl import *
 
-from service_header import *
+class ControllerImages(ControllerImages_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/controllernetwork.py b/xos/core/models/controllernetwork.py
new file mode 100644
index 0000000..6259bdb
--- /dev/null
+++ b/xos/core/models/controllernetwork.py
@@ -0,0 +1,30 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from controllernetwork_decl import *
+
+class ControllerNetwork(ControllerNetwork_decl):
+    class Meta:
+        proxy = True
+
+    def tologdict(self):
+        d=super(ControllerNetwork,self).tologdict()
+        try:
+            d['network_name']=self.network.name
+            d['controller_name']=self.controller.name
+        except:
+            pass
+        return d
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllerrole.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllerrole.py
index 4cf8491..91746a1 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllerrole.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controllerrole_decl import *
 
-from service_header import *
+class ControllerRole(ControllerRole_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllersite.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllersite.py
index 4cf8491..5186c05 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllersite.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controllersite_decl import *
 
-from service_header import *
+class ControllerSite(ControllerSite_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllersiteprivilege.py
similarity index 77%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllersiteprivilege.py
index 4cf8491..915aa37 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllersiteprivilege.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controllersiteprivilege_decl import *
 
-from service_header import *
+class ControllerSitePrivilege(ControllerSitePrivilege_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/controllernetwork_model.py b/xos/core/models/controllerslice.py
similarity index 60%
rename from xos/core/models/attic/controllernetwork_model.py
rename to xos/core/models/controllerslice.py
index 1bd6a61..8b468af 100644
--- a/xos/core/models/attic/controllernetwork_model.py
+++ b/xos/core/models/controllerslice.py
@@ -13,12 +13,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controllerslice_decl import *
 
-def tologdict(self):
-    d=super(ControllerNetwork,self).tologdict()
-    try:
-        d['network_name']=self.network.name
-        d['controller_name']=self.controller.name
-    except:
-        pass
-    return d
+class ControllerSlice(ControllerSlice_decl):
+    class Meta:
+        proxy = True
+
+    def tologdict(self):
+        d=super(ControllerSlice,self).tologdict()
+        try:
+            d['slice_name']=self.slice.name
+            d['controller_name']=self.controller.name
+        except:
+            pass
+        return d
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controllersliceprivilege.py
similarity index 76%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controllersliceprivilege.py
index 4cf8491..e9963ff 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controllersliceprivilege.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controllersliceprivilege_decl import *
 
-from service_header import *
+class ControllerSlicePrivilege(ControllerSlicePrivilege_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/controlleruser.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/controlleruser.py
index 4cf8491..cdd152d 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/controlleruser.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from controlleruser_decl import *
 
-from service_header import *
+class ControllerUser(ControllerUser_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 4cef3bd..f74b73d 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -1,10 +1,13 @@
 option app_label = "core";
+option legacy="True";
 
 // use thi policy to allow access to admins only
 policy admin_policy < ctx.user.is_admin >
 
 message XOSBase {
      option skip_init = True;
+     option custom_header = "xosbase_header";
+     option abstract = True;
 
      required string created = 1 [content_type = "date", auto_now_add = True];
      required string updated = 2 [default = "now()", content_type = "date"];
@@ -189,24 +192,16 @@
      optional string segmentation_id = 10 [db_index = False, max_length = 32, null = True, blank = True];
 }
 
-
 message ControllerRole (XOSBase) {
      required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
 }
 
-
 message ControllerSite (XOSBase) {
      required manytoone site->Site:controllersite = 1 [db_index = True, null = False, blank = False, unique_with="controller", tosca_key = True];
      optional manytoone controller->Controller:controllersite = 2 [db_index = True, null = True, blank = True, tosca_key = True];
      optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = True];
 }
 
-message ControllerPrivilege (XOSBase) {
-     required manytoone controller->Controller:controllerprivileges = 1 [db_index = True, null = False, blank = False];
-     required manytoone privilege->Privilege:controllerprivileges = 2 [db_index = True, null = False, blank = False];
-     optional string role_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone id", null = True, db_index = True];
-}
-
 message ControllerSitePrivilege (XOSBase) {
      required manytoone controller->Controller:controllersiteprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "site_privilege"];
      required manytoone site_privilege->SitePrivilege:controllersiteprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role_id"];
@@ -223,7 +218,6 @@
      optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = False];
 }
 
-
 message ControllerSlicePrivilege (XOSBase) {
      required manytoone controller->Controller:controllersliceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "slice_privilege"];
      required manytoone slice_privilege->SlicePrivilege:controllersliceprivileges = 2 [db_index = True, null = False, blank = False];
@@ -252,18 +246,6 @@
 }
 
 
-message DeploymentPrivilege (XOSBase) {
-     required manytoone user->User:deploymentprivileges = 1 [db_index = True, null = False, blank = False, unique_with="deployment"];
-     required manytoone deployment->Deployment:deploymentprivileges = 2 [db_index = True, null = False, blank = False, unique_with="role"];
-     required manytoone role->DeploymentRole:deploymentprivileges = 3 [db_index = True, null = False, blank = False];
-}
-
-
-message DeploymentRole (XOSBase) {
-     required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False, tosca_key=True];
-}
-
-
 message Diag (XOSBase) {
     option gui_hidden = True;
     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the synchronizer", null = False, db_index = False];
@@ -339,8 +321,6 @@
      required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
      required bool autoconnect = 17 [help_text = "This network can be autoconnected to the slice that owns it", default = True, null = False, db_index = False, blank = True];
      required manytomany permitted_slices->Slice/Network_permitted_slices:availableNetworks = 18 [db_index = False, null = False, blank = True];
-     required manytomany slices->Slice/NetworkSlice:networks = 19 [db_index = False, null = False, blank = True];
-     required manytomany instances->Instance/Port:networks = 20 [db_index = False, null = False, blank = True];
 }
 
 
@@ -453,19 +433,6 @@
 }
 
 
-message ServicePrivilege (XOSBase) {
-     required manytoone user->User:serviceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "service"];
-     required manytoone service->Service:serviceprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role"];
-     required manytoone role->ServiceRole:serviceprivileges = 3 [db_index = True, null = False, blank = False];
-}
-
-
-message ServiceRole (XOSBase) {
-     required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
-}
-
-
-
 message Site::site_policy (XOSBase) {
      required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name for this Site", null = False, db_index = False];
      optional string site_url = 2 [max_length = 512, content_type = "url", blank = True, help_text = "Site's Home URL Page", null = True, db_index = False];
@@ -477,7 +444,6 @@
      required string login_base = 8 [max_length = 50, content_type = "stripped", blank = False, help_text = "Prefix for Slices associated with this Site", null = False, db_index = False];
      required bool is_public = 9 [help_text = "Indicates the visibility of this site to other members", default = True, null = False, db_index = False, blank = True];
      required string abbreviated_name = 10 [db_index = False, max_length = 80, null = False, content_type = "stripped", blank = False];
-     required manytomany deployments->Deployment/SiteDeployment:sites = 11 [help_text = "Select which sites are allowed to host nodes in this deployment", null = False, db_index = False, blank = True];
 }
 
 
@@ -488,7 +454,6 @@
      optional string availability_zone = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack availability zone", null = True, db_index = False];
 }
 
-
 message SitePrivilege (XOSBase) {
      required manytoone user->User:siteprivileges = 1 [db_index = True, null = False, blank = False];
      required manytoone site->Site:siteprivileges = 2 [db_index = True, null = False, blank = False, tosca_key=True];
@@ -527,7 +492,6 @@
      required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
 }
 
-
 message SlicePrivilege (XOSBase) {
      required manytoone user->User:sliceprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
      required manytoone slice->Slice:sliceprivileges = 2 [db_index = True, null = False, blank = False, unique_with = "role"];
@@ -590,9 +554,9 @@
      optional string node_label = 5 [max_length = 30, content_type = "stripped", blank = True, help_text = "Node constraint", null = True, db_index = False];
 }
 
-message XOS (XOSBase) {
-     option singular="XOS";
-     option plural="XOSes";
+message XOSCore (XOSBase) {
+     option singular="XOSCore";
+     option plural="XOSCores";
      required string name = 1 [default = "XOS", max_length = 200, content_type = "stripped", blank = False, help_text = "Name of XOS", null = False, db_index = False];
 }
 
diff --git a/xos/core/models/deployment.py b/xos/core/models/deployment.py
new file mode 100644
index 0000000..ffc46e5
--- /dev/null
+++ b/xos/core/models/deployment.py
@@ -0,0 +1,54 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from deployment_decl import *
+from core.acl import AccessControlList
+
+class Deployment(Deployment_decl):
+    class Meta:
+        proxy = True
+
+    def get_acl(self):
+        return AccessControlList(self.accessControl)
+
+    def test_acl(self, slice=None, user=None):
+        potential_users=[]
+
+        if user:
+            potential_users.append(user)
+
+        if slice:
+            potential_users.append(slice.creator)
+            for priv in slice.sliceprivileges.all():
+                if priv.user not in potential_users:
+                    potential_users.append(priv.user)
+
+        acl = self.get_acl()
+        for user in potential_users:
+            if acl.test(user) == "allow":
+                return True
+
+        return False
+
+    @staticmethod
+    def select_by_acl(user):
+        ids = []
+        for deployment in Deployment.objects.all():
+            acl = deployment.get_acl()
+            if acl.test(user) == "allow":
+                ids.append(deployment.id)
+
+        return Deployment.objects.filter(id__in=ids)
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/diag.py
similarity index 83%
rename from xos/core/models/attic/serviceinstance_top.py
rename to xos/core/models/diag.py
index 4cf8491..1af74ff 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/diag.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from diag_decl import *
 
-from service_header import *
+class Diag(Diag_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/flavor.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/flavor.py
index 4cf8491..e2156b2 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/flavor.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from flavor_decl import *
 
-from service_header import *
+class Flavor(Flavor_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/header.py b/xos/core/models/header.py
deleted file mode 120000
index 721b5c0..0000000
--- a/xos/core/models/header.py
+++ /dev/null
@@ -1 +0,0 @@
-attic/header.py
\ No newline at end of file
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/image.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/image.py
index 4cf8491..7aa139a 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/image.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from image_decl import *
 
-from service_header import *
+class Image(Image_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/imagedeployments.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/imagedeployments.py
index 4cf8491..3db6a47 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/imagedeployments.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from imagedeployments_decl import *
 
-from service_header import *
+class ImageDeployments(ImageDeployments_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/instance.py b/xos/core/models/instance.py
new file mode 100644
index 0000000..45215bc
--- /dev/null
+++ b/xos/core/models/instance.py
@@ -0,0 +1,108 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from instance_decl import *
+
+class Instance(Instance_decl):
+    class Meta:
+        proxy = True
+
+    def get_controller (self):
+        return self.node.site_deployment.controller
+
+    def tologdict(self):
+        d=super(Instance,self).tologdict()
+        try:
+            d['slice_name']=self.slice.name
+            d['controller_name']=self.get_controller().name
+        except:
+            pass
+        return d
+
+    def save(self, *args, **kwargs):
+        if not self.name:
+            self.name = self.slice.name
+        if not self.creator and hasattr(self, 'caller'):
+            self.creator = self.caller
+
+        super(Instance, self).save(*args, **kwargs)
+
+    def all_ips(self):
+        ips={}
+        for ns in self.ports.all():
+           if ns.ip:
+               ips[ns.network.name] = ns.ip
+        return ips
+
+    def all_ips_string(self):
+        result = []
+        ips = self.all_ips()
+        for key in sorted(ips.keys()):
+            #result.append("%s = %s" % (key, ips[key]))
+            result.append(ips[key])
+        return ", ".join(result)
+    all_ips_string.short_description = "addresses"
+
+    def get_public_ip(self):
+        for ns in self.ports.all():
+            if (ns.ip) and (ns.network.template.visibility=="public") and (ns.network.template.translation=="none"):
+                return ns.ip
+        return None
+
+    # return an address on nat-net
+    def get_network_ip(self, pattern):
+        for ns in self.ports.all():
+            if pattern in ns.network.name.lower():
+                return ns.ip
+        return None
+
+    # return an address that the synchronizer can use to SSH to the instance
+    def get_ssh_ip(self):
+        # first look specifically for a management_local network
+        for ns in self.ports.all():
+            if ns.network.template and ns.network.template.vtn_kind=="MANAGEMENT_LOCAL":
+                return ns.ip
+
+        # for compatibility, now look for any management network
+        management=self.get_network_ip("management")
+        if management:
+            return management
+
+        # if all else fails, look for nat-net (for OpenCloud?)
+        return self.get_network_ip("nat")
+
+    def get_ssh_command(self):
+        if (not self.instance_id) or (not self.node) or (not self.instance_name):
+            return None
+        else:
+            return 'ssh -o "ProxyCommand ssh -q %s@%s" ubuntu@%s' % (self.instance_id, self.node.name, self.instance_name)
+
+    def get_public_keys(self):
+        from core.models.sliceprivilege import Privilege
+        slice_privileges = Privilege.objects.filter(object_id=self.slice.id, object_type='Slice', accessor_type='User')
+        slice_users = [User.objects.get(pk = priv.accessor_id) for priv in slice_privileges]
+        pubkeys = set([u.public_key for u in slice_users if u.public_key])
+
+        if self.creator.public_key:
+            pubkeys.add(self.creator.public_key)
+
+        if self.slice.creator.public_key:
+            pubkeys.add(self.slice.creator.public_key)
+
+        if self.slice.service and self.slice.service.public_key:
+            pubkeys.add(self.slice.service.public_key)
+
+        return pubkeys
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/interfacetype.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/interfacetype.py
index 4cf8491..264a981 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/interfacetype.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from interfacetype_decl import *
 
-from service_header import *
+class InterfaceType(InterfaceType_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/network.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/network.py
index 4cf8491..466b30f 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/network.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from network_decl import *
 
-from service_header import *
+class Network(Network_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networkparameter.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networkparameter.py
index 4cf8491..b8d4b8c 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networkparameter.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from networkparameter_decl import *
 
-from service_header import *
+class NetworkParameter(NetworkParameter_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networkparametertype.py
similarity index 78%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networkparametertype.py
index 4cf8491..f33aa28 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networkparametertype.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from networkparametertype_decl import *
 
-from service_header import *
+class NetworkParameterType(NetworkParameterType_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networkslice.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networkslice.py
index 4cf8491..9a1b119 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networkslice.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from networkslice_decl import *
 
-from service_header import *
+class NetworkSlice(NetworkSlice_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/networktemplate.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/networktemplate.py
index 4cf8491..ee8f5bd 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/networktemplate.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from networktemplate_decl import *
 
-from service_header import *
+class NetworkTemplate(NetworkTemplate_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/node.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/node.py
index 4cf8491..aea3bac 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/node.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from node_decl import *
 
-from service_header import *
+class Node(Node_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/nodelabel.py b/xos/core/models/nodelabel.py
new file mode 100644
index 0000000..68e5f5c
--- /dev/null
+++ b/xos/core/models/nodelabel.py
@@ -0,0 +1,38 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from nodelabel_decl import *
+
+class NodeLabel(NodeLabel_decl):
+    class Meta:
+        proxy = True
+
+    def save(self, *args, **kwargs):
+        """ Hack to allow the creation of NodeLabel objects from outside core
+            until the ORM is extended with support for ManyToMany relations.
+        """
+
+        if self.name and '###' in self.name:
+            from core.models import Node
+
+            self.name, node_id_str = self.name.split('###')
+            node_ids = map(int, node_id_str.split(','))
+
+            for node_id in node_ids:
+                node = Node.get(node_id)
+                self.node.add(node)
+
+        super(NodeLabel, self).save(*args, **kwargs)
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/port.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/port.py
index 4cf8491..bbee8a7 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/port.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from port_decl import *
 
-from service_header import *
+class Port(Port_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/privilege.py
similarity index 81%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/privilege.py
index 4cf8491..1485ec8 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/privilege.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from privilege_decl import *
 
-from service_header import *
+class Privilege(Privilege_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/role.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/role.py
index 4cf8491..447df8e 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/role.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from role_decl import *
 
-from service_header import *
+class Role(Role_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
new file mode 100644
index 0000000..b03ac70
--- /dev/null
+++ b/xos/core/models/service.py
@@ -0,0 +1,308 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from service_decl import *
+
+class Scheduler(object):
+    # XOS Scheduler Abstract Base Class
+    # Used to implement schedulers that pick which node to put instances on
+
+    def __init__(self, slice):
+        self.slice = slice
+
+    def pick(self):
+        # this method should return a tuple (node, parent)
+        #    node is the node to instantiate on
+        #    parent is for container_vm instances only, and is the VM that will
+        #      hold the container
+
+        raise Exception("Abstract Base")
+
+
+class LeastLoadedNodeScheduler(Scheduler):
+    # This scheduler always return the node with the fewest number of
+    # instances.
+
+    def __init__(self, slice, label=None):
+        super(LeastLoadedNodeScheduler, self).__init__(slice)
+        self.label = label
+
+    def pick(self):
+        from core.models import Node
+
+        # start with all nodes
+        nodes = Node.objects.all()
+
+        # if a label is set, then filter by label
+        if self.label:
+            nodes = nodes.filter(nodelabels__name=self.label)
+
+        # if slice.default_node is set, then filter by default_node
+        if self.slice.default_node:
+            nodes = nodes.filter(name = self.slice.default_node)
+
+        # convert to list
+        nodes = list(nodes)
+
+        # sort so that we pick the least-loaded node
+        nodes = sorted(nodes, key=lambda node: node.instances.all().count())
+
+        if not nodes:
+            raise Exception(
+                "LeastLoadedNodeScheduler: No suitable nodes to pick from")
+
+        # 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())
+        return [nodes[0], None]
+
+
+class ContainerVmScheduler(Scheduler):
+    # This scheduler picks a VM in the slice with the fewest containers inside
+    # of it. If no VMs are suitable, then it creates a VM.
+
+    MAX_VM_PER_CONTAINER = 10
+
+    def __init__(self, slice):
+        super(ContainerVmScheduler, self).__init__(slice)
+
+    @property
+    def image(self):
+        from core.models import Image
+
+        # If slice has default_image set then use it
+        if self.slice.default_image:
+            return self.slice.default_image
+
+        raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
+
+    def make_new_instance(self):
+        from core.models import Instance, Flavor
+
+        flavors = Flavor.objects.filter(name="m1.small")
+        if not flavors:
+            raise XOSConfigurationError("No m1.small flavor")
+
+        (node, parent) = LeastLoadedNodeScheduler(self.slice).pick()
+
+        instance = Instance(slice=self.slice,
+                            node=node,
+                            image=self.image,
+                            creator=self.slice.creator,
+                            deployment=node.site_deployment.deployment,
+                            flavor=flavors[0],
+                            isolation="vm",
+                            parent=parent)
+        instance.save()
+        # We rely on a special naming convention to identify the VMs that will
+        # hole containers.
+        instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
+        instance.save()
+        return instance
+
+    def pick(self):
+        from core.models import Instance, Flavor
+
+        for vm in self.slice.instances.filter(isolation="vm"):
+            avail_vms = []
+            if (vm.name.startswith("%s-outer-" % self.slice.name)):
+                container_count = Instance.objects.filter(parent=vm).count()
+                if (container_count < self.MAX_VM_PER_CONTAINER):
+                    avail_vms.append((vm, container_count))
+            # sort by least containers-per-vm
+            avail_vms = sorted(avail_vms, key=lambda x: x[1])
+            print "XXX", avail_vms
+            if avail_vms:
+                instance = avail_vms[0][0]
+                return (instance.node, instance)
+
+        instance = self.make_new_instance()
+        return (instance.node, instance)
+
+class Service(Service_decl):
+    class Meta:
+        proxy = True
+
+    KIND = "generic"
+
+    def __init__(self, *args, **kwargs):
+        # for subclasses, set the default kind appropriately
+        # TODO: rethink this -- remember the class variable bug
+        self._meta.get_field("kind").default = self.KIND
+        super(Service, self).__init__(*args, **kwargs)
+
+    @property
+    def serviceattribute_dict(self):
+        attrs = {}
+        for attr in self.serviceattributes.all():
+            attrs[attr.name] = attr.value
+        return attrs
+
+    def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
+        """
+             Get a list of nodes that can be used to scale up a slice.
+
+                slice - slice to scale up
+                max_per_node - maximum numbers of instances that 'slice' can have on a single node
+                exclusive_slices - list of slices that must have no nodes in common with 'slice'.
+        """
+
+        # late import to get around order-of-imports constraint in __init__.py
+        from core.models import Node, Instance
+
+        nodes = list(Node.objects.all())
+
+        conflicting_instances = Instance.objects.filter(
+            slice__in=exclusive_slices)
+        conflicting_nodes = Node.objects.filter(
+            instances__in=conflicting_instances)
+
+        nodes = [x for x in nodes if x not in conflicting_nodes]
+
+        # If max_per_node is set, then limit the number of instances this slice
+        # can have on a single node.
+        if max_per_node:
+            acceptable_nodes = []
+            for node in nodes:
+                existing_count = node.instances.filter(slice=slice).count()
+                if existing_count < max_per_node:
+                    acceptable_nodes.append(node)
+            nodes = acceptable_nodes
+
+        return nodes
+
+    def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
+        # Pick the best node to scale up a slice.
+
+        nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
+        nodes = sorted(nodes, key=lambda node: node.instances.all().count())
+        if not nodes:
+            return None
+        return nodes[0]
+
+    def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
+        # late import to get around order-of-imports constraint in __init__.py
+        from core.models import Instance
+
+        slices = [x for x in self.slices.all() if slice_hint in x.name]
+        for slice in slices:
+            while slice.instances.all().count() > scale:
+                s = slice.instances.all()[0]
+                # print "drop instance", s
+                s.delete()
+
+            while slice.instances.all().count() < scale:
+                node = self.pick_node(slice, max_per_node, exclusive_slices)
+                if not node:
+                    # no more available nodes
+                    break
+
+                image = slice.default_image
+                if not image:
+                    raise XOSConfigurationError(
+                        "No default_image for slice %s" % slice.name)
+
+                flavor = slice.default_flavor
+                if not flavor:
+                    raise XOSConfigurationError(
+                        "No default_flavor for slice %s" % slice.name)
+
+                s = Instance(slice=slice,
+                             node=node,
+                             creator=slice.creator,
+                             image=image,
+                             flavor=flavor,
+                             deployment=node.site_deployment.deployment)
+                s.save()
+
+                # print "add instance", s
+
+    def get_vtn_src_nets(self):
+        nets = []
+        for slice in self.slices.all():
+            for ns in slice.networkslices.all():
+                if not ns.network:
+                    continue
+    #                if ns.network.template.access in ["direct", "indirect"]:
+    #                    # skip access networks; we want to use the private network
+    #                    continue
+                if "management" in ns.network.name:
+                    # don't try to connect the management network to anything
+                    continue
+                if ns.network.name in ["wan_network", "lan_network"]:
+                    # we don't want to attach to the vCPE's lan or wan network
+                    # we only want to attach to its private network
+                    # TODO: fix hard-coding of network name
+                    continue
+                for cn in ns.network.controllernetworks.all():
+                    if cn.net_id:
+                        net = {"name": ns.network.name, "net_id": cn.net_id}
+                        nets.append(net)
+        return nets
+
+    def get_vtn_nets(self):
+        nets = []
+        for slice in self.slices.all():
+            for ns in slice.networkslices.all():
+                if not ns.network:
+                    continue
+                if ns.network.template.access not in ["direct", "indirect"]:
+                    # skip anything that's not an access network
+                    continue
+                for cn in ns.network.controllernetworks.all():
+                    if cn.net_id:
+                        net = {"name": ns.network.name, "net_id": cn.net_id}
+                        nets.append(net)
+        return nets
+
+    def get_vtn_dependencies_nets(self):
+        provider_nets = []
+        for tenant in self.subscribed_tenants.all():
+            if tenant.provider_service:
+                for net in tenant.provider_service.get_vtn_nets():
+                    if not net in provider_nets:
+                        net["bidirectional"] = tenant.connect_method!="private-unidirectional"
+                        provider_nets.append(net)
+        return provider_nets
+
+    def get_vtn_dependencies_ids(self):
+        return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
+
+    def get_vtn_dependencies_names(self):
+        return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_dependencies_nets()]
+
+    def get_vtn_src_ids(self):
+        return [x["net_id"] for x in self.get_vtn_src_nets()]
+
+    def get_vtn_src_names(self):
+        return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_src_nets()]
+
+    def get_composable_networks(self):
+        SUPPORTED_VTN_SERVCOMP_KINDS = ['VSG','PRIVATE']
+
+        nets = []
+        for slice in self.slices.all():
+            for net in slice.networks.all():
+                if (net.template.vtn_kind not in SUPPORTED_VTN_SERVCOMP_KINDS) or (net.owner != slice):
+                    continue
+
+                if not net.controllernetworks.exists():
+                    continue
+                nets.append(net)
+        return nets
+
+
+
diff --git a/xos/core/models/service_header.py b/xos/core/models/service_header.py
deleted file mode 120000
index a90002d..0000000
--- a/xos/core/models/service_header.py
+++ /dev/null
@@ -1 +0,0 @@
-attic/service_header.py
\ No newline at end of file
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/serviceattribute.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/serviceattribute.py
index 4cf8491..19b4550 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/serviceattribute.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from serviceattribute_decl import *
 
-from service_header import *
+class ServiceAttribute(ServiceAttribute_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/servicedependency.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/servicedependency.py
index 4cf8491..ae661b3 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/servicedependency.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from servicedependency_decl import *
 
-from service_header import *
+class ServiceDependency(ServiceDependency_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/servicegraphconstraint.py
similarity index 77%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/servicegraphconstraint.py
index 4cf8491..cc703a8 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/servicegraphconstraint.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from servicegraphconstraint_decl import *
 
-from service_header import *
+class ServiceGraphConstraint(ServiceGraphConstraint_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/serviceinstance.py b/xos/core/models/serviceinstance.py
new file mode 100644
index 0000000..9cc6dab
--- /dev/null
+++ b/xos/core/models/serviceinstance.py
@@ -0,0 +1,65 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from serviceinstance_decl import *
+
+class ServiceInstance(ServiceInstance_decl):
+    class Meta:
+        proxy = True
+
+    def __init__(self, *args, **kwargs):
+        super(ServiceInstance, self).__init__(*args, **kwargs)
+
+    @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, none_okay=False):
+        if not none_okay and (self.service_specific_id is None):
+            raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
+                                  "service_specific_id": "cannot be none"})
+
+        if self.service_specific_id:
+            conflicts = self.__class__.objects.filter(
+                service_specific_id=self.service_specific_id)
+            if self.pk:
+                conflicts = conflicts.exclude(pk=self.pk)
+            if conflicts:
+                raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
+                                      "service_specific_id": "duplicate key"})
+
+    def get_subscribed_tenants(self, tenant_class):
+        """ Return all ServiceInstances of class tenant_class that have a link to this ServiceInstance """
+        results=[]
+        # TODO: Make query more efficient
+        for si in tenant_class.objects.all():
+            for link in si.subscribed_links.all():
+                if link.provider_service_instance == self:
+                    results.append(si)
+        return results
+
+    def get_newest_subscribed_tenant(self, kind):
+        st = list(self.get_subscribed_tenants(kind))
+        if not st:
+            return None
+        return sorted(st, key=attrgetter('id'))[0]
+
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/serviceinstanceattribute.py
similarity index 76%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/serviceinstanceattribute.py
index 4cf8491..8dcaa81 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/serviceinstanceattribute.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from serviceinstanceattribute_decl import *
 
-from service_header import *
+class ServiceInstanceAttribute(ServiceInstanceAttribute_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/serviceinstancelink.py b/xos/core/models/serviceinstancelink.py
new file mode 100644
index 0000000..c2ce0c1
--- /dev/null
+++ b/xos/core/models/serviceinstancelink.py
@@ -0,0 +1,57 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from serviceinstancelink_decl import *
+
+class ServiceInstanceLink(ServiceInstanceLink_decl):
+    class Meta:
+        proxy = True
+
+    def save(self, *args, **kwargs):
+        subCount = sum([1 for e in [self.subscriber_service, self.subscriber_service_instance, self.subscriber_network] if e is not None])
+        if (subCount > 1):
+            raise XOSConflictingField(
+                "Only one of subscriber_service, subscriber_service_instance, subscriber_network should be set")
+
+        try:
+            existing_instance = ServiceInstanceLink.objects.get(
+                provider_service_instance=self.provider_service_instance,
+                subscriber_service_instance=self.subscriber_service_instance,
+                subscriber_service=self.subscriber_service,
+                subscriber_network=self.subscriber_network
+            )
+
+            if (not self.pk and existing_instance) or (self.pk and self.pk != existing_instance.pk):
+                raise XOSValidationError(
+                    "A ServiceInstanceLink with attributes 'provider_service_instance=%s, subscriber_service_instance=%s, subscriber_service=%s, subscriber_network=%s' already exists"
+                    % (self.provider_service_instance, self.subscriber_service_instance, self.subscriber_service,
+                       self.subscriber_network))
+        except self.DoesNotExist:
+            # NOTE this is correct, no duplicated links
+            pass
+
+        super(ServiceInstanceLink, self).save(*args, **kwargs)
+
+
+    def delete(self, *args, **kwargs):
+        provider_service_instance = self.provider_service_instance
+        super(ServiceInstanceLink, self).delete(*args, **kwargs)
+
+        # This should be handled by a model_policy, but we don't currently have a
+        # model policy for core objects, so handle it during the save method.
+        if provider_service_instance and (not provider_service_instance.deleted):
+            provider_service_instance.link_deleted_count += 1
+            provider_service_instance.save(always_update_timestamp=True, update_fields=["updated", "link_deleted_count"])
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/serviceinterface.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/serviceinterface.py
index 4cf8491..c6a7c5d 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/serviceinterface.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from serviceinterface_decl import *
 
-from service_header import *
+class ServiceInterface(ServiceInterface_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/servicemonitoringagentinfo.py
similarity index 76%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/servicemonitoringagentinfo.py
index 4cf8491..7b7befc 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/servicemonitoringagentinfo.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from servicemonitoringagentinfo_decl import *
 
-from service_header import *
+class ServiceMonitoringAgentInfo(ServiceMonitoringAgentInfo_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/site.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/site.py
index 4cf8491..1fabf93 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/site.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from site_decl import *
 
-from service_header import *
+class Site(Site_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/sitedeployment.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/sitedeployment.py
index 4cf8491..9c79264 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/sitedeployment.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from sitedeployment_decl import *
 
-from service_header import *
+class SiteDeployment(SiteDeployment_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/siteprivilege.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/siteprivilege.py
index 4cf8491..4c13592 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/siteprivilege.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from siteprivilege_decl import *
 
-from service_header import *
+class SitePrivilege(SitePrivilege_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/siterole.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/siterole.py
index 4cf8491..0c89ef3 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/siterole.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from siterole_decl import *
 
-from service_header import *
+class SiteRole(SiteRole_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/slice.py b/xos/core/models/slice.py
new file mode 100644
index 0000000..3cf55f4
--- /dev/null
+++ b/xos/core/models/slice.py
@@ -0,0 +1,51 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from slice_decl import *
+
+class Slice(Slice_decl):
+    class Meta:
+        proxy = True
+
+    NETWORK_CHOICES = ((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))
+
+    @property
+    def slicename(self):
+        return "%s_%s" % (self.site.login_base, self.name)
+
+    def save(self, *args, **kwargs):
+        # set creator on first save
+        if not self.creator and hasattr(self, 'caller'):
+            self.creator = self.caller
+
+        # only admins change a slice's creator
+        if 'creator' in self.changed_fields and \
+            (not hasattr(self, 'caller') or not self.caller.is_admin):
+
+            if (self._initial["creator"]==None) and (self.creator==getattr(self,"caller",None)):
+                # it's okay if the creator is being set by the caller to
+                # himeself on a new slice object.
+                pass
+            else:
+                raise PermissionDenied("Insufficient privileges to change slice creator",
+                                       {'creator': "Insufficient privileges to change slice creator"})
+
+        if self.network=="Private Only":
+            # "Private Only" was the default from the old Tenant View
+            self.network=None
+        self.enforce_choices(self.network, self.NETWORK_CHOICES)
+
+        super(Slice, self).save(*args,  **kwargs)
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/sliceprivilege.py
similarity index 80%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/sliceprivilege.py
index 4cf8491..53eab21 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/sliceprivilege.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from sliceprivilege_decl import *
 
-from service_header import *
+class SlicePrivilege(SlicePrivilege_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/slicerole.py
similarity index 81%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/slicerole.py
index 4cf8491..373fb8c 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/slicerole.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from slicerole_decl import *
 
-from service_header import *
+class SliceRole(SliceRole_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/tag.py
similarity index 83%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/tag.py
index 4cf8491..ae3a8f1 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/tag.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from tag_decl import *
 
-from service_header import *
+class Tag(Tag_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/tenantwithcontainer.py b/xos/core/models/tenantwithcontainer.py
new file mode 100644
index 0000000..f6b73b4
--- /dev/null
+++ b/xos/core/models/tenantwithcontainer.py
@@ -0,0 +1,164 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from tenantwithcontainer_decl import *
+
+class TenantWithContainer(TenantWithContainer_decl):
+    class Meta:
+        proxy = True
+
+    def __init__(self, *args, **kwargs):
+        super(TenantWithContainer, self).__init__(*args, **kwargs)
+
+        # vSG service relies on knowing when instance id has changed
+        self.orig_instance_id = self.get_attribute("instance_id")
+
+    # vSG service relies on instance_id attribute
+    def get_attribute(self, name, default=None):
+        if name=="instance_id":
+            if self.instance:
+                return self.instance.id
+            else:
+                return None
+        else:
+            return super(TenantWithContainer, self).get_attribute(name, default)
+
+    # Services may wish to override the image() function to return different
+    # images based on criteria in the tenant object. For example,
+    #    if (self.has_feature_A):
+    #        return Instance.object.get(name="image_with_feature_a")
+    #    elif (self.has_feature_B):
+    #        return Instance.object.get(name="image_with_feature_b")
+    #    else:
+    #        return super(MyTenantClass,self).image()
+
+    @property
+    def image(self):
+        from core.models import Image
+        # Implement the logic here to pick the image that should be used when
+        # instantiating the VM that will hold the container.
+
+        slice = self.provider_service.slices.all()
+        if not slice:
+            raise XOSProgrammingError("provider service has no slice")
+        slice = slice[0]
+
+        # If slice has default_image set then use it
+        if slice.default_image:
+            return slice.default_image
+
+        raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
+
+    def save_instance(self, instance):
+        # Override this function to do custom pre-save or post-save processing,
+        # such as creating ports for containers.
+        instance.save()
+
+    def pick_least_loaded_instance_in_slice(self, slices, image):
+        for slice in slices:
+            if slice.instances.all().count() > 0:
+                for instance in slice.instances.all():
+                    if instance.image != image:
+                        continue
+                    # Pick the first instance that has lesser than 5 tenants
+                    if self.count_of_tenants_of_an_instance(instance) < 5:
+                        return instance
+        return None
+
+    # TODO: Ideally the tenant count for an instance should be maintained using a
+    # many-to-one relationship attribute, however this model being proxy, it does
+    # not permit any new attributes to be defined. Find if any better solutions
+    def count_of_tenants_of_an_instance(self, instance):
+        tenant_count = 0
+        for tenant in self.__class__.objects.all():
+            if tenant.get_attribute("instance_id", None) == instance.id:
+                tenant_count += 1
+        return tenant_count
+
+    def manage_container(self):
+        from core.models import Instance, Flavor
+
+        if self.deleted:
+            return
+
+        if (self.instance is not None) and (self.instance.image != self.image):
+            self.instance.delete()
+            self.instance = None
+
+        if self.instance is None:
+            if not self.provider_service.slices.count():
+                raise XOSConfigurationError("The service has no slices")
+
+            new_instance_created = False
+            instance = None
+            if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
+                # Find if any existing instances can be used for this tenant
+                slices = self.provider_service.slices.all()
+                instance = self.pick_least_loaded_instance_in_slice(slices, self.image)
+
+            if not instance:
+                slice = self.provider_service.slices.all()[0]
+
+                flavor = slice.default_flavor
+                if not flavor:
+                    flavors = Flavor.objects.filter(name="m1.small")
+                    if not flavors:
+                        raise XOSConfigurationError("No m1.small flavor")
+                    flavor = flavors[0]
+
+                if slice.default_isolation == "container_vm":
+                    (node, parent) = ContainerVmScheduler(slice).pick()
+                else:
+                    (node, parent) = LeastLoadedNodeScheduler(slice).pick()
+
+                instance = Instance(slice=slice,
+                                    node=node,
+                                    image=self.image,
+                                    creator=self.creator,
+                                    deployment=node.site_deployment.deployment,
+                                    flavor=flavor,
+                                    isolation=slice.default_isolation,
+                                    parent=parent)
+                self.save_instance(instance)
+                new_instance_created = True
+
+            try:
+                self.instance = instance
+                super(TenantWithContainer, self).save()
+            except:
+                if new_instance_created:
+                    instance.delete()
+                raise
+
+    def cleanup_container(self):
+        if self.instance:
+            if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
+                # Delete the instance only if this is last tenant in that
+                # instance
+                tenant_count = self.count_of_tenants_of_an_instance(
+                    self.instance)
+                if tenant_count == 0:
+                    self.instance.delete()
+            else:
+                self.instance.delete()
+            self.instance = None
+
+    def save(self, *args, **kwargs):
+        if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
+            self.creator = self.caller
+
+        super(TenantWithContainer, self).save(*args, **kwargs)
+
diff --git a/xos/core/models/xosbase.py b/xos/core/models/xosbase.py
new file mode 100644
index 0000000..03404cf
--- /dev/null
+++ b/xos/core/models/xosbase.py
@@ -0,0 +1,196 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xos.exceptions import *
+from xosbase_decl import *
+
+class XOSBase(XOSBase_decl):
+    objects = XOSBaseManager()
+    deleted_objects = XOSBaseDeletionManager()
+
+    class Meta:
+        # Changing abstract to False would require the managers of subclasses of
+        # XOSBase to be customized individually.
+        abstract = True
+        app_label = "core"
+
+    def __init__(self, *args, **kwargs):
+        super(XOSBase, self).__init__(*args, **kwargs)
+        self._initial = self._dict # for PlModelMixIn
+        self.silent = False
+
+    def get_controller(self):
+        return self.controller
+
+    def delete(self, *args, **kwds):
+        # so we have something to give the observer
+        purge = kwds.get('purge',False)
+        if purge:
+            del kwds['purge']
+        silent = kwds.get('silent',False)
+        if silent:
+            del kwds['silent']
+        try:
+            purge = purge or observer_disabled
+        except NameError:
+            pass
+
+        if (purge):
+            pk = self.pk
+            super(XOSBase, self).delete(*args, **kwds)
+            self.push_redis_event(deleted=True, pk=pk)
+        else:
+            if (not self.write_protect ):
+                self.deleted = True
+                self.enacted=None
+                self.policed=None
+                self.save(update_fields=['enacted','deleted','policed'], silent=silent)
+
+                collector = XOSCollector(using=router.db_for_write(self.__class__, instance=self))
+                collector.collect([self])
+                with transaction.atomic():
+                    for (k, models) in collector.data.items():
+                        for model in models:
+                            if not hasattr(model, "deleted"):
+                                # Automatically generated through relations from ManyToMany fields do not have soft-delete
+                                # capability.
+                                continue
+                            if model.deleted:
+                                # in case it's already been deleted, don't delete again
+                                continue
+                            model.deleted = True
+                            model.enacted=None
+                            model.policed=None
+                            model.save(update_fields=['enacted','deleted','policed'], silent=silent)
+
+    def verify_live_keys(self, update_fields):
+        """ Check the fields to be updated, if they contain foreign keys, that the foreign keys only point
+            to live objects in the database.
+
+            This is to catch races between model policies where an object is being deleted while a model policy is
+            still operating on it.
+        """
+
+        if getattr(self, "deleted", False):
+            # If this model is already deleted, then no need to check anything. We only need to check for live
+            # models that point to dead models. If a dead model points to other dead models, then we could
+            # be updating something else in the dead model (backend_status, etc)
+            return
+
+        for field in self._meta.fields:
+            try:
+                f = getattr(self, field.name)
+            except Exception, e:
+                # Exception django.db.models.fields.related.RelatedObjectDoesNotExist
+                # is thrown by django when you're creating an object that has a base and the base doesn't exist yet
+                continue
+
+            if f is None:
+                # If field hold a null value, we don't care
+                continue
+
+            ftype = field.get_internal_type()
+            if (ftype != "ForeignKey"):
+                # If field isn't a foreign key, we don't care
+                continue
+
+            if (update_fields) and (field.name not in update_fields):
+                # If update_fields is nonempty, and field is not to be updated, we don't care.
+                continue
+
+            if getattr(f, "deleted", False):
+                raise Exception("Attempt to save object with deleted foreign key reference")
+
+    def save(self, *args, **kwargs):
+
+        # let the user specify silence as either a kwarg or an instance varible
+        silent = self.silent
+        if "silent" in kwargs:
+            silent=silent or kwargs.pop("silent")
+
+        caller_kind = "unknown"
+
+        if ('synchronizer' in threading.current_thread().name):
+            caller_kind = "synchronizer"
+
+        if "caller_kind" in kwargs:
+            caller_kind = kwargs.pop("caller_kind")
+
+        always_update_timestamp = False
+        if "always_update_timestamp" in kwargs:
+            always_update_timestamp = always_update_timestamp or kwargs.pop("always_update_timestamp")
+
+        # SMBAKER: if an object is trying to delete itself, or if the observer
+        # is updating an object's backend_* fields, then let it slip past the
+        # composite key check.
+        ignore_composite_key_check=False
+        if "update_fields" in kwargs:
+            ignore_composite_key_check=True
+            for field in kwargs["update_fields"]:
+                if not (field in ["backend_register", "backend_status", "deleted", "enacted", "updated"]):
+                    ignore_composite_key_check=False
+
+        if (caller_kind!="synchronizer") or always_update_timestamp:
+            self.updated = timezone.now()
+        else:
+            # We're not auto-setting timestamp, but let's check to make sure that the caller hasn't tried to set our
+            # timestamp backward...
+            if (self.updated != self._initial["updated"]) and ((not kwargs.get("update_fields")) or ("updated" in kwargs.get("update_fields"))):
+                log.info("Synchronizer tried to change `updated` timestamp on model %s from %s to %s. Ignored." % (self, self._initial["updated"], self.updated))
+                self.updated = self._initial["updated"]
+
+        with transaction.atomic():
+            self.verify_live_keys(update_fields = kwargs.get("update_fields"))
+            super(XOSBase, self).save(*args, **kwargs)
+
+        self.push_redis_event()
+
+        self._initial = self._dict
+
+    def tologdict(self):
+        try:
+            d = {'model_name':self.__class__.__name__, 'pk': self.pk}
+        except:
+            d = {}
+
+        return d
+
+    # for the old django admin UI
+    def __unicode__(self):
+        if hasattr(self, "name") and self.name:
+            return u'%s' % self.name
+        elif hasattr(self, "id") and self.id:
+            if hasattr(self, "leaf_model_name") and self.leaf_model_name:
+                return u'%s-%s' % (self.leaf_model_name, self.id)
+            else:
+                return u'%s-%s' % (self.__class__.__name__, self.id)
+        else:
+            return u'%s-unsaved' % self.__class__.__name__
+
+    def get_content_type_key(self):
+        ct = ContentType.objects.get_for_model(self.__class__)
+        return "%s.%s" % (ct.app_label, ct.model)
+
+    @staticmethod
+    def get_content_type_from_key(key):
+        (app_name, model_name) = key.split(".")
+        return ContentType.objects.get_by_natural_key(app_name, model_name)
+
+    @staticmethod
+    def get_content_object(content_type, object_id):
+        ct = XOSBase.get_content_type_from_key(content_type)
+        cls = ct.model_class()
+        return cls.objects.get(id=object_id)
+
diff --git a/xos/core/models/xosbase_header.py b/xos/core/models/xosbase_header.py
deleted file mode 120000
index dd654e6..0000000
--- a/xos/core/models/xosbase_header.py
+++ /dev/null
@@ -1 +0,0 @@
-attic/xosbase_header.py
\ No newline at end of file
diff --git a/xos/core/models/attic/xosbase_header.py b/xos/core/models/xosbase_header.py
similarity index 79%
rename from xos/core/models/attic/xosbase_header.py
rename to xos/core/models/xosbase_header.py
index 8afc3f0..f16b611 100644
--- a/xos/core/models/attic/xosbase_header.py
+++ b/xos/core/models/xosbase_header.py
@@ -22,6 +22,7 @@
 import inspect
 import sys
 import threading
+import django
 from django.db import models
 from django.utils.timezone import now
 from django.db.models import *
@@ -31,7 +32,6 @@
 from django.core.exceptions import PermissionDenied
 from cgi import escape as html_escape
 from django.db.models.deletion import Collector
-from django.db.models.query import QuerySet
 from django.db import router
 from django.contrib.contenttypes.models import ContentType
 
@@ -144,6 +144,29 @@
     def get_field_diff(self, field_name):
         return self.diff.get(field_name, None)
 
+    @property
+    def leaf_model(self):
+        leaf_model_name = getattr(self, "leaf_model_name", None)
+        if not leaf_model_name:
+            return self
+
+        if (leaf_model_name == self.__class__.__name__):
+            return self
+
+        all_models = django.apps.apps.get_models(include_auto_created=False)
+        all_models_by_name = {}
+        for model in all_models:
+            all_models_by_name[model.__name__] = model
+
+        leaf_model_class = all_models_by_name.get(self.leaf_model_name)
+
+        assert (self.id)
+
+        if self.deleted:
+            return leaf_model_class.deleted_objects.get(id=self.id)
+        else:
+            return leaf_model_class.objects.get(id=self.id)
+
     #classmethod
     def getValidators(cls):
         """ primarily for REST API, return a dictionary of field names mapped
@@ -285,6 +308,54 @@
             log.error('Connection to Redis failed')
             pass
 
+class AttributeMixin(object):
+    # helper for extracting things from a json-encoded
+    # service_specific_attribute
+
+    def get_attribute(self, name, default=None):
+        if self.service_specific_attribute:
+            attributes = json.loads(self.service_specific_attribute)
+        else:
+            attributes = {}
+        return attributes.get(name, default)
+
+    def set_attribute(self, name, value):
+        if self.service_specific_attribute:
+            attributes = json.loads(self.service_specific_attribute)
+        else:
+            attributes = {}
+        attributes[name] = value
+        self.service_specific_attribute = json.dumps(attributes)
+
+    def get_initial_attribute(self, name, default=None):
+        if self._initial["service_specific_attribute"]:
+            attributes = json.loads(
+                self._initial["service_specific_attribute"])
+        else:
+            attributes = {}
+        return attributes.get(name, default)
+
+    @classmethod
+    def get_default_attribute(cls, name):
+        for (attrname, default) in cls.simple_attributes:
+            if attrname == name:
+                return default
+        if hasattr(cls, "default_attributes"):
+            if name in cls.default_attributes:
+                return cls.default_attributes[name]
+
+        return None
+
+    @classmethod
+    def setup_simple_attributes(cls):
+        for (attrname, default) in cls.simple_attributes:
+            setattr(cls, attrname, property(
+                lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
+                lambda self, value, attrname=attrname: self.set_attribute(
+                    attrname, value),
+                None,
+                attrname))
+
 # For cascading deletes, we need a Collector that doesn't do fastdelete,
 # so we get a full list of models.
 class XOSCollector(Collector):
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/xoscore.py
similarity index 82%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/xoscore.py
index 4cf8491..1243a56 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/xoscore.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from xoscore_decl import *
 
-from service_header import *
+class XOSCore(XOSCore_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/core/models/attic/serviceinstance_top.py b/xos/core/models/xosguiextension.py
similarity index 79%
copy from xos/core/models/attic/serviceinstance_top.py
copy to xos/core/models/xosguiextension.py
index 4cf8491..835a430 100644
--- a/xos/core/models/attic/serviceinstance_top.py
+++ b/xos/core/models/xosguiextension.py
@@ -13,5 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from xos.exceptions import *
+from xosguiextension_decl import *
 
-from service_header import *
+class XOSGuiExtension(XOSGuiExtension_decl):
+    class Meta:
+        proxy = True
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index 7c8c6a0..3bb2abb 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -239,7 +239,6 @@
             elif (ftype == "GenericIPAddressField"):
                 setattr(p_obj, field.name, str(getattr(obj, field.name)))
 
-
         # Introspecting the django object for related objects is problematic due to _decl-style attics. The descendant
         # class's _meta's related_objects doesn't include related objects from the base. For example, VSGServiceInstance
         # was missing provided_links and subscribed_links, since those were declared in ServiceInstance. (This problem
diff --git a/xos/coreapi/reaper.py b/xos/coreapi/reaper.py
index 75f75b2..ebc4556 100644
--- a/xos/coreapi/reaper.py
+++ b/xos/coreapi/reaper.py
@@ -138,6 +138,9 @@
                         log.info("skipping because it has need_delete_policy set", object = d)
                         continue
 
+                    if hasattr(d, "leaf_model"):
+                        d = d.leaf_model
+
                     cascade_set = self.get_cascade_set(d)
                     if cascade_set:
                         self.journal_object(d, "reaper.cascade_set", msg=",".join([str(m) for m in cascade_set]))
@@ -152,6 +155,7 @@
                         self.journal_object(d, "reaper.purge.exception")
                         log.error('REAPER: exception purging object', object = d)
                         traceback.print_exc()
+
             try:
                 reset_queries()
             except:
diff --git a/xos/xos_client/xosapi/convenience/network.py b/xos/xos_client/xosapi/convenience/network.py
new file mode 100644
index 0000000..7ea792a
--- /dev/null
+++ b/xos/xos_client/xosapi/convenience/network.py
@@ -0,0 +1,33 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import json
+from xosapi.orm import ORMWrapper, ORMLocalObjectManager, register_convenience_wrapper
+
+class ORMWrapperNetwork(ORMWrapper):
+    # slices- emulates the ManyToMany from Slice to Network via NetworkSlice
+    @property
+    def slices(self):
+        idList = [x.slice.id for x in self.networkslices.all()]
+        return ORMLocalObjectManager(self.stub, "Slice", idList, False)
+
+    # instances- emulates the ManyToMany from Network to Instance via Port
+    @property
+    def instances(self):
+        idList = [x.instance.id for x in self.links.all()]
+        return ORMLocalObjectManager(self.stub, "Instance", idList, False)
+
+register_convenience_wrapper("Network", ORMWrapperNetwork)
diff --git a/xos/xos_client/xosapi/convenience/slice.py b/xos/xos_client/xosapi/convenience/slice.py
index 7686129..8431bd2 100644
--- a/xos/xos_client/xosapi/convenience/slice.py
+++ b/xos/xos_client/xosapi/convenience/slice.py
@@ -15,7 +15,7 @@
 
 
 import json
-from xosapi.orm import ORMWrapper, register_convenience_wrapper
+from xosapi.orm import ORMWrapper, ORMLocalObjectManager, register_convenience_wrapper
 
 class ORMWrapperSlice(ORMWrapper):
     # TODO: this looks to be incorrect
@@ -23,4 +23,10 @@
     def slicename(self):
         return "%s_%s" % (self.site.login_base, self.name)
 
+    # networks - emulates the ManyToMany from Slice to Network via NetworkSlice
+    @property
+    def networks(self):
+        idList = [x.network.id for x in self.networkslices.all()]
+        return ORMLocalObjectManager(self.stub, "Network", idList, False)
+
 register_convenience_wrapper("Slice", ORMWrapperSlice)
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index adbe58d..db9c468 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -610,6 +610,7 @@
 import convenience.addresspool
 import convenience.privilege
 import convenience.instance
+import convenience.network
 import convenience.cordsubscriberroot
 import convenience.volttenant
 import convenience.vsgserviceinstance