CORD-1154: Integrate xproto generator with corebuilder

Change-Id: Id3fb73f5368a15a79097a56b097726b6a57573b0
diff --git a/README.md b/README.md
index dc673ad..0b93d3f 100644
--- a/README.md
+++ b/README.md
@@ -21,4 +21,3 @@
 version is configured with a service graph that includes
 `ExampleService`, which is a good platform for understanding how to
 build and use XOS.
-
diff --git a/containers/xos/Dockerfile.corebuilder b/containers/xos/Dockerfile.corebuilder
index f1e16a8..6b364b7 100644
--- a/containers/xos/Dockerfile.corebuilder
+++ b/containers/xos/Dockerfile.corebuilder
@@ -5,6 +5,9 @@
 ADD xos/tools/corebuilder /opt/xos/tools/corebuilder
 ADD xos/tosca/custom_types /opt/xos/tools/corebuilder/custom_types
 
+# Temporary fix. This should go away when we update xos-core to a more recent image.
+RUN pip install git+https://github.com/sb98052/plyprotobuf
+
 ENV HOME /root
 WORKDIR /opt/xos/tools/corebuilder
 
diff --git a/xos/core/core-onboard.yaml b/xos/core/core-onboard.yaml
new file mode 100644
index 0000000..59aefac
--- /dev/null
+++ b/xos/core/core-onboard.yaml
@@ -0,0 +1,14 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard XOS Core
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    servicecontroller#core:
+      type: tosca.nodes.ServiceController
+      properties:
+          base_url: file:///opt/xos/core
+          xproto: core/models
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
deleted file mode 100644
index 7bfdb58..0000000
--- a/xos/core/models/__init__.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from .plcorebase import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,PlModelMixIn,ModelLink
-from .project import Project
-from .singletonmodel import SingletonModel
-from .xosmodel import XOS, XOSVolume
-from .service import Service, Tenant, TenantWithContainer, ServiceDependency, ServicePrivilege, TenantRoot, TenantRootPrivilege, TenantRootRole, TenantPrivilege, TenantRole
-from .service import ServiceAttribute, TenantAttribute, ServiceRole, ServiceMonitoringAgentInfo
-from .service import ServiceController, LoadableModule, LoadableModuleResource, Library
-from .service import XOSComponent, XOSComponentLink, XOSComponentVolume, XOSComponentVolumeContainer
-from .tag import Tag
-from .role import Role
-from .site import Site, Deployment, DeploymentRole, DeploymentPrivilege, Controller, ControllerRole, ControllerSite, SiteDeployment,Diag
-from .dashboard import DashboardView, ControllerDashboardView, XOSGuiExtension
-from .user import User, UserDashboardView
-from .serviceclass import ServiceClass
-from .site import ControllerManager, ControllerDeletionManager, ControllerLinkManager,ControllerLinkDeletionManager
-from .flavor import Flavor
-from .image import Image
-from .slice import Slice, ControllerSlice
-from .controlleruser import ControllerUser, ControllerSitePrivilege, ControllerSlicePrivilege
-from .image import ImageDeployments, ControllerImages
-from .serviceresource import ServiceResource
-from .slice import SliceRole
-from .slice import SlicePrivilege
-from .credential import UserCredential,SiteCredential,SliceCredential
-from .site import SiteRole
-from .site import SitePrivilege
-from .node import Node, NodeLabel
-from .slicetag import SliceTag
-from .instance import Instance
-from .reservation import ReservedResource
-from .reservation import Reservation
-from .network import Network, NetworkParameterType, NetworkParameter, Port, NetworkTemplate, Router, NetworkSlice, ControllerNetwork, AddressPool
-from .billing import Account, Invoice, Charge, UsableObject, Payment
-from .program import Program
-from .journal import JournalEntry, journal_object
diff --git a/xos/core/models/account.xproto b/xos/core/models/account.xproto
new file mode 100644
index 0000000..ba24140
--- /dev/null
+++ b/xos/core/models/account.xproto
@@ -0,0 +1,5 @@
+
+
+message Account (PlCoreBase){
+     required manytoone site->Site:accounts = 1 [help_text = "Site for this account", null = False, db_index = True, blank = False];
+}
diff --git a/xos/core/models/addresspool.xproto b/xos/core/models/addresspool.xproto
new file mode 100644
index 0000000..737de26
--- /dev/null
+++ b/xos/core/models/addresspool.xproto
@@ -0,0 +1,11 @@
+
+
+message AddressPool (PlCoreBase){
+     required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+     optional string addresses = 2 [db_index = False, null = True, blank = True];
+     optional string gateway_ip = 3 [db_index = False, max_length = 32, null = True, blank = False];
+     optional string gateway_mac = 4 [db_index = False, max_length = 32, null = True, blank = False];
+     optional string cidr = 5 [db_index = False, max_length = 32, null = True, blank = False];
+     optional string inuse = 6 [db_index = False, null = True, blank = True];
+     optional manytoone service->Service:addresspools = 7 [db_index = True, null = True, blank = True];
+}
diff --git a/xos/core/models/attic/README.md b/xos/core/models/attic/README.md
new file mode 100644
index 0000000..784a8b6
--- /dev/null
+++ b/xos/core/models/attic/README.md
@@ -0,0 +1 @@
+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/account_model.py b/xos/core/models/attic/account_model.py
new file mode 100644
index 0000000..3e75dc2
--- /dev/null
+++ b/xos/core/models/attic/account_model.py
@@ -0,0 +1,23 @@
+@property
+def total_invoices(self):
+    # Since the amount of an invoice is the sum of it's charges, we can
+    # compute the sum of the invoices by summing all charges where
+    # charge.invoice != Null.
+    x=self.charges.filter(invoice__isnull=False).aggregate(Sum('amount'))["amount__sum"]
+    if (x==None):
+        return 0.0
+    return x
+
+@property
+def total_payments(self):
+    x=self.payments.all().aggregate(Sum('amount'))["amount__sum"]
+    if (x==None):
+        return 0.0
+    return x
+
+@property
+def balance_due(self):
+    return self.total_invoices - self.total_payments
+
+def __unicode__(self):  return u'%s' % (self.site.name)
+
diff --git a/xos/core/models/attic/addresspool_model.py b/xos/core/models/attic/addresspool_model.py
new file mode 100644
index 0000000..47576f1
--- /dev/null
+++ b/xos/core/models/attic/addresspool_model.py
@@ -0,0 +1,52 @@
+def __unicode__(self): return u'%s' % (self.name)
+
+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/charge_model.py b/xos/core/models/attic/charge_model.py
new file mode 100644
index 0000000..0eed5ac
--- /dev/null
+++ b/xos/core/models/attic/charge_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s-%0.2f-%s' % (self.account.site.name, self.amount, str(self.date))
diff --git a/xos/core/models/attic/controller_model.py b/xos/core/models/attic/controller_model.py
new file mode 100644
index 0000000..b053cab
--- /dev/null
+++ b/xos/core/models/attic/controller_model.py
@@ -0,0 +1,26 @@
+objects = ControllerManager()
+deleted_objects = ControllerDeletionManager()
+
+def __init__(self, *args, **kwargs):
+    super(Controller, self).__init__(*args, **kwargs)
+    self.no_sync=True
+
+def __unicode__(self):  return u'%s %s %s' % (self.name, self.backend_type, self.version)
+
+@property
+def auth_url_v3(self):
+    if self.auth_url and self.auth_url[-1] == '/':
+        return '{}/v3/'.format('/'.join(self.auth_url.split('/')[:-2]))
+    else:
+        return '{}/v3/'.format('/'.join(self.auth_url.split('/')[:-1]))
+
+@staticmethod
+def select_by_user(user):
+
+    if user.is_admin:
+        qs = Controller.objects.all()
+    else:
+        from core.models.deploymentprivilege import DeploymentPrivilege
+        deployments = [dp.deployment for dp in DeploymentPrivilege.objects.filter(user=user, role__role__in=['Admin', 'admin'])]
+        qs = Controller.objects.filter(deployment__in=deployments)
+    return qs
diff --git a/xos/core/models/attic/controllercredential_model.py b/xos/core/models/attic/controllercredential_model.py
new file mode 100644
index 0000000..64ee81b
--- /dev/null
+++ b/xos/core/models/attic/controllercredential_model.py
@@ -0,0 +1,5 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
+
+def __unicode__(self):
+    return self.name
diff --git a/xos/core/models/attic/controllerdashboardview_model.py b/xos/core/models/attic/controllerdashboardview_model.py
new file mode 100644
index 0000000..bb2ede2
--- /dev/null
+++ b/xos/core/models/attic/controllerdashboardview_model.py
@@ -0,0 +1,2 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
diff --git a/xos/core/models/attic/controllerimages_model.py b/xos/core/models/attic/controllerimages_model.py
new file mode 100644
index 0000000..42f2d1a
--- /dev/null
+++ b/xos/core/models/attic/controllerimages_model.py
@@ -0,0 +1,7 @@
+class Meta:
+    unique_together = ('image', 'controller')
+         
+def __unicode__(self):  return u'%s %s' % (self.image, self.controller)
+
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
diff --git a/xos/core/models/attic/controllernetwork_model.py b/xos/core/models/attic/controllernetwork_model.py
new file mode 100644
index 0000000..b1c9247
--- /dev/null
+++ b/xos/core/models/attic/controllernetwork_model.py
@@ -0,0 +1,26 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
+
+class Meta:
+    unique_together = ('network', 'controller')
+
+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
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = ControllerNetwork.objects.all()
+    else:
+        from core.models.slice import Slice
+        slices = Slice.select_by_user(user)
+        networks = Network.objects.filter(owner__in=slices)
+        qs = ControllerNetwork.objects.filter(network__in=networks)
+    return qs
+
diff --git a/xos/core/models/attic/controllerrole_model.py b/xos/core/models/attic/controllerrole_model.py
new file mode 100644
index 0000000..c5ebd53
--- /dev/null
+++ b/xos/core/models/attic/controllerrole_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s' % (self.role)
diff --git a/xos/core/models/attic/controllersite_model.py b/xos/core/models/attic/controllersite_model.py
new file mode 100644
index 0000000..987ce4c
--- /dev/null
+++ b/xos/core/models/attic/controllersite_model.py
@@ -0,0 +1,2 @@
+class Meta:
+    unique_together = ('site', 'controller')
diff --git a/xos/core/models/attic/controllersiteprivilege_model.py b/xos/core/models/attic/controllersiteprivilege_model.py
new file mode 100644
index 0000000..a3b3451
--- /dev/null
+++ b/xos/core/models/attic/controllersiteprivilege_model.py
@@ -0,0 +1,27 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
+
+class Meta:
+    unique_together = ('controller', 'site_privilege', 'role_id')
+
+def __unicode__(self):  return u'%s %s' % (self.controller, self.site_privilege)
+
+def can_update(self, user):
+    if user.is_readonly:
+        return False
+    if user.is_admin:
+        return True
+    cprivs = ControllerSitePrivilege.objects.filter(site_privilege__user=user)
+    for cpriv in dprivs:
+        if cpriv.site_privilege.role.role == ['admin', 'Admin']:
+            return True
+    return False
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = ControllerSitePrivilege.objects.all()
+    else:
+        cpriv_ids = [cp.id for cp in ControllerSitePrivilege.objects.filter(site_privilege__user=user)]
+        qs = ControllerSitePrivilege.objects.filter(id__in=cpriv_ids)
+    return qs
diff --git a/xos/core/models/attic/controllerslice_model.py b/xos/core/models/attic/controllerslice_model.py
new file mode 100644
index 0000000..9e9fdc3
--- /dev/null
+++ b/xos/core/models/attic/controllerslice_model.py
@@ -0,0 +1,36 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
+
+class Meta:
+    unique_together = ('controller', 'slice')
+ 
+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
+
+def __unicode__(self):  return u'%s %s'  % (self.slice, self.controller)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = ControllerSlice.objects.all()
+    else:
+        slices = Slice.select_by_user(user)
+        qs = ControllerSlice.objects.filter(slice__in=slices)
+    return qs    
+
+def get_cpu_stats(self):
+    filter = 'project_id=%s'%self.tenant_id
+    return monitor.get_meter('cpu',filter,None)
+
+def get_bw_stats(self):
+    filter = 'project_id=%s'%self.tenant_id
+    return monitor.get_meter('network.outgoing.bytes',filter,None)
+
+def get_node_stats(self):
+    return len(self.slice.instances)
diff --git a/xos/core/models/attic/controllersliceprivilege_model.py b/xos/core/models/attic/controllersliceprivilege_model.py
new file mode 100644
index 0000000..6414464
--- /dev/null
+++ b/xos/core/models/attic/controllersliceprivilege_model.py
@@ -0,0 +1,24 @@
+class Meta:
+    unique_together = ('controller', 'slice_privilege')
+
+def __unicode__(self):  return u'%s %s' % (self.controller, self.slice_privilege)
+
+def can_update(self, user):
+    if user.is_readonly:
+        return False
+    if user.is_admin:
+        return True
+    cprivs = ControllerSlicePrivilege.objects.filter(slice_privilege__user=user)
+    for cpriv in dprivs:
+        if cpriv.role.role == ['admin', 'Admin']:
+            return True
+    return False
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = ControllerSlicePrivilege.objects.all()
+    else:
+        cpriv_ids = [cp.id for cp in ControllerSlicePrivilege.objects.filter(slice_privilege__user=user)]
+        qs = ControllerSlicePrivilege.objects.filter(id__in=cpriv_ids)
+    return qs
diff --git a/xos/core/models/attic/controlleruser_model.py b/xos/core/models/attic/controlleruser_model.py
new file mode 100644
index 0000000..d6d73cb
--- /dev/null
+++ b/xos/core/models/attic/controlleruser_model.py
@@ -0,0 +1,20 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
+
+class Meta:
+    unique_together = ('user', 'controller')
+
+def __unicode__(self):  return u'%s %s' % (self.controller, self.user)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = ControllerUser.objects.all()
+    else:
+        users = User.select_by_user(user)
+        qs = ControllerUser.objects.filter(user__in=users)
+    return qs
+
+def can_update(self, user):
+    return user.can_update_root()    
+
diff --git a/xos/core/models/attic/dashboardview_model.py b/xos/core/models/attic/dashboardview_model.py
new file mode 100644
index 0000000..087b164
--- /dev/null
+++ b/xos/core/models/attic/dashboardview_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s' % (self.name)
diff --git a/xos/core/models/attic/deployment_model.py b/xos/core/models/attic/deployment_model.py
new file mode 100644
index 0000000..a055df3
--- /dev/null
+++ b/xos/core/models/attic/deployment_model.py
@@ -0,0 +1,40 @@
+def __init__(self, *args, **kwargs):
+    super(Deployment, self).__init__(*args, **kwargs)
+    self.no_sync=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)
+
+def can_update(self, user):
+    return user.can_update_deployment(self)
+
+def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/attic/deploymentprivilege_model.py b/xos/core/models/attic/deploymentprivilege_model.py
new file mode 100644
index 0000000..539234c
--- /dev/null
+++ b/xos/core/models/attic/deploymentprivilege_model.py
@@ -0,0 +1,17 @@
+class Meta:
+    unique_together = ('user', 'deployment', 'role')
+
+def __unicode__(self):  return u'%s %s %s' % (self.deployment, self.user, self.role)
+
+def can_update(self, user):
+    return user.can_update_deployment(self)
+
+@staticmethod
+def select_by_user(user):
+    from core.models.deploymentprivilege import DeploymentPrivilege
+    if user.is_admin:
+        qs = DeploymentPrivilege.objects.all()
+    else:
+        dpriv_ids = [dp.id for dp in DeploymentPrivilege.objects.filter(user=user)]
+        qs = DeploymentPrivilege.objects.filter(id__in=dpriv_ids)
+    return qs
diff --git a/xos/core/models/attic/deploymentrole_model.py b/xos/core/models/attic/deploymentrole_model.py
new file mode 100644
index 0000000..b35a54b
--- /dev/null
+++ b/xos/core/models/attic/deploymentrole_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s' % (self.role)
diff --git a/xos/core/models/attic/diag_model.py b/xos/core/models/attic/diag_model.py
new file mode 100644
index 0000000..bf6bd68
--- /dev/null
+++ b/xos/core/models/attic/diag_model.py
@@ -0,0 +1,7 @@
+@property
+def enacted(self):
+    return None
+
+@enacted.setter
+def enacted(self, value):
+    pass # Ignore sets, Diag objects are always pending.
diff --git a/xos/core/models/attic/flavor_model.py b/xos/core/models/attic/flavor_model.py
new file mode 100644
index 0000000..f5403f7
--- /dev/null
+++ b/xos/core/models/attic/flavor_model.py
@@ -0,0 +1,9 @@
+class Meta:
+    app_label = "core"
+    ordering = ('order', 'name')
+
+def __init__(self, *args, **kwargs):
+    super(Flavor, self).__init__(*args, **kwargs)
+    self.no_sync=True
+
+def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/attic/header.py b/xos/core/models/attic/header.py
new file mode 100644
index 0000000..fe0a7f6
--- /dev/null
+++ b/xos/core/models/attic/header.py
@@ -0,0 +1,226 @@
+from __future__ import absolute_import
+
+import sys
+import json
+import operator
+from operator import attrgetter
+from core.models.plcorebase 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
+from xos.config import Config
+
+config = Config()
+
+# If true, then IP addresses will be allocated by the model. If false, then
+# we will assume the observer handles it.
+NO_OBSERVER=False
+
+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))
+
+
+
+def get_default_flavor(controller = None):
+    # Find a default flavor that can be used for a instance. This is particularly
+    # useful in evolution. It's also intended this helper function can be used
+    # for admin.py when users
+
+    if controller:
+        flavors = controller.flavors.all()
+    else:
+        from core.models.flavor import Flavor
+        flavors = Flavor.objects.all()
+
+    if not flavors:
+        return None
+
+    for flavor in flavors:
+        if flavor.default:
+            return flavor
+
+    return flavors[0]
+
+class InstanceDeletionManager(PlCoreBaseDeletionManager):
+    def get_queryset(self):
+        parent=super(InstanceDeletionManager, self)
+        try:
+            backend_type = config.observer_backend_type
+        except AttributeError:
+            backend_type = None
+
+        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
+        if (backend_type):
+            return parent_queryset.filter(Q(node__controller__backend_type=backend_type))
+        else:
+            return parent_queryset
+
+    # deprecated in django 1.7 in favor of get_queryset().
+    def get_query_set(self):
+        return self.get_queryset()
+
+
+class InstanceManager(PlCoreBaseManager):
+    def get_queryset(self):
+        parent=super(InstanceManager, self)
+
+        try:
+            backend_type = config.observer_backend_type
+        except AttributeError:
+            backend_type = None
+
+        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
+
+        if backend_type:
+            return parent_queryset.filter(Q(node__controller__backend_type=backend_type))
+        else:
+            return parent_queryset
+
+    # deprecated in django 1.7 in favor of get_queryset().
+    def get_query_set(self):
+        return self.get_queryset()
+        
+def get_default_serviceclass():
+    from core.models.serviceclass import ServiceClass
+    try:
+        return ServiceClass.objects.get(name="Best Effort")
+    except ServiceClass.DoesNotExist:
+        return None
+
+class ControllerLinkDeletionManager(PlCoreBaseDeletionManager):
+    def get_queryset(self):
+        parent=super(ControllerLinkDeletionManager, self)
+        try:
+            backend_type = config.observer_backend_type
+        except AttributeError:
+            backend_type = None
+
+        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
+        if (backend_type):
+            return parent_queryset.filter(Q(controller__backend_type=backend_type))
+        else:
+            return parent_queryset
+
+    # deprecated in django 1.7 in favor of get_queryset().
+    def get_query_set(self):
+        return self.get_queryset()
+
+
+class ControllerDeletionManager(PlCoreBaseDeletionManager):
+    def get_queryset(self):
+        parent=super(ControllerDeletionManager, self)
+
+        try:
+            backend_type = config.observer_backend_type
+        except AttributeError:
+            backend_type = None
+
+        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
+
+        if backend_type:
+            return parent_queryset.filter(Q(backend_type=backend_type))
+        else:
+            return parent_queryset
+
+    # deprecated in django 1.7 in favor of get_queryset().
+    def get_query_set(self):
+        return self.get_queryset()
+
+class ControllerLinkManager(PlCoreBaseManager):
+    def get_queryset(self):
+        parent=super(ControllerLinkManager, self)
+
+        try:
+            backend_type = config.observer_backend_type
+        except AttributeError:
+            backend_type = None
+
+        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
+
+        if backend_type:
+            return parent_queryset.filter(Q(controller__backend_type=backend_type))
+        else:
+            return parent_queryset
+
+    # deprecated in django 1.7 in favor of get_queryset().
+    def get_query_set(self):
+        return self.get_queryset()
+
+
+class ControllerManager(PlCoreBaseManager):
+    def get_queryset(self):
+        parent=super(ControllerManager, self)
+
+        try:
+            backend_type = config.observer_backend_type
+        except AttributeError:
+            backend_type = None
+
+        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
+
+        if backend_type:
+            return parent_queryset.filter(Q(backend_type=backend_type))
+        else:
+            return parent_queryset
+
+    # deprecated in django 1.7 in favor of get_queryset().
+    def get_query_set(self):
+        return self.get_queryset()
+
diff --git a/xos/core/models/attic/image_model.py b/xos/core/models/attic/image_model.py
new file mode 100644
index 0000000..32aa211
--- /dev/null
+++ b/xos/core/models/attic/image_model.py
@@ -0,0 +1,4 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
+
+def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/attic/imagedeployments_model.py b/xos/core/models/attic/imagedeployments_model.py
new file mode 100644
index 0000000..612cbb2
--- /dev/null
+++ b/xos/core/models/attic/imagedeployments_model.py
@@ -0,0 +1,7 @@
+class Meta:
+    unique_together = ('image', 'deployment')
+
+def __unicode__(self):  return u'%s %s' % (self.image, self.deployment)
+
+def can_update(self, user):
+    return user.can_update_deployment(self.deployment)
diff --git a/xos/core/models/attic/instance_bottom.py b/xos/core/models/attic/instance_bottom.py
new file mode 100644
index 0000000..7b87eab
--- /dev/null
+++ b/xos/core/models/attic/instance_bottom.py
@@ -0,0 +1,7 @@
+def controller_setter(instance, **kwargs):
+    try:
+        instance.controller = instance.node.site_deployment.controller
+    except:
+        instance.controller = None
+
+models.signals.post_init.connect(controller_setter, Instance)
diff --git a/xos/core/models/attic/instance_model.py b/xos/core/models/attic/instance_model.py
new file mode 100644
index 0000000..b42dcfc
--- /dev/null
+++ b/xos/core/models/attic/instance_model.py
@@ -0,0 +1,153 @@
+objects = InstanceManager()
+deleted_objects = InstanceDeletionManager()
+
+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 __unicode__(self):
+    if self.name and Slice.objects.filter(id=self.slice_id) and (self.name != self.slice.name):
+        # NOTE: The weird check on self.slice_id was due to a problem when
+        #   deleting the slice before the instance.
+        return u'%s' % self.name
+    elif self.instance_name:
+        return u'%s' % (self.instance_name)
+    elif self.id:
+        return u'uninstantiated-%s' % str(self.id)
+    elif self.slice:
+        return u'unsaved-instance on %s' % self.slice.name
+    else:
+        return u'unsaved-instance'
+
+def save(self, *args, **kwds):
+    if not self.name:
+        self.name = self.slice.name
+    if not self.creator and hasattr(self, 'caller'):
+        self.creator = self.caller
+    if not self.creator:
+        raise ValidationError('instance has no creator')
+
+    if (self.isolation == "container") or (self.isolation == "container_vm"):
+        if (self.image.kind != "container"):
+           raise ValidationError("Container instance must use container image")
+    elif (self.isolation == "vm"):
+        if (self.image.kind != "vm"):
+           raise ValidationError("VM instance must use VM image")
+
+    if (self.isolation == "container_vm") and (not self.parent):
+        raise ValidationError("Container-vm instance must have a parent")
+
+    if (self.parent) and (self.isolation != "container_vm"):
+        raise ValidationError("Parent field can only be set on Container-vm instances")
+
+    if (self.slice.creator != self.creator):
+        from core.models.sliceprivilege import SlicePrivilege
+        # Check to make sure there's a slice_privilege for the user. If there
+        # isn't, then keystone will throw an exception inside the observer.
+        slice_privs = SlicePrivilege.objects.filter(slice=self.slice, user=self.creator)
+        if not slice_privs:
+            raise ValidationError('instance creator has no privileges on slice')
+
+# XXX smbaker - disabled for now, was causing fault in tenant view create slice
+#        if not self.controllerNetwork.test_acl(slice=self.slice):
+#            raise exceptions.ValidationError("Deployment %s's ACL does not allow any of this slice %s's users" % (self.controllerNetwork.name, self.slice.name))
+
+    super(Instance, self).save(*args, **kwds)
+
+def can_update(self, user):
+    return user.can_update_slice(self.slice)
+
+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")
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = Instance.objects.all()
+    else:
+        slices = Slice.select_by_user(user)
+        qs = Instance.objects.filter(slice__in=slices)
+    return qs
+
+def get_cpu_stats(self):
+    filter = 'instance_id=%s'%self.instance_id
+    return monitor.get_meter('cpu',filter,None)
+
+def get_bw_stats(self):
+    filter = 'instance_id=%s'%self.instance_id
+    return monitor.get_meter('network.outgoing.bytes',filter,None)
+
+def get_node_stats(self):
+    # Note sure what should go back here
+    return 1
+
+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 SlicePrivilege
+    slice_memberships = SlicePrivilege.objects.filter(slice=self.slice)
+    pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.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/invoice_model.py b/xos/core/models/attic/invoice_model.py
new file mode 100644
index 0000000..73dea2c
--- /dev/null
+++ b/xos/core/models/attic/invoice_model.py
@@ -0,0 +1,5 @@
+@property
+def amount(self):
+    return str(self.charges.all().aggregate(Sum('amount'))["amount__sum"])
+
+def __unicode__(self):  return u'%s-%s' % (self.account.site.name, str(self.date))
diff --git a/xos/core/models/attic/loadable_top.py b/xos/core/models/attic/loadable_top.py
new file mode 100644
index 0000000..474a085
--- /dev/null
+++ b/xos/core/models/attic/loadable_top.py
@@ -0,0 +1 @@
+from service_header import *
diff --git a/xos/core/models/attic/loadablemodule_model.py b/xos/core/models/attic/loadablemodule_model.py
new file mode 100644
index 0000000..53d4404
--- /dev/null
+++ b/xos/core/models/attic/loadablemodule_model.py
@@ -0,0 +1,74 @@
+def __unicode__(self): return u'%s' % (self.name)
+
+def save(self, *args, **kwargs):
+   super(LoadableModule, self).save(*args, **kwargs)
+
+   # This is necessary, as the XOS syncstep handles rerunning the docker-
+   # compose.
+   # TODO: Update synchronizer and replace with watcher functionality
+   if self.xos:
+       # force XOS to rebuild
+       self.xos.save(update_fields=["updated"])
+
+def get_provides_list(self):
+    prov_list = []
+    if self.provides and self.provides.strip():
+        for prov in self.provides.split(","):
+            prov=prov.strip()
+            if "=" in prov:
+                (name, version) = prov.split("=",1)
+                name = name.strip()
+                version = version.strip()
+            else:
+                name = prov
+                version = "1.0.0"
+            prov_list.append( {"name": name, "version": version} )
+
+    # every controller provides itself
+    prov_list.append( {"name": self.name, "version": self.version} )
+
+    return prov_list
+
+
+@classmethod
+def dependency_check(cls, dep_list):
+    missing = []
+    satisfied = []
+    operators = {">=": operator.ge,
+                 "<=": operator.le,
+                 ">": operator.gt,
+                 "<": operator.lt,
+                 "!=": operator.ne,
+                 "=": operator.eq}
+    from core.models.servicecontroller import ServiceController
+
+    for dep in dep_list:
+        dep = dep.strip()
+        name = dep
+        version = None
+        this_op = None
+        for op in operators.keys():
+            if op in dep:
+                (name, version) = dep.split(op,1)
+                name = name.strip()
+                version = version.strip()
+                this_op = operators[op]
+                break
+        found=False
+        scs = ServiceController.objects.all()
+        for sc in scs:
+            for provide in sc.get_provides_list():
+                if (provide["name"] != name):
+                    continue
+                if not this_op:
+                    satisfied.append(sc)
+                    found=True
+                    break
+                elif this_op(LooseVersion(provide["version"]), LooseVersion(version)):
+                    satisfied.append(sc)
+                    found=True
+                    break
+        if not found:
+            missing.append(dep)
+
+    return (satisfied, missing)
diff --git a/xos/core/models/attic/loadablemoduleresource_model.py b/xos/core/models/attic/loadablemoduleresource_model.py
new file mode 100644
index 0000000..d248f33
--- /dev/null
+++ b/xos/core/models/attic/loadablemoduleresource_model.py
@@ -0,0 +1,8 @@
+def __unicode__(self): return u'%s' % (self.name)
+
+@property
+def full_url(self):
+    if self.loadable_module and self.loadable_module.base_url:
+        return urlparse.urljoin(self.loadable_module.base_url, self.url)
+    else:
+        return self.url
diff --git a/xos/core/models/attic/network_header.py b/xos/core/models/attic/network_header.py
new file mode 100644
index 0000000..e7c67bf
--- /dev/null
+++ b/xos/core/models/attic/network_header.py
@@ -0,0 +1,3 @@
+# If true, then IP addresses will be allocated by the model. If false, then
+# we will assume the observer handles it.
+NO_OBSERVER=False
diff --git a/xos/core/models/attic/network_model.py b/xos/core/models/attic/network_model.py
new file mode 100644
index 0000000..b3a3493
--- /dev/null
+++ b/xos/core/models/attic/network_model.py
@@ -0,0 +1,34 @@
+def __unicode__(self):  return u'%s' % (self.name)
+
+def save(self, *args, **kwds):
+    if (not self.subnet) and (NO_OBSERVER):
+        from util.network_subnet_allocator import find_unused_subnet
+        self.subnet = find_unused_subnet(existing_subnets=[x.subnet for x in Network.objects.all()])
+        print "DEF_MOD_NET_IP", self.start_ip
+    super(Network, self).save(*args, **kwds)
+
+def can_update(self, user):
+    return user.can_update_slice(self.owner)
+
+@property
+def nat_list(self):
+    return ParseNatList(self.ports)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = Network.objects.all()
+    else:
+        slices = Slice.select_by_user(user)
+        #slice_ids = [s.id for s in Slice.select_by_user(user)]
+        qs = Network.objects.filter(owner__in=slices)
+    return qs
+
+def get_parameters(self):
+    # returns parameters from the template, updated by self.
+    p={}
+    if self.template:
+        p = self.template.get_parameters()
+    p.update(ParameterMixin.get_parameters(self))
+    return p
+
diff --git a/xos/core/models/attic/network_top.py b/xos/core/models/attic/network_top.py
new file mode 100644
index 0000000..47ae705
--- /dev/null
+++ b/xos/core/models/attic/network_top.py
@@ -0,0 +1,34 @@
+
+from core.models.networkparameter import NetworkParameter
+
+class ParameterMixin(object):
+    # helper classes for dealing with NetworkParameter
+
+    def get_parameters(self):
+        parameter_dict = {}
+
+        instance_type = ContentType.objects.get_for_model(self)
+        for param in NetworkParameter.objects.filter(content_type__pk=instance_type.id, object_id=self.id):
+            parameter_dict[param.parameter.name] = param.value
+
+        return parameter_dict
+
+    def set_parameter(self, name, value):
+        instance_type = ContentType.objects.get_for_model(self)
+        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
+        if existing_params:
+            p=existing_params[0]
+            p.value = value
+            p.save()
+        else:
+            from core.models.networkparametertype import NetworkParameterType
+            pt = NetworkParameterType.objects.get(name=name)
+            p = NetworkParameter(parameter=pt, content_type=instance_type, object_id=self.id, value=value)
+            p.save()
+
+    def unset_parameter(self, name):
+        instance_type = ContentType.objects.get_for_model(self)
+        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
+        for p in existing_params:
+            p.delete()
+
diff --git a/xos/core/models/attic/networkparameter_model.py b/xos/core/models/attic/networkparameter_model.py
new file mode 100644
index 0000000..2bbfbe7
--- /dev/null
+++ b/xos/core/models/attic/networkparameter_model.py
@@ -0,0 +1,5 @@
+content_object = GenericForeignKey('content_type', 'object_id')
+
+def __unicode__(self):
+    return self.parameter.name
+
diff --git a/xos/core/models/attic/networkparametertype_model.py b/xos/core/models/attic/networkparametertype_model.py
new file mode 100644
index 0000000..3fd623e
--- /dev/null
+++ b/xos/core/models/attic/networkparametertype_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/attic/networkslice_model.py b/xos/core/models/attic/networkslice_model.py
new file mode 100644
index 0000000..9c038fa
--- /dev/null
+++ b/xos/core/models/attic/networkslice_model.py
@@ -0,0 +1,29 @@
+class Meta:
+    unique_together = ('network', 'slice')
+
+def save(self, *args, **kwds):
+    slice = self.slice
+    if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
+        # to add a instance to the network, then one of the following must be true:
+        #   1) instance's slice is in network's permittedSlices list,
+        #   2) instance's slice is network's owner, or
+        #   3) network's permitAllSlices is true
+        raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
+
+    super(NetworkSlice, self).save(*args, **kwds)
+
+def __unicode__(self):  return u'%s-%s' % (self.network.name, self.slice.name)
+
+def can_update(self, user):
+    return user.can_update_slice(self.slice)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = NetworkSlice.objects.all()
+    else:
+        slice_ids = [s.id for s in Slice.select_by_user(user)]
+        network_ids = [network.id for network in Network.select_by_user(user)]
+        qs = NetworkSlice.objects.filter(Q(slice__in=slice_ids) | Q(network__in=network_ids))
+    return qs
+
diff --git a/xos/core/models/attic/networktemplate_model.py b/xos/core/models/attic/networktemplate_model.py
new file mode 100644
index 0000000..2bd4db3
--- /dev/null
+++ b/xos/core/models/attic/networktemplate_model.py
@@ -0,0 +1,21 @@
+ACCESS_CHOICES = ((None, 'None'), ('indirect', 'Indirect'), ('direct', 'Direct'))
+
+def __init__(self, *args, **kwargs):
+    super(NetworkTemplate, self).__init__(*args, **kwargs)
+
+    if (self.topology_kind=="BigSwitch"):
+        print >> sys.stderr, "XXX warning: topology_kind invalid case"
+        self.topology_kind="bigswitch"
+    elif (self.topology_kind=="Physical"):
+        print >> sys.stderr, "XXX warning: topology_kind invalid case"
+        self.topology_kind="physical"
+    elif (self.topology_kind=="Custom"):
+        print >> sys.stderr, "XXX warning: topology_kind invalid case"
+        self.toplogy_kind="custom"
+
+def save(self, *args, **kwargs):
+    self.enforce_choices(self.access, self.ACCESS_CHOICES)
+    super(NetworkTemplate, self).save(*args, **kwargs)
+
+def __unicode__(self):  return u'%s' % (self.name)
+
diff --git a/xos/core/models/attic/node_model.py b/xos/core/models/attic/node_model.py
new file mode 100644
index 0000000..71b769c
--- /dev/null
+++ b/xos/core/models/attic/node_model.py
@@ -0,0 +1,14 @@
+def __unicode__(self):  return u'%s' % (self.name)
+
+def __init__(self, *args, **kwargs):
+    super(Node, self).__init__(*args, **kwargs)
+    self.no_sync=True
+
+def can_update(self, user):
+    return user.can_update_site(self.site, allow=['tech'])
+
+def save(self, *args, **kwds):
+    if self.site is None and self.site_deployment is not None:
+        self.site = self.site_deployment.site
+
+    super(Node, self).save(*args, **kwds)
diff --git a/xos/core/models/attic/nodelabel_model.py b/xos/core/models/attic/nodelabel_model.py
new file mode 100644
index 0000000..087b164
--- /dev/null
+++ b/xos/core/models/attic/nodelabel_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s' % (self.name)
diff --git a/xos/core/models/attic/payment_model.py b/xos/core/models/attic/payment_model.py
new file mode 100644
index 0000000..75d7580
--- /dev/null
+++ b/xos/core/models/attic/payment_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s-%0.2f-%s' % (self.account.site.name, self.amount, str(self.date))
diff --git a/xos/core/models/attic/port_model.py b/xos/core/models/attic/port_model.py
new file mode 100644
index 0000000..3b3ffcc
--- /dev/null
+++ b/xos/core/models/attic/port_model.py
@@ -0,0 +1,48 @@
+class Meta:
+    unique_together = ('network', 'instance')
+
+def save(self, *args, **kwds):
+    if self.instance:
+        slice = self.instance.slice
+        if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
+            # to add a instance to the network, then one of the following must be true:
+            #   1) instance's slice is in network's permittedSlices list,
+            #   2) instance's slice is network's owner, or
+            #   3) network's permitAllSlices is true
+            raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
+
+    super(Port, self).save(*args, **kwds)
+
+def __unicode__(self):
+    if self.instance:
+        return u'%s-%s' % (self.network.name, self.instance.instance_name)
+    else:
+        return u'%s-unboundport-%s' % (self.network.name, self.id)
+
+def can_update(self, user):
+    if self.instance:
+        return user.can_update_slice(self.instance.slice)
+    if self.network:
+        return user.can_update_slice(self.network.owner)
+    return False
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = Port.objects.all()
+    else:
+        instances = Instance.select_by_user(user)
+        instance_ids = [instance.id for instance in instances]
+        networks = Network.select_by_user(user)
+        network_ids = [network.id for network in networks]
+        qs = Port.objects.filter(Q(instance__in=instance_ids) | Q(network__in=network_ids))
+    return qs
+
+def get_parameters(self):
+    # returns parameters from the network, updated by self.
+    p={}
+    if self.network:
+        p = self.network.get_parameters()
+    p.update(ParameterMixin.get_parameters(self))
+    return p
+
diff --git a/xos/core/models/attic/port_top.py b/xos/core/models/attic/port_top.py
new file mode 100644
index 0000000..67dabf0
--- /dev/null
+++ b/xos/core/models/attic/port_top.py
@@ -0,0 +1,34 @@
+
+from core.models.networkparameter import NetworkParameter
+from core.models.networkparametertype import NetworkParameterType
+
+class ParameterMixin(object):
+    # helper classes for dealing with NetworkParameter
+
+    def get_parameters(self):
+        parameter_dict = {}
+
+        instance_type = ContentType.objects.get_for_model(self)
+        for param in NetworkParameter.objects.filter(content_type__pk=instance_type.id, object_id=self.id):
+            parameter_dict[param.parameter.name] = param.value
+
+        return parameter_dict
+
+    def set_parameter(self, name, value):
+        instance_type = ContentType.objects.get_for_model(self)
+        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
+        if existing_params:
+            p=existing_params[0]
+            p.value = value
+            p.save()
+        else:
+            pt = NetworkParameterType.objects.get(name=name)
+            p = NetworkParameter(parameter=pt, content_type=instance_type, object_id=self.id, value=value)
+            p.save()
+
+    def unset_parameter(self, name):
+        instance_type = ContentType.objects.get_for_model(self)
+        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
+        for p in existing_params:
+            p.delete()
+
diff --git a/xos/core/models/attic/program_model.py b/xos/core/models/attic/program_model.py
new file mode 100644
index 0000000..dc7ccdf
--- /dev/null
+++ b/xos/core/models/attic/program_model.py
@@ -0,0 +1,18 @@
+@classmethod
+def select_by_user(cls, user):
+    return cls.objects.all()
+
+def __unicode__(self): return u'%s' % (self.name)
+
+def can_update(self, user):
+    return True
+
+def save(self, *args, **kwargs):
+    # set creator on first save
+    if not self.owner and hasattr(self, 'caller'):
+        self.owner = self.caller
+
+    if (self.command in ["run", "destroy"]) and (self.status in ["complete", "exception"]):
+        self.status = "queued"
+
+    super(Program, self).save(*args, **kwargs)
diff --git a/xos/core/models/attic/project_model.py b/xos/core/models/attic/project_model.py
new file mode 100644
index 0000000..3fd623e
--- /dev/null
+++ b/xos/core/models/attic/project_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/attic/reservation_model.py b/xos/core/models/attic/reservation_model.py
new file mode 100644
index 0000000..e5e9cda
--- /dev/null
+++ b/xos/core/models/attic/reservation_model.py
@@ -0,0 +1,17 @@
+def __unicode__(self):  return u'%s to %s' % (self.startTime, self.endTime)
+
+@property
+def endTime(self):
+    return self.startTime + datetime.timedelta(hours=self.duration)
+
+def can_update(self, user):
+    return user.can_update_slice(self.slice)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = Reservation.objects.all()
+    else:
+        slice_ids = [s.id for s in Slice.select_by_user(user)]
+        qs = Reservation.objects.filter(id__in=slice_ids)
+    return qs
diff --git a/xos/core/models/attic/reservedresource_model.py b/xos/core/models/attic/reservedresource_model.py
new file mode 100644
index 0000000..d2caa15
--- /dev/null
+++ b/xos/core/models/attic/reservedresource_model.py
@@ -0,0 +1,16 @@
+class Meta(PlCoreBase.Meta):
+   verbose_name_plural = "Reserved Resources"
+
+def __unicode__(self):  return u'%d %s on %s' % (self.quantity, self.resource, self.instance)
+
+def can_update(self, user):
+    return user.can_update(self.instance.slice)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = ReservedResource.objects.all()
+    else:
+        instance_ids = [s.id for s in Instance.select_by_user(user)]
+        qs = ReservedResource.objects.filter(id__in=instance_ids)
+    return qs
diff --git a/xos/core/models/attic/role_model.py b/xos/core/models/attic/role_model.py
new file mode 100644
index 0000000..81bd86e
--- /dev/null
+++ b/xos/core/models/attic/role_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s:%s' % (self.content_type,self.role_type)
diff --git a/xos/core/models/attic/router_model.py b/xos/core/models/attic/router_model.py
new file mode 100644
index 0000000..a538cd4
--- /dev/null
+++ b/xos/core/models/attic/router_model.py
@@ -0,0 +1,5 @@
+def __unicode__(self):  return u'%s' % (self.name)
+
+def can_update(self, user):
+    return user.can_update_slice(self.owner)
+
diff --git a/xos/core/models/attic/service_header.py b/xos/core/models/attic/service_header.py
new file mode 100644
index 0000000..04e7353
--- /dev/null
+++ b/xos/core/models/attic/service_header.py
@@ -0,0 +1,187 @@
+from __future__ import absolute_import
+
+from core.models.xos import XOS
+from core.models.plcorebase 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 *
+from xos.config import Config
+
+
+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
new file mode 100644
index 0000000..1798018
--- /dev/null
+++ b/xos/core/models/attic/service_model.py
@@ -0,0 +1,196 @@
+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)
+
+@classmethod
+def get_service_objects(cls):
+    return cls.objects.filter(kind=cls.KIND)
+
+@classmethod
+def get_deleted_service_objects(cls):
+    return cls.deleted_objects.filter(kind=cls.KIND)
+
+@classmethod
+def get_service_objects_by_user(cls, user):
+    return cls.select_by_user(user).filter(kind=cls.KIND)
+
+@classmethod
+def select_by_user(cls, user):
+    if user.is_admin:
+        return cls.objects.all()
+    else:
+        from core.models.serviceprivilege import ServicePrivilege
+        service_ids = [
+            sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
+        return cls.objects.filter(id__in=service_ids)
+
+@property
+def serviceattribute_dict(self):
+    attrs = {}
+    for attr in self.serviceattributes.all():
+        attrs[attr.name] = attr.value
+    return attrs
+
+def __unicode__(self): return u'%s' % (self.name)
+
+def can_update(self, user):
+    return user.can_update_service(self, allow=['admin'])
+
+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/serviceclass_model.py b/xos/core/models/attic/serviceclass_model.py
new file mode 100644
index 0000000..ebdfdfe
--- /dev/null
+++ b/xos/core/models/attic/serviceclass_model.py
@@ -0,0 +1,8 @@
+class Meta(PlCoreBase.Meta):
+   verbose_name_plural = "Service classes"
+
+def __unicode__(self):  return u'%s' % (self.name)
+
+def save_by_user(self, user, *args, **kwds):
+    if self.can_update(user):
+        super(ServiceClass, self).save(*args, **kwds)
diff --git a/xos/core/models/attic/servicedependency_model.py b/xos/core/models/attic/servicedependency_model.py
new file mode 100644
index 0000000..dce32e7
--- /dev/null
+++ b/xos/core/models/attic/servicedependency_model.py
@@ -0,0 +1,11 @@
+KIND="coarse"
+
+def save(self, *args, **kwargs):
+    if (not self.subscriber_service):
+        raise XOSValidationError("subscriber_service cannot be null")
+    if (self.subscriber_tenant or self.subscriber_user):
+        raise XOSValidationError(
+            "subscriber_tenant and subscriber_user must be null")
+
+    super(ServiceDependency, self).save()
+
diff --git a/xos/core/models/attic/serviceprivilege_model.py b/xos/core/models/attic/serviceprivilege_model.py
new file mode 100644
index 0000000..64ca985
--- /dev/null
+++ b/xos/core/models/attic/serviceprivilege_model.py
@@ -0,0 +1,28 @@
+class Meta:
+    unique_together = ('user', 'service', 'role')
+
+def __unicode__(self): return u'%s %s %s' % (
+    self.service, self.user, self.role)
+
+def can_update(self, user):
+    if not self.service.enabled:
+        raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
+    return self.service.can_update(user)
+
+def save(self, *args, **kwds):
+    if not self.service.enabled:
+        raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
+    super(ServicePrivilege, self).save(*args, **kwds)
+
+def delete(self, *args, **kwds):
+    if not self.service.enabled:
+        raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
+    super(ServicePrivilege, self).delete(*args, **kwds)
+
+@classmethod
+def select_by_user(cls, user):
+    if user.is_admin:
+        qs = cls.objects.all()
+    else:
+        qs = cls.objects.filter(user=user)
+    return qs
diff --git a/xos/core/models/attic/serviceresource_model.py b/xos/core/models/attic/serviceresource_model.py
new file mode 100644
index 0000000..3fd623e
--- /dev/null
+++ b/xos/core/models/attic/serviceresource_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/attic/servicerole_model.py b/xos/core/models/attic/servicerole_model.py
new file mode 100644
index 0000000..b35a54b
--- /dev/null
+++ b/xos/core/models/attic/servicerole_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s' % (self.role)
diff --git a/xos/core/models/attic/singletonmodel.py b/xos/core/models/attic/singletonmodel.py
new file mode 100644
index 0000000..4ab6f6e
--- /dev/null
+++ b/xos/core/models/attic/singletonmodel.py
@@ -0,0 +1,16 @@
+from django.db import models
+
+class SingletonModel(models.Model):
+    class Meta:
+        abstract = True
+ 
+    def save(self, *args, **kwargs):
+        self.__class__.objects.exclude(id=self.id).delete()
+        super(SingletonModel, self).save(*args, **kwargs)
+ 
+    @classmethod
+    def load(cls):
+        try:
+            return cls.objects.get()
+        except cls.DoesNotExist:
+            return cls()
diff --git a/xos/core/models/attic/site_model.py b/xos/core/models/attic/site_model.py
new file mode 100644
index 0000000..a256049
--- /dev/null
+++ b/xos/core/models/attic/site_model.py
@@ -0,0 +1,9 @@
+objects = ControllerManager()
+deleted_objects = ControllerDeletionManager()
+
+def __unicode__(self):  return u'%s' % (self.name)
+
+def can_update(self, user):
+    return user.can_update_site(self, allow=['pi'])
+
+
diff --git a/xos/core/models/attic/sitecredential_model.py b/xos/core/models/attic/sitecredential_model.py
new file mode 100644
index 0000000..df937ad
--- /dev/null
+++ b/xos/core/models/attic/sitecredential_model.py
@@ -0,0 +1,4 @@
+def __unicode__(self):
+    return self.name
+
+
diff --git a/xos/core/models/attic/sitedeployment_model.py b/xos/core/models/attic/sitedeployment_model.py
new file mode 100644
index 0000000..a4383f3
--- /dev/null
+++ b/xos/core/models/attic/sitedeployment_model.py
@@ -0,0 +1,7 @@
+objects = ControllerLinkManager()
+deleted_objects = ControllerLinkDeletionManager()
+
+class Meta:
+    unique_together = ('site', 'deployment', 'controller')
+
+def __unicode__(self):  return u'%s %s' % (self.deployment, self.site)
diff --git a/xos/core/models/attic/siteprivilege_model.py b/xos/core/models/attic/siteprivilege_model.py
new file mode 100644
index 0000000..fde823f
--- /dev/null
+++ b/xos/core/models/attic/siteprivilege_model.py
@@ -0,0 +1,18 @@
+def __unicode__(self):  return u'%s %s %s' % (self.site, self.user, self.role)
+
+def can_update(self, user):
+    return user.can_update_site(self, allow=['pi'])
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = SitePrivilege.objects.all()
+    else:
+        sp_ids = [sp.id for sp in SitePrivilege.objects.filter(user=user)]
+        qs = SitePrivilege.objects.filter(id__in=sp_ids)
+    return qs
+
+def save(self, *args, **kwds):
+    if not self.user.is_active:
+        raise PermissionDenied, "Cannot modify role(s) of a disabled user"
+    super(SitePrivilege, self).save(*args, **kwds)
diff --git a/xos/core/models/attic/siterole_model.py b/xos/core/models/attic/siterole_model.py
new file mode 100644
index 0000000..c5ebd53
--- /dev/null
+++ b/xos/core/models/attic/siterole_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s' % (self.role)
diff --git a/xos/core/models/attic/slice_model.py b/xos/core/models/attic/slice_model.py
new file mode 100644
index 0000000..b9aba1b
--- /dev/null
+++ b/xos/core/models/attic/slice_model.py
@@ -0,0 +1,81 @@
+NETWORK_CHOICES = ((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))
+
+def __unicode__(self):  return u'%s' % (self.name)
+
+@property
+def slicename(self):
+    return "%s_%s" % (self.site.login_base, self.name)
+
+def save(self, *args, **kwds):
+    site = Site.objects.get(id=self.site.id)
+    # allow preexisting slices to keep their original name for now
+    if not self.id and not self.name.startswith(site.login_base):
+        raise XOSValidationError('slice name must begin with %s' % site.login_base)
+
+    if self.name == site.login_base+"_":
+        raise XOSValidationError('slice name is too short')
+
+    if " " in self.name:
+        raise XOSValidationError('slice name must not contain spaces')
+
+    # 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")
+    
+    if not self.creator:
+        raise XOSValidationError('slice has no 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, **kwds)
+
+def can_update(self, user):
+    return user.can_update_slice(self)
+
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = Slice.objects.all()
+    else:
+        from core.models.sliceprivilege import SlicePrivilege 
+        from core.models.siteprivilege import SitePrivilege
+        # users can see slices they belong to 
+        slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user)]
+        # pis and admins can see slices at their sites
+        sites = [sp.site for sp in SitePrivilege.objects.filter(user=user)\
+                    if (sp.role.role == 'pi') or (sp.role.role == 'admin')]
+        slice_ids.extend([s.id for s in Slice.objects.filter(site__in=sites)])
+        qs = Slice.objects.filter(id__in=slice_ids)
+    return qs
+
+"""
+def delete(self, *args, **kwds):
+    # delete networks associated with this slice
+    from core.models.network import Network
+    nets = Network.objects.filter(slices=self)
+    nets.delete() 
+    # delete slice controllers
+    slice_controllers = ControllerSlice.objects.filter(slice=self)
+    slice_controllers.delete()
+    # delete slice privilege
+    slice_privileges = SlicePrivilege.objects.filter(slice=self)
+    slice_privileges.delete() 
+    # continue with normal delete
+    super(Slice, self).delete(*args, **kwds) 
+"""
+
diff --git a/xos/core/models/attic/slicecredential_model.py b/xos/core/models/attic/slicecredential_model.py
new file mode 100644
index 0000000..f876fd6
--- /dev/null
+++ b/xos/core/models/attic/slicecredential_model.py
@@ -0,0 +1,3 @@
+def __unicode__(self):
+    return self.name
+
diff --git a/xos/core/models/attic/sliceprivilege_model.py b/xos/core/models/attic/sliceprivilege_model.py
new file mode 100644
index 0000000..c7e0bfb
--- /dev/null
+++ b/xos/core/models/attic/sliceprivilege_model.py
@@ -0,0 +1,31 @@
+class Meta:
+    unique_together = ('user', 'slice', 'role')
+
+def __unicode__(self):  return u'%s %s %s' % (self.slice, self.user, self.role)
+
+def save(self, *args, **kwds):
+    super(SlicePrivilege, self).save(*args, **kwds)
+
+def can_update(self, user):
+    return user.can_update_slice(self.slice)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = SlicePrivilege.objects.all()
+    else:
+        # You can see your own SlicePrivileges
+        sp_ids = [sp.id for sp in SlicePrivilege.objects.filter(user=user)]
+
+        from core.models.siteprivilege import SitePrivilege
+        # A site pi or site admin can see the SlicePrivileges for all slices in his Site
+        for priv in SitePrivilege.objects.filter(user=user):
+            if priv.role.role in ['pi', 'admin']:
+                sp_ids.extend( [sp.id for sp in SlicePrivilege.objects.filter(slice__site = priv.site)] )
+
+        # A slice admin can see the SlicePrivileges for his Slice
+        for priv in SlicePrivilege.objects.filter(user=user, role__role="admin"):
+            sp_ids.extend( [sp.id for sp in SlicePrivilege.objects.filter(slice=priv.slice)] )
+
+        qs = SlicePrivilege.objects.filter(id__in=sp_ids)
+    return qs
diff --git a/xos/core/models/attic/slicerole_model.py b/xos/core/models/attic/slicerole_model.py
new file mode 100644
index 0000000..c5ebd53
--- /dev/null
+++ b/xos/core/models/attic/slicerole_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s' % (self.role)
diff --git a/xos/core/models/attic/slicetag_model.py b/xos/core/models/attic/slicetag_model.py
new file mode 100644
index 0000000..87f00e4
--- /dev/null
+++ b/xos/core/models/attic/slicetag_model.py
@@ -0,0 +1,11 @@
+def can_update(self, user):
+    return user.can_update_slice(self.slice)
+
+@staticmethod
+def select_by_user(user):
+    if user.is_admin:
+        qs = SliceTag.objects.all()
+    else:
+        slices = Slice.select_by_user(user)
+        qs = SliceTag.objects.filter(slice__in=slices)
+    return qs
diff --git a/xos/core/models/attic/tag_model.py b/xos/core/models/attic/tag_model.py
new file mode 100644
index 0000000..c6437e8
--- /dev/null
+++ b/xos/core/models/attic/tag_model.py
@@ -0,0 +1,15 @@
+content_object = GenericForeignKey('content_type', 'object_id') # Not generated by xproto
+
+def __unicode__(self):
+    return self.name
+
+def can_update(self, user):
+    return user.can_update_root()
+
+@classmethod
+def select_by_content_object(cls, obj):
+    return cls.objects.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id)
+
+@staticmethod
+def select_by_user(user):
+    return Tag.objects.all()
diff --git a/xos/core/models/attic/tenant_model.py b/xos/core/models/attic/tenant_model.py
new file mode 100644
index 0000000..6a2e074
--- /dev/null
+++ b/xos/core/models/attic/tenant_model.py
@@ -0,0 +1,62 @@
+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)
+
+def __unicode__(self):
+    return u"%s-tenant-%s" % (str(self.kind), str(self.id))
+
+@classmethod
+def get_tenant_objects(cls):
+    return cls.objects.filter(kind=cls.KIND)
+
+@classmethod
+def get_tenant_objects_by_user(cls, user):
+    return cls.select_by_user(user).filter(kind=cls.KIND)
+
+@classmethod
+def get_deleted_tenant_objects(cls):
+    return cls.deleted_objects.filter(kind=cls.KIND)
+
+@property
+def tenantattribute_dict(self):
+    attrs = {}
+    for attr in self.tenantattributes.all():
+        attrs[attr.name] = attr.value
+    return attrs
+
+# helper function to be used in subclasses that want to ensure
+# service_specific_id is unique
+def validate_unique_service_specific_id(self):
+    if self.pk is None:
+        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.get_tenant_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 save(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")
+
+    super(Tenant, self).save(*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]
+
diff --git a/xos/core/models/attic/tenant_top.py b/xos/core/models/attic/tenant_top.py
new file mode 100644
index 0000000..474a085
--- /dev/null
+++ b/xos/core/models/attic/tenant_top.py
@@ -0,0 +1 @@
+from service_header import *
diff --git a/xos/core/models/attic/tenantattribute_model.py b/xos/core/models/attic/tenantattribute_model.py
new file mode 100644
index 0000000..ec771ed
--- /dev/null
+++ b/xos/core/models/attic/tenantattribute_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s-%s' % (self.name, self.id)
diff --git a/xos/core/models/attic/tenantprivilege_model.py b/xos/core/models/attic/tenantprivilege_model.py
new file mode 100644
index 0000000..165eb89
--- /dev/null
+++ b/xos/core/models/attic/tenantprivilege_model.py
@@ -0,0 +1,25 @@
+def __unicode__(self): return u'%s %s %s' % (
+    self.tenant, self.user, self.role)
+
+def save(self, *args, **kwds):
+    if not self.user.is_active:
+        raise PermissionDenied, "Cannot modify role(s) of a disabled user"
+    super(TenantPrivilege, self).save(*args, **kwds)
+
+def can_update(self, user):
+    return user.can_update_tenant_privilege(self)
+
+@classmethod
+def select_by_user(cls, user):
+    if user.is_admin:
+        return cls.objects.all()
+    else:
+        # User can see his own privilege
+        trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
+
+        # A tenant admin can see the TenantPrivileges for their Tenants
+        for priv in cls.objects.filter(user=user, role__role="admin"):
+            trp_ids.extend(
+                [trp.id for trp in cls.objects.filter(tenant=priv.tenant)])
+
+        return cls.objects.filter(id__in=trp_ids)
diff --git a/xos/core/models/attic/tenantrole_model.py b/xos/core/models/attic/tenantrole_model.py
new file mode 100644
index 0000000..b35a54b
--- /dev/null
+++ b/xos/core/models/attic/tenantrole_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s' % (self.role)
diff --git a/xos/core/models/attic/tenantroot_model.py b/xos/core/models/attic/tenantroot_model.py
new file mode 100644
index 0000000..b9501af
--- /dev/null
+++ b/xos/core/models/attic/tenantroot_model.py
@@ -0,0 +1,60 @@
+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 __unicode__(self):
+    if not self.name:
+        return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
+    else:
+        return self.name
+
+def can_update(self, user):
+    return user.can_update_tenant_root(self, allow=['admin'])
+
+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]
+
+@classmethod
+def get_tenant_objects(cls):
+    return cls.objects.filter(kind=cls.KIND)
+
+@classmethod
+def get_tenant_objects_by_user(cls, user):
+    return cls.select_by_user(user).filter(kind=cls.KIND)
+
+@classmethod
+def select_by_user(cls, user):
+    if user.is_admin:
+        return cls.objects.all()
+    else:
+        from core.models.tenantrootprivilege import TenantRootPrivilege
+        tr_ids = [
+            trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
+        return cls.objects.filter(id__in=tr_ids)
+
+# 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.get_tenant_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/tenantrootprivilege_model.py b/xos/core/models/attic/tenantrootprivilege_model.py
new file mode 100644
index 0000000..158f1c0
--- /dev/null
+++ b/xos/core/models/attic/tenantrootprivilege_model.py
@@ -0,0 +1,29 @@
+class Meta:
+    unique_together = ('user', 'tenant_root', 'role')
+
+def __unicode__(self): return u'%s %s %s' % (
+    self.tenant_root, self.user, self.role)
+
+def save(self, *args, **kwds):
+    if not self.user.is_active:
+        raise PermissionDenied, "Cannot modify role(s) of a disabled user"
+    super(TenantRootPrivilege, self).save(*args, **kwds)
+
+def can_update(self, user):
+    return user.can_update_tenant_root_privilege(self)
+
+@classmethod
+def select_by_user(cls, user):
+    if user.is_admin:
+        return cls.objects.all()
+    else:
+        # User can see his own privilege
+        trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
+
+        # A slice admin can see the SlicePrivileges for his Slice
+        for priv in cls.objects.filter(user=user, role__role="admin"):
+            trp_ids.extend(
+                [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)])
+
+        return cls.objects.filter(id__in=trp_ids)
+
diff --git a/xos/core/models/attic/tenantrootrole_model.py b/xos/core/models/attic/tenantrootrole_model.py
new file mode 100644
index 0000000..b35a54b
--- /dev/null
+++ b/xos/core/models/attic/tenantrootrole_model.py
@@ -0,0 +1 @@
+def __unicode__(self): return u'%s' % (self.role)
diff --git a/xos/core/models/attic/tenantwithcontainer_model.py b/xos/core/models/attic/tenantwithcontainer_model.py
new file mode 100644
index 0000000..7b11323
--- /dev/null
+++ b/xos/core/models/attic/tenantwithcontainer_model.py
@@ -0,0 +1,141 @@
+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.get_tenant_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/attic/usableobject_model.py b/xos/core/models/attic/usableobject_model.py
new file mode 100644
index 0000000..3fd623e
--- /dev/null
+++ b/xos/core/models/attic/usableobject_model.py
@@ -0,0 +1 @@
+def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/attic/usercredential_model.py b/xos/core/models/attic/usercredential_model.py
new file mode 100644
index 0000000..e6feca2
--- /dev/null
+++ b/xos/core/models/attic/usercredential_model.py
@@ -0,0 +1,3 @@
+
+def __unicode__(self):
+    return self.name
diff --git a/xos/core/models/attic/xos_model.py b/xos/core/models/attic/xos_model.py
new file mode 100644
index 0000000..12b5979
--- /dev/null
+++ b/xos/core/models/attic/xos_model.py
@@ -0,0 +1,22 @@
+def __unicode__(self):  return u'%s' % (self.name)
+
+def __init__(self, *args, **kwargs):
+    super(XOS, self).__init__(*args, **kwargs)
+
+def save(self, *args, **kwds):
+    super(XOS, self).save(*args, **kwds)
+
+#    def can_update(self, user):
+#        return user.can_update_site(self.site, allow=['tech'])
+
+def rebuild(self, services=[]):
+    # If `services` is empty, then only rebuild the UI
+    # Otherwise, only rebuild the services listed in `services`
+    with transaction.atomic():
+        for loadable_module in self.loadable_modules.all():
+            if (services) and (loadable_module.name not in services):
+                continue
+            for lmr in loadable_module.loadable_module_resources.all():
+               lmr.save()
+            loadable_module.save()
+        self.save()
diff --git a/xos/core/models/attic/xoscomponentlink_model.py b/xos/core/models/attic/xoscomponentlink_model.py
new file mode 100644
index 0000000..413ce07
--- /dev/null
+++ b/xos/core/models/attic/xoscomponentlink_model.py
@@ -0,0 +1,7 @@
+def save(self, *args, **kwds):
+    # If this is a new object, then check to make sure it doesn't already exist
+    if not self.pk:
+        existing = XOSComponentLink.objects.filter(container=self.container, alias=self.alias)
+        if len(existing) > 0:
+            raise XOSValidationError('XOSComponentLink for %s:%s already defined' % (self.container, self.alias))
+    super(XOSComponentLink, self).save(*args, **kwds)
diff --git a/xos/core/models/attic/xoscomponentvolume_model.py b/xos/core/models/attic/xoscomponentvolume_model.py
new file mode 100644
index 0000000..a2ae229
--- /dev/null
+++ b/xos/core/models/attic/xoscomponentvolume_model.py
@@ -0,0 +1,8 @@
+def save(self, *args, **kwds):
+    # If this is a new object, then check to make sure it doesn't already exist
+    if not self.pk:
+        existing = XOSComponentVolume.objects.filter(container_path=self.container_path, host_path=self.host_path)
+        if len(existing) > 0:
+            raise XOSValidationError('XOSComponentVolume for %s:%s already defined' % (self.container_path, self.host_path))
+    super(XOSComponentVolume, self).save(*args, **kwds)
+
diff --git a/xos/core/models/attic/xoscomponentvolumecontainer_model.py b/xos/core/models/attic/xoscomponentvolumecontainer_model.py
new file mode 100644
index 0000000..fda0b03
--- /dev/null
+++ b/xos/core/models/attic/xoscomponentvolumecontainer_model.py
@@ -0,0 +1,8 @@
+def save(self, *args, **kwds):
+    # If this is a new object, then check to make sure it doesn't already exist
+    if not self.pk:
+        existing = XOSComponentVolumeContainer.objects.filter(name=self.name)
+        if len(existing) > 0:
+            raise XOSValidationError('XOSComponentVolumeContainer for %s:%s already defined' % (self.container_path, self.host_path))
+    super(XOSComponentVolumeContainer, self).save(*args, **kwds)
+
diff --git a/xos/core/models/attic/xosguiextension_model.py b/xos/core/models/attic/xosguiextension_model.py
new file mode 100644
index 0000000..dbbad60
--- /dev/null
+++ b/xos/core/models/attic/xosguiextension_model.py
@@ -0,0 +1,4 @@
+"""Persist GUI Extension"""
+class Meta:
+    app_label = "core"
+
diff --git a/xos/core/models/attic/xosvolume_model.py b/xos/core/models/attic/xosvolume_model.py
new file mode 100644
index 0000000..47c0c1b
--- /dev/null
+++ b/xos/core/models/attic/xosvolume_model.py
@@ -0,0 +1,11 @@
+def __unicode__(self): return u'%s' % (self.container_path)
+
+def save(self, *args, **kwargs):
+   super(XOSVolume, self).save(*args, **kwargs)
+
+   # This is necessary, as the XOS syncstep handles rerunning the docker-
+   # compose.
+   # TODO: Update onboarding synchronizer and replace this with watcher functionality
+   if self.xos:
+       # force XOS to rebuild
+       self.xos.save(update_fields=["updated"])
diff --git a/xos/core/models/billing.py b/xos/core/models/billing.py
deleted file mode 100644
index 083149a..0000000
--- a/xos/core/models/billing.py
+++ /dev/null
@@ -1,84 +0,0 @@
-import datetime
-import os
-import socket
-from django.db import models
-from core.models import PlCoreBase, Site, Slice, Instance, Deployment, ModelLink
-from core.models.plcorebase import StrippedCharField
-from django.contrib.contenttypes.models import ContentType
-from django.db.models import Sum
-from django.utils import timezone
-
-class Account(PlCoreBase):
-    site = models.ForeignKey(Site, related_name="accounts", help_text="Site for this account")
-
-    xos_links = [ModelLink(Site,via='site')]
-
-    @property
-    def total_invoices(self):
-        # Since the amount of an invoice is the sum of it's charges, we can
-        # compute the sum of the invoices by summing all charges where
-        # charge.invoice != Null.
-        x=self.charges.filter(invoice__isnull=False).aggregate(Sum('amount'))["amount__sum"]
-        if (x==None):
-            return 0.0
-        return x
-
-    @property
-    def total_payments(self):
-        x=self.payments.all().aggregate(Sum('amount'))["amount__sum"]
-        if (x==None):
-            return 0.0
-        return x
-
-    @property
-    def balance_due(self):
-        return self.total_invoices - self.total_payments
-
-    def __unicode__(self):  return u'%s' % (self.site.name)
-
-class Invoice(PlCoreBase):
-    date = models.DateTimeField()
-    account = models.ForeignKey(Account, related_name="invoices")
-
-    xos_links = [ModelLink(Account,via='account')]
-
-    @property
-    def amount(self):
-        return str(self.charges.all().aggregate(Sum('amount'))["amount__sum"])
-
-    def __unicode__(self):  return u'%s-%s' % (self.account.site.name, str(self.date))
-
-class UsableObject(PlCoreBase):
-    name = StrippedCharField(max_length=1024)
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-class Payment(PlCoreBase):
-    account = models.ForeignKey(Account, related_name="payments")
-    amount = models.FloatField(default=0.0)
-    date = models.DateTimeField(default=timezone.now)
-
-    xos_links = [ModelLink(Account, via='account')] 
-
-    def __unicode__(self): return u'%s-%0.2f-%s' % (self.account.site.name, self.amount, str(self.date))
-
-class Charge(PlCoreBase):
-    KIND_CHOICES = (('besteffort', 'besteffort'), ('reservation', 'reservation'), ('monthlyfee', 'monthlyfee'))
-    STATE_CHOICES = (('pending', 'pending'), ('invoiced', 'invoiced'))
-
-    account = models.ForeignKey(Account, related_name="charges")
-    slice = models.ForeignKey(Slice, related_name="charges", null=True, blank=True)
-    kind = StrippedCharField(max_length=30, choices=KIND_CHOICES, default="besteffort")
-    state = StrippedCharField(max_length=30, choices=STATE_CHOICES, default="pending")
-    date = models.DateTimeField()
-    object = models.ForeignKey(UsableObject)
-    amount = models.FloatField(default=0.0)
-    coreHours = models.FloatField(default=0.0)
-    invoice = models.ForeignKey(Invoice, blank=True, null=True, related_name="charges")
-    xos_links = [ModelLink(Account,via='account'),ModelLink(Slice,via='slice'),ModelLink(Invoice,via='invoice')]
-
-    def __unicode__(self):  return u'%s-%0.2f-%s' % (self.account.site.name, self.amount, str(self.date))
-
-
-
-
diff --git a/xos/core/models/charge.xproto b/xos/core/models/charge.xproto
new file mode 100644
index 0000000..df45134
--- /dev/null
+++ b/xos/core/models/charge.xproto
@@ -0,0 +1,13 @@
+
+
+message Charge (PlCoreBase){
+     required manytoone account->Account:charges = 1 [db_index = True, null = False, blank = False];
+     optional manytoone slice->Slice:charges = 2 [db_index = True, null = True, blank = True];
+     required string kind = 3 [default = "besteffort", choices = "(('besteffort', 'besteffort'), ('reservation', 'reservation'), ('monthlyfee', 'monthlyfee'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     required string state = 4 [default = "pending", choices = "(('pending', 'pending'), ('invoiced', 'invoiced'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     required string date = 5 [db_index = False, null = False, content_type = "date", blank = False];
+     required manytoone object->UsableObject:charge = 6 [db_index = True, null = False, blank = False];
+     required float amount = 7 [default = 0.0, null = False, db_index = False, blank = False];
+     required float coreHours = 8 [default = 0.0, null = False, db_index = False, blank = False];
+     optional manytoone invoice->Invoice:charges = 9 [db_index = True, null = True, blank = True];
+}
diff --git a/xos/core/models/contenttype.py b/xos/core/models/contenttype.py
new file mode 100644
index 0000000..62e77ea
--- /dev/null
+++ b/xos/core/models/contenttype.py
@@ -0,0 +1 @@
+from django.contrib.contenttypes.models import ContentType
diff --git a/xos/core/models/controller.xproto b/xos/core/models/controller.xproto
new file mode 100644
index 0000000..da2d1e3
--- /dev/null
+++ b/xos/core/models/controller.xproto
@@ -0,0 +1,16 @@
+
+
+message Controller (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the Controller", null = False, db_index = False];
+     required string backend_type = 2 [max_length = 200, content_type = "stripped", blank = False, help_text = "Type of compute controller, e.g. EC2, OpenStack, or OpenStack version", null = False, db_index = False];
+     required string version = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Controller version", null = False, db_index = False];
+     optional string auth_url = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "Auth url for the compute controller", null = True, db_index = False];
+     optional string admin_user = 5 [max_length = 200, content_type = "stripped", blank = True, help_text = "Username of an admin user at this controller", null = True, db_index = False];
+     optional string admin_password = 6 [max_length = 200, content_type = "stripped", blank = True, help_text = "Password of theadmin user at this controller", null = True, db_index = False];
+     optional string admin_tenant = 7 [max_length = 200, content_type = "stripped", blank = True, help_text = "Name of the tenant the admin user belongs to", null = True, db_index = False];
+     optional string domain = 8 [max_length = 200, content_type = "stripped", blank = True, help_text = "Name of the domain this controller belongs to", null = True, db_index = False];
+     optional string rabbit_host = 9 [max_length = 200, content_type = "stripped", blank = True, help_text = "IP address of rabbitmq server at this controller", null = True, db_index = False];
+     optional string rabbit_user = 10 [max_length = 200, content_type = "stripped", blank = True, help_text = "Username of rabbitmq server at this controller", null = True, db_index = False];
+     optional string rabbit_password = 11 [max_length = 200, content_type = "stripped", blank = True, help_text = "Password of rabbitmq server at this controller", null = True, db_index = False];
+     required manytoone deployment->Deployment:controllerdeployments = 12 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/controllerdashboardview.xproto b/xos/core/models/controllerdashboardview.xproto
new file mode 100644
index 0000000..ed4229c
--- /dev/null
+++ b/xos/core/models/controllerdashboardview.xproto
@@ -0,0 +1,8 @@
+
+
+message ControllerDashboardView (PlCoreBase){
+     required manytoone controller->Controller:controllerdashboardviews = 1 [db_index = True, null = False, blank = False];
+     required manytoone dashboardView->DashboardView:controllerdashboardviews = 2 [db_index = True, null = False, blank = False];
+     required bool enabled = 3 [default = True, null = False, db_index = False, blank = True];
+     required string url = 4 [max_length = 1024, content_type = "stripped", blank = False, help_text = "URL of Dashboard", null = False, db_index = False];
+}
diff --git a/xos/core/models/controllerimages.xproto b/xos/core/models/controllerimages.xproto
new file mode 100644
index 0000000..38d6237
--- /dev/null
+++ b/xos/core/models/controllerimages.xproto
@@ -0,0 +1,7 @@
+
+
+message ControllerImages (PlCoreBase){
+     required manytoone image->Image:controllerimages = 1 [db_index = True, null = False, blank = False];
+     required manytoone controller->Controller:controllerimages = 2 [db_index = True, null = False, blank = False];
+     optional string glance_image_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Glance image id", null = True, db_index = False];
+}
diff --git a/xos/core/models/controllernetwork.xproto b/xos/core/models/controllernetwork.xproto
new file mode 100644
index 0000000..ba6acec
--- /dev/null
+++ b/xos/core/models/controllernetwork.xproto
@@ -0,0 +1,14 @@
+
+
+message ControllerNetwork (PlCoreBase){
+     required manytoone network->Network:controllernetworks = 1 [db_index = True, null = False, blank = False];
+     required manytoone controller->Controller:controllernetworks = 2 [db_index = True, null = False, blank = False];
+     required string subnet = 3 [db_index = False, max_length = 32, null = False, blank = True];
+     required string start_ip = 4 [db_index = False, max_length = 32, null = False, blank = True];
+     required string stop_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+     optional string net_id = 6 [help_text = "Neutron network", max_length = 256, null = True, db_index = False, blank = True];
+     optional string router_id = 7 [help_text = "Neutron router id", max_length = 256, null = True, db_index = False, blank = True];
+     optional string subnet_id = 8 [help_text = "Neutron subnet id", max_length = 256, null = True, db_index = False, blank = True];
+     optional string gateway = 9 [db_index = False, max_length = 32, null = True, blank = True];
+     optional string segmentation_id = 10 [db_index = False, max_length = 32, null = True, blank = True];
+}
diff --git a/xos/core/models/controllerrole.xproto b/xos/core/models/controllerrole.xproto
new file mode 100644
index 0000000..4ae4bb2
--- /dev/null
+++ b/xos/core/models/controllerrole.xproto
@@ -0,0 +1,5 @@
+
+
+message ControllerRole (PlCoreBase){
+     required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/controllersite.xproto b/xos/core/models/controllersite.xproto
new file mode 100644
index 0000000..6fb4be8
--- /dev/null
+++ b/xos/core/models/controllersite.xproto
@@ -0,0 +1,7 @@
+
+
+message ControllerSite (PlCoreBase){
+     required manytoone site->Site:controllersite = 1 [db_index = True, null = False, blank = False];
+     optional manytoone controller->Controller:controllersite = 2 [db_index = True, null = True, blank = True];
+     optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = True];
+}
diff --git a/xos/core/models/controllersiteprivilege.xproto b/xos/core/models/controllersiteprivilege.xproto
new file mode 100644
index 0000000..ca5ca75
--- /dev/null
+++ b/xos/core/models/controllersiteprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message ControllerSitePrivilege (PlCoreBase){
+     required manytoone controller->Controller:controllersiteprivileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone site_privilege->SitePrivilege:controllersiteprivileges = 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];
+}
diff --git a/xos/core/models/controllerslice.xproto b/xos/core/models/controllerslice.xproto
new file mode 100644
index 0000000..46e15ae
--- /dev/null
+++ b/xos/core/models/controllerslice.xproto
@@ -0,0 +1,7 @@
+
+
+message ControllerSlice (PlCoreBase){
+     required manytoone controller->Controller:controllerslices = 1 [db_index = True, null = False, blank = False];
+     required manytoone slice->Slice:controllerslices = 2 [db_index = True, null = False, blank = False];
+     optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = False];
+}
diff --git a/xos/core/models/controllersliceprivilege.xproto b/xos/core/models/controllersliceprivilege.xproto
new file mode 100644
index 0000000..c1308dc
--- /dev/null
+++ b/xos/core/models/controllersliceprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message ControllerSlicePrivilege (PlCoreBase){
+     required manytoone controller->Controller:controllersliceprivileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone slice_privilege->SlicePrivilege:controllersliceprivileges = 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];
+}
diff --git a/xos/core/models/controlleruser.py b/xos/core/models/controlleruser.py
deleted file mode 100644
index 0e05cfa..0000000
--- a/xos/core/models/controlleruser.py
+++ /dev/null
@@ -1,110 +0,0 @@
-import os
-import datetime
-from collections import defaultdict
-from django.db import models
-from django.db.models import F, Q
-from core.models import PlCoreBase,User,Controller,ModelLink
-from core.models.site import SitePrivilege
-from core.models.slice import SlicePrivilege
-from core.models.plcorebase import StrippedCharField
-from core.models import Controller,ControllerLinkManager,ControllerLinkDeletionManager
-
-class ControllerUser(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-
-    user = models.ForeignKey(User,related_name='controllerusers')
-    controller = models.ForeignKey(Controller,related_name='controllersusers')
-    kuser_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Keystone user id")
-
-    xos_links = [ModelLink(Controller,via='controller'), ModelLink(User,via='user')]
-
-
-    class Meta:
-        unique_together = ('user', 'controller')
-
-    def __unicode__(self):  return u'%s %s' % (self.controller, self.user)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = ControllerUser.objects.all()
-        else:
-            users = User.select_by_user(user)
-            qs = ControllerUser.objects.filter(user__in=users)
-        return qs
-
-    def can_update(self, user):
-        return user.can_update_root()    
-
-
-class ControllerSitePrivilege(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-
-    controller = models.ForeignKey('Controller', related_name='controllersiteprivileges')
-    site_privilege = models.ForeignKey('SitePrivilege', related_name='controllersiteprivileges')
-    role_id = StrippedCharField(null=True, blank=True, max_length=200, db_index=True, help_text="Keystone id")
-    xos_links = [ModelLink(Controller,via='controller'), ModelLink(SitePrivilege,via='site_privilege')]
-
-    class Meta:
-        unique_together = ('controller', 'site_privilege', 'role_id')
-
-    def __unicode__(self):  return u'%s %s' % (self.controller, self.site_privilege)
-
-    def can_update(self, user):
-        if user.is_readonly:
-            return False
-        if user.is_admin:
-            return True
-        cprivs = ControllerSitePrivilege.objects.filter(site_privilege__user=user)
-        for cpriv in dprivs:
-            if cpriv.site_privilege.role.role == ['admin', 'Admin']:
-                return True
-        return False
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = ControllerSitePrivilege.objects.all()
-        else:
-            cpriv_ids = [cp.id for cp in ControllerSitePrivilege.objects.filter(site_privilege__user=user)]
-            qs = ControllerSitePrivilege.objects.filter(id__in=cpriv_ids)
-        return qs
-
-
-class ControllerSlicePrivilege(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-
-    controller = models.ForeignKey('Controller', related_name='controllersliceprivileges')
-    slice_privilege = models.ForeignKey('SlicePrivilege', related_name='controllersliceprivileges')
-    role_id = StrippedCharField(null=True, blank=True, max_length=200, db_index=True, help_text="Keystone id")
-    xos_links = [ModelLink(Controller,via='controller'), ModelLink(SlicePrivilege,via='slice_privilege')]
-
-
-    class Meta:
-        unique_together = ('controller', 'slice_privilege')
-
-    def __unicode__(self):  return u'%s %s' % (self.controller, self.slice_privilege)
-
-    def can_update(self, user):
-        if user.is_readonly:
-            return False
-        if user.is_admin:
-            return True
-        cprivs = ControllerSlicePrivilege.objects.filter(slice_privilege__user=user)
-        for cpriv in dprivs:
-            if cpriv.role.role == ['admin', 'Admin']:
-                return True
-        return False
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = ControllerSlicePrivilege.objects.all()
-        else:
-            cpriv_ids = [cp.id for cp in ControllerSlicePrivilege.objects.filter(slice_privilege__user=user)]
-            qs = ControllerSlicePrivilege.objects.filter(id__in=cpriv_ids)
-        return qs
-
diff --git a/xos/core/models/controlleruser.xproto b/xos/core/models/controlleruser.xproto
new file mode 100644
index 0000000..5560d6d
--- /dev/null
+++ b/xos/core/models/controlleruser.xproto
@@ -0,0 +1,7 @@
+
+
+message ControllerUser (PlCoreBase){
+     required manytoone user->User:controllerusers = 1 [db_index = True, null = False, blank = False];
+     required manytoone controller->Controller:controllersusers = 2 [db_index = True, null = False, blank = False];
+     optional string kuser_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone user id", null = True, db_index = False];
+}
diff --git a/xos/core/models/credential.py b/xos/core/models/credential.py
deleted file mode 100644
index 166b546..0000000
--- a/xos/core/models/credential.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase,ModelLink
-from core.models import User,Site,Slice,Controller
-from core.models.plcorebase import StrippedCharField
-from encrypted_fields import EncryptedCharField
-from core.models import Controller,ControllerLinkManager,ControllerLinkDeletionManager
-
-class UserCredential(PlCoreBase):
-    user = models.ForeignKey(User, related_name='usercredentials', help_text="The User this credential is associated with")
-
-    name = models.SlugField(help_text="The credential type, e.g. ec2", max_length=128)
-    key_id = StrippedCharField(help_text="The backend id of this credential", max_length=1024)
-    enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
-
-    xos_links = [ModelLink(User,via='user')]
-
-
-    def __unicode__(self):
-        return self.name
-
-class SiteCredential(PlCoreBase):
-    site = models.ForeignKey(Site, related_name='sitecredentials', help_text="The User this credential is associated with")
-
-    name = models.SlugField(help_text="The credential type, e.g. ec2", max_length=128)
-    key_id = StrippedCharField(help_text="The backend id of this credential", max_length=1024)
-    enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
-
-    xos_links = [ModelLink(Site,via='site')]
-
-    def __unicode__(self):
-        return self.name
-
-class SliceCredential(PlCoreBase):
-    slice = models.ForeignKey(Slice, related_name='slicecredentials', help_text="The User this credential is associated with")
-
-    name = models.SlugField(help_text="The credential type, e.g. ec2", max_length=128)
-    key_id = StrippedCharField(help_text="The backend id of this credential", max_length=1024)
-    enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
-
-
-    xos_links = [ModelLink(Slice,via='slice')]
-
-
-    def __unicode__(self):
-        return self.name
-
-class ControllerCredential(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-    controller = models.ForeignKey(Controller, related_name='controllercredentials', help_text="The User this credential is associated with")
-
-    name = models.SlugField(help_text="The credential type, e.g. ec2", max_length=128)
-    key_id = models.CharField(help_text="The backend id of this credential", max_length=1024)
-    enc_value = EncryptedCharField(help_text="The key value of this credential", max_length=1024)
-
-    
-    xos_links = [ModelLink(Controller,via='controller')]
-
-
-    def __unicode__(self):
-        return self.name
diff --git a/xos/core/models/dashboard.py b/xos/core/models/dashboard.py
deleted file mode 100644
index d57c8ba..0000000
--- a/xos/core/models/dashboard.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase, Controller, Deployment
-from core.models.plcorebase import StrippedCharField, ModelLink
-from core.models.site import ControllerLinkManager, ControllerLinkDeletionManager
-
-
-class DashboardView(PlCoreBase):
-    name = StrippedCharField(max_length=200, unique=True, help_text="Name of the View")
-    url = StrippedCharField(max_length=1024, help_text="URL of Dashboard")
-    controllers = models.ManyToManyField(Controller, blank=True, related_name="dashboardviews", through='ControllerDashboardView')
-    enabled = models.BooleanField(default=True)
-    icon = models.CharField(max_length=200, default="default-icon.png", help_text="Icon for Dashboard")
-    icon_active = models.CharField(max_length=200, default="default-icon-active.png", help_text="Icon for active Dashboard")
-    deployments = models.ManyToManyField(Deployment, blank=True, related_name="dashboardviews", help_text="Deployments that should be included in this view")
-
-    def __unicode__(self): return u'%s' % (self.name)
-
-
-class ControllerDashboardView(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-    controller = models.ForeignKey(Controller, related_name='controllerdashboardviews')
-    dashboardView = models.ForeignKey(DashboardView, related_name='controllerdashboardviews')
-    enabled = models.BooleanField(default=True)
-    url = StrippedCharField(max_length=1024, help_text="URL of Dashboard")
-    xos_links = [ModelLink(Controller, via='controller'), ModelLink(DashboardView, via='dashboardview')]
-
-
-class XOSGuiExtension(PlCoreBase):
-    """Persist GUI Extension"""
-    class Meta:
-        app_label = "core"
-
-    name = StrippedCharField(max_length=200, unique=True, help_text="Name of the GUI Extensions")
-    files = StrippedCharField(max_length=1024, help_text="List of comma separated file composing the view")
diff --git a/xos/core/models/dashboardview.xproto b/xos/core/models/dashboardview.xproto
new file mode 100644
index 0000000..76e4f30
--- /dev/null
+++ b/xos/core/models/dashboardview.xproto
@@ -0,0 +1,11 @@
+
+
+message DashboardView (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the View", null = False, db_index = False];
+     required string url = 2 [max_length = 1024, content_type = "stripped", blank = False, help_text = "URL of Dashboard", null = False, db_index = False];
+     required bool enabled = 3 [default = True, null = False, db_index = False, blank = True];
+     required string icon = 4 [default = "default-icon.png", max_length = 200, blank = False, help_text = "Icon for Dashboard", null = False, db_index = False];
+     required string icon_active = 5 [default = "default-icon-active.png", max_length = 200, blank = False, help_text = "Icon for active Dashboard", null = False, db_index = False];
+     required manytomany controllers->Controller/ControllerDashboardView:dashboardviews = 6 [db_index = False, null = False, blank = True];
+     required manytomany deployments->Deployment/DashboardView_deployments:dashboardviews = 7 [help_text = "Deployments that should be included in this view", null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/deployment.xproto b/xos/core/models/deployment.xproto
new file mode 100644
index 0000000..779f74d
--- /dev/null
+++ b/xos/core/models/deployment.xproto
@@ -0,0 +1,6 @@
+
+
+message Deployment (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the Deployment", null = False, db_index = False];
+     required string accessControl = 2 [default = "allow all", max_length = 200, blank = False, help_text = "Access control list that specifies which sites/users may use nodes in this deployment", null = False, db_index = False];
+}
diff --git a/xos/core/models/deploymentprivilege.xproto b/xos/core/models/deploymentprivilege.xproto
new file mode 100644
index 0000000..33efc2a
--- /dev/null
+++ b/xos/core/models/deploymentprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message DeploymentPrivilege (PlCoreBase){
+     required manytoone user->User:deploymentprivileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone deployment->Deployment:deploymentprivileges = 2 [db_index = True, null = False, blank = False];
+     required manytoone role->DeploymentRole:deploymentprivileges = 3 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/deploymentrole.xproto b/xos/core/models/deploymentrole.xproto
new file mode 100644
index 0000000..61adab5
--- /dev/null
+++ b/xos/core/models/deploymentrole.xproto
@@ -0,0 +1,5 @@
+
+
+message DeploymentRole (PlCoreBase){
+     required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/diag.xproto b/xos/core/models/diag.xproto
new file mode 100644
index 0000000..7869f50
--- /dev/null
+++ b/xos/core/models/diag.xproto
@@ -0,0 +1,5 @@
+
+
+message Diag (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the synchronizer", null = False, db_index = False];
+}
diff --git a/xos/core/models/flavor.py b/xos/core/models/flavor.py
deleted file mode 100644
index 693a5f8..0000000
--- a/xos/core/models/flavor.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import os
-import socket
-from django.db import models
-from core.models import PlCoreBase, Deployment
-from core.models.plcorebase import StrippedCharField
-from django.contrib.contenttypes.models import ContentType
-
-class Flavor(PlCoreBase):
-    name = StrippedCharField(max_length=32, help_text="name of this flavor, as displayed to users")
-    description = StrippedCharField(max_length=1024, blank=True, null=True)
-    flavor = StrippedCharField(max_length=32, help_text="flavor string used to configure deployments")
-    deployments = models.ManyToManyField(Deployment, blank=True, related_name="flavors")
-    order = models.IntegerField(default=0, help_text="used to order flavors when displayed in a list")
-    default = models.BooleanField(default=False, help_text="make this a default flavor to use when creating new instances")
-
-    class Meta:
-        app_label = "core"
-        ordering = ('order', 'name')
-
-    def __init__(self, *args, **kwargs):
-        super(Flavor, self).__init__(*args, **kwargs)
-        self.no_sync=True
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-""" FlavorParameterType and FlavorParameter are below for completeness sake,
-    waiting for the day we might want to add parameters to flavors.
-
-class FlavorParameterType(PlCoreBase):
-    name = models.SlugField(help_text="The name of this parameter", max_length=128)
-    description = StrippedCharField(max_length=1024)
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-class FlavorParameter(PlCoreBase):
-    parameter = models.ForeignKey(FlavorParameterType, related_name="flavorparameters", help_text="The type of the parameter")
-    value = StrippedCharField(help_text="The value of this parameter", max_length=1024)
-
-    flavor = models.ForeignKey(Flavor,related_name='flavorparameter')
-
-    def __unicode__(self):
-        return self.parameter.name
-
-"""
-
diff --git a/xos/core/models/flavor.xproto b/xos/core/models/flavor.xproto
new file mode 100644
index 0000000..d30a7c2
--- /dev/null
+++ b/xos/core/models/flavor.xproto
@@ -0,0 +1,10 @@
+
+
+message Flavor (PlCoreBase){
+     required string name = 1 [max_length = 32, content_type = "stripped", blank = False, help_text = "name of this flavor, as displayed to users", null = False, db_index = False];
+     optional string description = 2 [db_index = False, max_length = 1024, null = True, content_type = "stripped", blank = True];
+     required string flavor = 3 [max_length = 32, content_type = "stripped", blank = False, help_text = "flavor string used to configure deployments", null = False, db_index = False];
+     required int32 order = 4 [help_text = "used to order flavors when displayed in a list", default = 0, null = False, db_index = False, blank = False];
+     required bool default = 5 [help_text = "make this a default flavor to use when creating new instances", default = False, null = False, db_index = False, blank = True];
+     required manytomany deployments->Deployment/DashboardView_deployments:flavors = 6 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/header.py b/xos/core/models/header.py
new file mode 120000
index 0000000..721b5c0
--- /dev/null
+++ b/xos/core/models/header.py
@@ -0,0 +1 @@
+attic/header.py
\ No newline at end of file
diff --git a/xos/core/models/image.py b/xos/core/models/image.py
deleted file mode 100644
index 8f48458..0000000
--- a/xos/core/models/image.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase
-from core.models.plcorebase import StrippedCharField,ModelLink
-from core.models import Deployment, DeploymentPrivilege, Controller,ControllerLinkManager,ControllerLinkDeletionManager
-
-# Create your models here.
-
-class Image(PlCoreBase):
-    KIND_CHOICES = (('vm', 'Virtual Machine'), ('container', 'Container'), )
-
-    name = StrippedCharField(max_length=256, unique=True)
-    kind = models.CharField(null=False, blank=False, max_length=30, choices=KIND_CHOICES, default="vm")
-    disk_format = StrippedCharField(max_length=256)
-    container_format = StrippedCharField(max_length=256)
-    path = StrippedCharField(max_length=256, null=True, blank=True, help_text="Path to image on local disk")
-    deployments = models.ManyToManyField('Deployment', through='ImageDeployments', blank=True, help_text="Select which images should be instantiated on this deployment", related_name='images')
-
-    tag = StrippedCharField(max_length=256, null=True, blank=True, help_text="For Docker Images, tag of image")
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-class ImageDeployments(PlCoreBase):
-    image = models.ForeignKey(Image,related_name='imagedeployments')
-    deployment = models.ForeignKey(Deployment,related_name='imagedeployments')
-    xos_links = [ModelLink(Image,'image'),ModelLink(Deployment,'deployment')]
-
-    class Meta:
-        unique_together = ('image', 'deployment')
-
-    def __unicode__(self):  return u'%s %s' % (self.image, self.deployment)
-
-    def can_update(self, user):
-        return user.can_update_deployment(self.deployment)
-
-class ControllerImages(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-    image = models.ForeignKey(Image,related_name='controllerimages')
-    controller = models.ForeignKey(Controller,related_name='controllerimages')
-    glance_image_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Glance image id") 
-    xos_links = [ModelLink(Image,'image'),ModelLink(Controller,'controller')] 
-   
-    class Meta:
-        unique_together = ('image', 'controller')
-         
-    def __unicode__(self):  return u'%s %s' % (self.image, self.controller)
diff --git a/xos/core/models/image.xproto b/xos/core/models/image.xproto
new file mode 100644
index 0000000..f810e0d
--- /dev/null
+++ b/xos/core/models/image.xproto
@@ -0,0 +1,11 @@
+
+
+message Image (PlCoreBase){
+     required string name = 1 [db_index = False, max_length = 256, null = False, content_type = "stripped", blank = False];
+     required string kind = 2 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'))", max_length = 30, blank = False, null = False, db_index = False];
+     required string disk_format = 3 [db_index = False, max_length = 256, null = False, content_type = "stripped", blank = False];
+     required string container_format = 4 [db_index = False, max_length = 256, null = False, content_type = "stripped", blank = False];
+     optional string path = 5 [max_length = 256, content_type = "stripped", blank = True, help_text = "Path to image on local disk", null = True, db_index = False];
+     optional string tag = 6 [max_length = 256, content_type = "stripped", blank = True, help_text = "For Docker Images, tag of image", null = True, db_index = False];
+     required manytomany deployments->Deployment/ImageDeployments:images = 7 [help_text = "Select which images should be instantiated on this deployment", null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/imagedeployments.xproto b/xos/core/models/imagedeployments.xproto
new file mode 100644
index 0000000..e11fedb
--- /dev/null
+++ b/xos/core/models/imagedeployments.xproto
@@ -0,0 +1,6 @@
+
+
+message ImageDeployments (PlCoreBase){
+     required manytoone image->Image:imagedeployments = 1 [db_index = True, null = False, blank = False];
+     required manytoone deployment->Deployment:imagedeployments = 2 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/instance.py b/xos/core/models/instance.py
deleted file mode 100644
index 043df7c..0000000
--- a/xos/core/models/instance.py
+++ /dev/null
@@ -1,261 +0,0 @@
-import os
-from django.db import models
-from django.db.models import Q
-from django.core import exceptions
-from core.models import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,ModelLink
-from core.models.plcorebase import StrippedCharField
-from core.models import Image
-from core.models import Slice, SlicePrivilege
-from core.models import Node
-from core.models import Site
-from core.models import Deployment
-from core.models import Controller
-from core.models import User
-from core.models import Tag
-from core.models import Flavor
-from django.contrib.contenttypes.fields import GenericRelation
-from xos.config import Config
-from django.core.exceptions import PermissionDenied, ValidationError
-
-config = Config()
-
-def get_default_flavor(controller = None):
-    # Find a default flavor that can be used for a instance. This is particularly
-    # useful in evolution. It's also intended this helper function can be used
-    # for admin.py when users
-
-    if controller:
-        flavors = controller.flavors.all()
-    else:
-        flavors = Flavor.objects.all()
-
-    if not flavors:
-        return None
-
-    for flavor in flavors:
-        if flavor.default:
-            return flavor
-
-    return flavors[0]
-
-class InstanceDeletionManager(PlCoreBaseDeletionManager):
-    def get_queryset(self):
-        parent=super(InstanceDeletionManager, self)
-        try:
-            backend_type = config.observer_backend_type
-        except AttributeError:
-            backend_type = None
-
-        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
-        if (backend_type):
-            return parent_queryset.filter(Q(node__controller__backend_type=backend_type))
-        else:
-            return parent_queryset
-
-    # deprecated in django 1.7 in favor of get_queryset().
-    def get_query_set(self):
-        return self.get_queryset()
-
-
-class InstanceManager(PlCoreBaseManager):
-    def get_queryset(self):
-        parent=super(InstanceManager, self)
-
-        try:
-            backend_type = config.observer_backend_type
-        except AttributeError:
-            backend_type = None
-
-        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
-
-        if backend_type:
-            return parent_queryset.filter(Q(node__controller__backend_type=backend_type))
-        else:
-            return parent_queryset
-
-    # deprecated in django 1.7 in favor of get_queryset().
-    def get_query_set(self):
-        return self.get_queryset()
-
-# Create your models here.
-class Instance(PlCoreBase):
-    ISOLATION_CHOICES = (('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))
-
-    objects = InstanceManager()
-    deleted_objects = InstanceDeletionManager()
-    instance_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Nova instance id")
-    instance_uuid = StrippedCharField(null=True, blank=True, max_length=200, help_text="Nova instance uuid")
-    name = StrippedCharField(max_length=200, help_text="Instance name")
-    instance_name = StrippedCharField(blank=True, null=True, max_length=200, help_text="OpenStack generated name")
-    ip = models.GenericIPAddressField(help_text="Instance ip address", blank=True, null=True)
-    image = models.ForeignKey(Image, related_name='instances')
-    creator = models.ForeignKey(User, related_name='instances', blank=True, null=True)
-    slice = models.ForeignKey(Slice, related_name='instances')
-    deployment = models.ForeignKey(Deployment, verbose_name='deployment', related_name='instance_deployment')
-    node = models.ForeignKey(Node, related_name='instances')
-    numberCores = models.IntegerField(verbose_name="Number of Cores", help_text="Number of cores for instance", default=0)
-    flavor = models.ForeignKey(Flavor, help_text="Flavor of this instance", default=get_default_flavor)
-    tags = GenericRelation(Tag)
-    userData = models.TextField(blank=True, null=True, help_text="user_data passed to instance during creation")
-    isolation = models.CharField(null=False, blank=False, max_length=30, choices=ISOLATION_CHOICES, default="vm")
-    volumes = models.TextField(null=True, blank=True, help_text="Comma-separated list of directories to expose to parent context")
-    parent = models.ForeignKey("Instance", null=True, blank=True, help_text="Parent Instance for containers nested inside of VMs")
-
-    xos_links = [ModelLink(dest=Slice,via='slice'),ModelLink(dest=Image,via='image')]
-
-    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 __unicode__(self):
-        if self.name and Slice.objects.filter(id=self.slice_id) and (self.name != self.slice.name):
-            # NOTE: The weird check on self.slice_id was due to a problem when
-            #   deleting the slice before the instance.
-            return u'%s' % self.name
-        elif self.instance_name:
-            return u'%s' % (self.instance_name)
-        elif self.id:
-            return u'uninstantiated-%s' % str(self.id)
-        elif self.slice:
-            return u'unsaved-instance on %s' % self.slice.name
-        else:
-            return u'unsaved-instance'
-
-    def save(self, *args, **kwds):
-        if not self.name:
-            self.name = self.slice.name
-        if not self.creator and hasattr(self, 'caller'):
-            self.creator = self.caller
-        if not self.creator:
-            raise ValidationError('instance has no creator')
-
-        if (self.isolation == "container") or (self.isolation == "container_vm"):
-            if (self.image.kind != "container"):
-               raise ValidationError("Container instance must use container image")
-        elif (self.isolation == "vm"):
-            if (self.image.kind != "vm"):
-               raise ValidationError("VM instance must use VM image")
-
-        if (self.isolation == "container_vm") and (not self.parent):
-            raise ValidationError("Container-vm instance must have a parent")
-
-        if (self.parent) and (self.isolation != "container_vm"):
-            raise ValidationError("Parent field can only be set on Container-vm instances")
-
-        if (self.slice.creator != self.creator):
-            # Check to make sure there's a slice_privilege for the user. If there
-            # isn't, then keystone will throw an exception inside the observer.
-            slice_privs = SlicePrivilege.objects.filter(slice=self.slice, user=self.creator)
-            if not slice_privs:
-                raise ValidationError('instance creator has no privileges on slice')
-
-# XXX smbaker - disabled for now, was causing fault in tenant view create slice
-#        if not self.controllerNetwork.test_acl(slice=self.slice):
-#            raise exceptions.ValidationError("Deployment %s's ACL does not allow any of this slice %s's users" % (self.controllerNetwork.name, self.slice.name))
-
-        super(Instance, self).save(*args, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_slice(self.slice)
-
-    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")
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = Instance.objects.all()
-        else:
-            slices = Slice.select_by_user(user)
-            qs = Instance.objects.filter(slice__in=slices)
-        return qs
-
-    def get_cpu_stats(self):
-        filter = 'instance_id=%s'%self.instance_id
-        return monitor.get_meter('cpu',filter,None)
-
-    def get_bw_stats(self):
-        filter = 'instance_id=%s'%self.instance_id
-        return monitor.get_meter('network.outgoing.bytes',filter,None)
-
-    def get_node_stats(self):
-        # Note sure what should go back here
-        return 1
-
-    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):
-        slice_memberships = SlicePrivilege.objects.filter(slice=self.slice)
-        pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.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
-
-def controller_setter(instance, **kwargs):
-    try:
-        instance.controller = instance.node.site_deployment.controller
-    except:
-        instance.controller = None
-
-models.signals.post_init.connect(controller_setter, Instance)
diff --git a/xos/core/models/instance.xproto b/xos/core/models/instance.xproto
new file mode 100644
index 0000000..b411bd4
--- /dev/null
+++ b/xos/core/models/instance.xproto
@@ -0,0 +1,21 @@
+
+
+message Instance (PlCoreBase){
+     optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
+     optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
+     required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
+     optional string instance_name = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack generated name", null = True, db_index = False];
+     optional string ip = 5 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+     required manytoone image->Image:instances = 6 [db_index = True, null = False, blank = False];
+     optional manytoone creator->User:instances = 7 [db_index = True, null = True, blank = True];
+     required manytoone slice->Slice:instances = 8 [db_index = True, null = False, blank = False];
+     required manytoone deployment->Deployment:instance_deployment = 9 [db_index = True, null = False, blank = False];
+     required manytoone node->Node:instances = 10 [db_index = True, null = False, blank = False];
+     required int32 numberCores = 11 [help_text = "Number of cores for instance", default = 0, null = False, db_index = False, blank = False];
+     required manytoone flavor->Flavor:instance = 12 [help_text = "Flavor of this instance", default = "get_default_flavor()", null = False, db_index = True, blank = False];
+     optional string userData = 13 [help_text = "user_data passed to instance during creation", null = True, db_index = False, blank = True];
+     required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
+     optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
+     optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
+     required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/invoice.xproto b/xos/core/models/invoice.xproto
new file mode 100644
index 0000000..7c05a58
--- /dev/null
+++ b/xos/core/models/invoice.xproto
@@ -0,0 +1,6 @@
+
+
+message Invoice (PlCoreBase){
+     required string date = 1 [db_index = False, null = False, content_type = "date", blank = False];
+     required manytoone account->Account:invoices = 2 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/journal.py b/xos/core/models/journal.py
index 6c25033..9d60f2a 100644
--- a/xos/core/models/journal.py
+++ b/xos/core/models/journal.py
@@ -1,3 +1,5 @@
+from __future__ import absolute_import
+
 from django.db import models
 from django.utils import timezone
 
diff --git a/xos/core/models/library.xproto b/xos/core/models/library.xproto
new file mode 100644
index 0000000..8505547
--- /dev/null
+++ b/xos/core/models/library.xproto
@@ -0,0 +1,4 @@
+
+
+message Library (LoadableModule){
+}
diff --git a/xos/core/models/loadablemodule.xproto b/xos/core/models/loadablemodule.xproto
new file mode 100644
index 0000000..9f953d4
--- /dev/null
+++ b/xos/core/models/loadablemodule.xproto
@@ -0,0 +1,10 @@
+
+
+message LoadableModule (PlCoreBase){
+     required manytoone xos->XOS:loadable_modules = 1 [help_text = "Pointer to XOS", default = "get_xos()", null = False, db_index = True, blank = False];
+     required string name = 2 [max_length = 30, content_type = "stripped", blank = False, help_text = "Service Name", null = False, db_index = False];
+     optional string base_url = 3 [max_length = 1024, content_type = "stripped", blank = True, help_text = "Base URL, allows use of relative URLs for resources", null = True, db_index = False];
+     optional string version = 4 [default = "1.0.0", max_length = 30, content_type = "stripped", blank = True, help_text = "Version of Service Controller", null = True, db_index = False];
+     optional string provides = 5 [max_length = 254, content_type = "stripped", blank = True, help_text = "Comma-separated list of things provided", null = True, db_index = False];
+     optional string requires = 6 [max_length = 254, content_type = "stripped", blank = True, help_text = "Comma-separated list of required Service Controllers", null = True, db_index = False];
+}
diff --git a/xos/core/models/loadablemoduleresource.xproto b/xos/core/models/loadablemoduleresource.xproto
new file mode 100644
index 0000000..f91729f
--- /dev/null
+++ b/xos/core/models/loadablemoduleresource.xproto
@@ -0,0 +1,10 @@
+
+
+message LoadableModuleResource (PlCoreBase){
+     required manytoone loadable_module->LoadableModule:loadable_module_resources = 1 [help_text = "The Loadable Module this resource is associated with", null = False, db_index = True, blank = False];
+     required string name = 2 [max_length = 30, content_type = "stripped", blank = False, help_text = "Object Name", null = False, db_index = False];
+     optional string subdirectory = 3 [max_length = 1024, content_type = "stripped", blank = True, help_text = "optional subdirectory", null = True, db_index = False];
+     required string kind = 4 [choices = "(('models', 'Models'), ('admin', 'Admin'), ('admin_template', 'Admin Template'), ('django_library', 'Django Library'), ('synchronizer', 'Synchronizer'), ('rest_service', 'REST API (service)'), ('rest_tenant', 'REST API (tenant)'), ('tosca_custom_types', 'Tosca Custom Types'), ('tosca_resource', 'Tosca Resource'), ('private_key', 'Private Key'), ('public_key', 'Public Key'), ('vendor_js', 'Vendor Javascript'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     required string format = 5 [choices = "(('python', 'Python'), ('manifest', 'Manifest'), ('docker', 'Docker Container'), ('yaml', 'YAML'), ('raw', 'raw'), ('javascript', 'Javascript'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     optional string url = 6 [max_length = 1024, content_type = "stripped", blank = True, help_text = "URL of resource", null = True, db_index = False];
+}
diff --git a/xos/core/models/network.py b/xos/core/models/network.py
deleted file mode 100644
index a2b61c2..0000000
--- a/xos/core/models/network.py
+++ /dev/null
@@ -1,426 +0,0 @@
-import os
-import socket
-import sys
-from django.db import models, transaction
-from core.models import PlCoreBase, Site, Slice, Instance, Controller, Service, ModelLink
-from core.models import ControllerLinkManager,ControllerLinkDeletionManager
-from django.contrib.contenttypes.models import ContentType
-from django.contrib.contenttypes.fields import GenericForeignKey
-from django.core.exceptions import ValidationError
-from django.db.models import Q
-
-# If true, then IP addresses will be allocated by the model. If false, then
-# we will assume the observer handles it.
-NO_OBSERVER=False
-
-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))
-
-class ParameterMixin(object):
-    # helper classes for dealing with NetworkParameter
-
-    def get_parameters(self):
-        parameter_dict = {}
-
-        instance_type = ContentType.objects.get_for_model(self)
-        for param in NetworkParameter.objects.filter(content_type__pk=instance_type.id, object_id=self.id):
-            parameter_dict[param.parameter.name] = param.value
-
-        return parameter_dict
-
-    def set_parameter(self, name, value):
-        instance_type = ContentType.objects.get_for_model(self)
-        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
-        if existing_params:
-            p=existing_params[0]
-            p.value = value
-            p.save()
-        else:
-            pt = NetworkParameterType.objects.get(name=name)
-            p = NetworkParameter(parameter=pt, content_type=instance_type, object_id=self.id, value=value)
-            p.save()
-
-    def unset_parameter(self, name):
-        instance_type = ContentType.objects.get_for_model(self)
-        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
-        for p in existing_params:
-            p.delete()
-
-
-class NetworkTemplate(PlCoreBase, ParameterMixin):
-    VISIBILITY_CHOICES = (('public', 'public'), ('private', 'private'))
-    TRANSLATION_CHOICES = (('none', 'none'), ('NAT', 'NAT'))
-    TOPOLOGY_CHOICES = (('bigswitch', 'BigSwitch'), ('physical', 'Physical'), ('custom', 'Custom'))
-    CONTROLLER_CHOICES = ((None, 'None'), ('onos', 'ONOS'), ('custom', 'Custom'))
-    ACCESS_CHOICES = ((None, 'None'), ('indirect', 'Indirect'), ('direct', 'Direct'))
-
-    VTN_KIND_CHOICES = (("PRIVATE", "Private"), ("PUBLIC", "Public"), ("MANAGEMENT_LOCAL", "Management Local"),

-                        ("MANAGEMENT_HOST", "Management Host"), ("VSG", "VSG"), ("ACCESS_AGENT", "Access Agent"))
-
-    name = models.CharField(max_length=32)
-    description = models.CharField(max_length=1024, blank=True, null=True)
-    guaranteed_bandwidth = models.IntegerField(default=0)
-    visibility = models.CharField(max_length=30, choices=VISIBILITY_CHOICES, default="private")
-    translation = models.CharField(max_length=30, choices=TRANSLATION_CHOICES, default="none")
-    access = models.CharField(max_length=30, null=True, blank=True, choices=ACCESS_CHOICES, help_text="Advertise this network as a means for other slices to contact this slice")
-    shared_network_name = models.CharField(max_length=30, blank=True, null=True)
-    shared_network_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
-    topology_kind = models.CharField(null=False, blank=False, max_length=30, choices=TOPOLOGY_CHOICES, default="bigswitch")
-    controller_kind = models.CharField(null=True, blank=True, max_length=30, choices=CONTROLLER_CHOICES, default=None)
-    vtn_kind = models.CharField(null=True, blank=True, max_length=30, choices=VTN_KIND_CHOICES, default="PRIVATE")
-
-    def __init__(self, *args, **kwargs):
-        super(NetworkTemplate, self).__init__(*args, **kwargs)
-
-        # somehow these got set wrong inside of the live database. Remove this
-        # code after all is well...
-        if (self.topology_kind=="BigSwitch"):
-            print >> sys.stderr, "XXX warning: topology_kind invalid case"
-            self.topology_kind="bigswitch"
-        elif (self.topology_kind=="Physical"):
-            print >> sys.stderr, "XXX warning: topology_kind invalid case"
-            self.topology_kind="physical"
-        elif (self.topology_kind=="Custom"):
-            print >> sys.stderr, "XXX warning: topology_kind invalid case"
-            self.toplogy_kind="custom"
-
-    def save(self, *args, **kwargs):
-        self.enforce_choices(self.access, self.ACCESS_CHOICES)
-        super(NetworkTemplate, self).save(*args, **kwargs)
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-class Network(PlCoreBase, ParameterMixin):
-    name = models.CharField(max_length=32)
-    template = models.ForeignKey(NetworkTemplate)
-    subnet = models.CharField(max_length=32, blank=True)
-    start_ip = models.CharField(max_length=32, blank=True)
-    end_ip = models.CharField(max_length=32, blank=True)
-    ports = models.CharField(max_length=1024, blank=True, null=True, validators=[ValidateNatList])
-    labels = models.CharField(max_length=1024, blank=True, null=True)
-    owner = models.ForeignKey(Slice, related_name="ownedNetworks", help_text="Slice that owns control of this Network")
-
-    guaranteed_bandwidth = models.IntegerField(default=0)
-    permit_all_slices = models.BooleanField(default=False)
-    permitted_slices = models.ManyToManyField(Slice, blank=True, related_name="availableNetworks")
-    slices = models.ManyToManyField(Slice, blank=True, related_name="networks", through="NetworkSlice")
-    instances = models.ManyToManyField(Instance, blank=True, related_name="networks", through="Port")
-
-    topology_parameters = models.TextField(null=True, blank=True)
-    controller_url = models.CharField(null=True, blank=True, max_length=1024)
-    controller_parameters = models.TextField(null=True, blank=True)
-
-    # for observer/manager
-    network_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
-    router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum router id")
-    subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum subnet id")
-
-    autoconnect = models.BooleanField(default=True, help_text="This network can be autoconnected to the slice that owns it")
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-    def save(self, *args, **kwds):
-        if (not self.subnet) and (NO_OBSERVER):
-            from util.network_subnet_allocator import find_unused_subnet
-            self.subnet = find_unused_subnet(existing_subnets=[x.subnet for x in Network.objects.all()])
-            print "DEF_MOD_NET_IP", self.start_ip
-        super(Network, self).save(*args, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_slice(self.owner)
-
-    @property
-    def nat_list(self):
-        return ParseNatList(self.ports)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = Network.objects.all()
-        else:
-            slices = Slice.select_by_user(user)
-            #slice_ids = [s.id for s in Slice.select_by_user(user)]
-            qs = Network.objects.filter(owner__in=slices)
-        return qs
-
-    def get_parameters(self):
-        # returns parameters from the template, updated by self.
-        p={}
-        if self.template:
-            p = self.template.get_parameters()
-        p.update(ParameterMixin.get_parameters(self))
-        return p
-
-class ControllerNetwork(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-
-    # Stores the openstack ids at various controllers
-    network = models.ForeignKey(Network, related_name='controllernetworks')
-    controller = models.ForeignKey(Controller, related_name='controllernetworks')
-    subnet = models.CharField(max_length=32, blank=True)
-    start_ip = models.CharField(max_length=32, blank=True)
-    stop_ip = models.CharField(max_length=32, blank=True)
-    net_id = models.CharField(null=True, blank=True, max_length=256, help_text="Neutron network") # feedback state
-    router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Neutron router id") # feedback state
-    subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Neutron subnet id") # feedback state
-    gateway = models.CharField(max_length=32, blank=True, null=True) # feedback state
-    segmentation_id = models.CharField(max_length=32, blank=True, null=True) # feedback state
-    xos_links = [ModelLink(Controller,via='controller'),ModelLink(Network,via='network')]
-
-    class Meta:
-        unique_together = ('network', 'controller')
-
-    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
- 
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = ControllerNetwork.objects.all()
-        else:
-            slices = Slice.select_by_user(user)
-            networks = Network.objects.filter(owner__in=slices)
-            qs = ControllerNetwork.objects.filter(network__in=networks)
-        return qs
-
-class NetworkSlice(PlCoreBase):
-    # This object exists solely so we can implement the permission check when
-    # adding slices to networks. It adds no additional fields to the relation.
-
-    network = models.ForeignKey(Network,related_name='networkslices')
-    slice = models.ForeignKey(Slice,related_name='networkslices')
-
-    xos_links = [ModelLink(Network,via='network'),ModelLink(Slice,via='slice')]
-
-    class Meta:
-        unique_together = ('network', 'slice')
-
-    def save(self, *args, **kwds):
-        slice = self.slice
-        if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
-            # to add a instance to the network, then one of the following must be true:
-            #   1) instance's slice is in network's permittedSlices list,
-            #   2) instance's slice is network's owner, or
-            #   3) network's permitAllSlices is true
-            raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
-
-        super(NetworkSlice, self).save(*args, **kwds)
-
-    def __unicode__(self):  return u'%s-%s' % (self.network.name, self.slice.name)
-
-    def can_update(self, user):
-        return user.can_update_slice(self.slice)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = NetworkSlice.objects.all()
-        else:
-            slice_ids = [s.id for s in Slice.select_by_user(user)]
-            network_ids = [network.id for network in Network.select_by_user(user)]
-            qs = NetworkSlice.objects.filter(Q(slice__in=slice_ids) | Q(network__in=network_ids))
-        return qs
-
-class Port(PlCoreBase, ParameterMixin):
-    network = models.ForeignKey(Network,related_name='links')
-    instance = models.ForeignKey(Instance, null=True, blank=True, related_name='ports')
-    ip = models.GenericIPAddressField(help_text="Instance ip address", blank=True, null=True)
-    port_id = models.CharField(null=True, blank=True, max_length=256, help_text="Neutron port id")
-    mac = models.CharField(null=True, blank=True, max_length=256, help_text="MAC address associated with this port")
-    xos_created = models.BooleanField(default=False) # True if XOS created this port in Neutron, False if port created by Neutron and observed by XOS
-
-    xos_links = [ModelLink(Network,via='network'), ModelLink('Instance',via='instance')]
-
-    class Meta:
-        unique_together = ('network', 'instance')
-
-    def save(self, *args, **kwds):
-        if self.instance:
-            slice = self.instance.slice
-            if (slice not in self.network.permitted_slices.all()) and (slice != self.network.owner) and (not self.network.permit_all_slices):
-                # to add a instance to the network, then one of the following must be true:
-                #   1) instance's slice is in network's permittedSlices list,
-                #   2) instance's slice is network's owner, or
-                #   3) network's permitAllSlices is true
-                raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
-
-        super(Port, self).save(*args, **kwds)
-
-    def __unicode__(self):
-        if self.instance:
-            return u'%s-%s' % (self.network.name, self.instance.instance_name)
-        else:
-            return u'%s-unboundport-%s' % (self.network.name, self.id)
-
-    def can_update(self, user):
-        if self.instance:
-            return user.can_update_slice(self.instance.slice)
-        if self.network:
-            return user.can_update_slice(self.network.owner)
-        return False
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = Port.objects.all()
-        else:
-            instances = Instance.select_by_user(user)
-            instance_ids = [instance.id for instance in instances]
-            networks = Network.select_by_user(user)
-            network_ids = [network.id for network in networks]
-            qs = Port.objects.filter(Q(instance__in=instance_ids) | Q(network__in=network_ids))
-        return qs
-
-    def get_parameters(self):
-        # returns parameters from the network, updated by self.
-        p={}
-        if self.network:
-            p = self.network.get_parameters()
-        p.update(ParameterMixin.get_parameters(self))
-        return p
-
-class Router(PlCoreBase):
-    name = models.CharField(max_length=32)
-    owner = models.ForeignKey(Slice, related_name="routers")
-    permittedNetworks = models.ManyToManyField(Network, blank=True, related_name="availableRouters")
-    networks = models.ManyToManyField(Network, blank=True, related_name="routers")
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-    def can_update(self, user):
-        return user.can_update_slice(self.owner)
-
-class NetworkParameterType(PlCoreBase):
-    name = models.SlugField(help_text="The name of this parameter", max_length=128)
-    description = models.CharField(max_length=1024)
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-class NetworkParameter(PlCoreBase):
-    parameter = models.ForeignKey(NetworkParameterType, related_name="networkparameters", help_text="The type of the parameter")
-    value = models.CharField(help_text="The value of this parameter", max_length=1024)
-
-    # The required fields to do a ObjectType lookup, and object_id assignment
-    content_type = models.ForeignKey(ContentType)
-    object_id = models.PositiveIntegerField()
-    content_object = GenericForeignKey('content_type', 'object_id')
-
-    def __unicode__(self):
-        return self.parameter.name
-
-class AddressPool(PlCoreBase):
-    name = models.CharField(max_length=32)
-    addresses = models.TextField(blank=True, null=True)
-    gateway_ip = models.CharField(max_length=32, null=True)
-    gateway_mac = models.CharField(max_length=32, null=True)
-    cidr = models.CharField(max_length=32, null=True)
-    inuse = models.TextField(blank=True, null=True)
-    service = models.ForeignKey(Service, related_name="addresspools", null=True, blank=True)
-
-    def __unicode__(self): return u'%s' % (self.name)
-
-    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/network.xproto b/xos/core/models/network.xproto
new file mode 100644
index 0000000..4e66a1f
--- /dev/null
+++ b/xos/core/models/network.xproto
@@ -0,0 +1,24 @@
+
+
+message Network (PlCoreBase,ParameterMixin){
+     required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+     required manytoone template->NetworkTemplate:network = 2 [db_index = True, null = False, blank = False];
+     required string subnet = 3 [db_index = False, max_length = 32, null = False, blank = True];
+     required string start_ip = 4 [db_index = False, max_length = 32, null = False, blank = True];
+     required string end_ip = 5 [db_index = False, max_length = 32, null = False, blank = True];
+     optional string ports = 6 [db_index = False, max_length = 1024, null = True, blank = True];
+     optional string labels = 7 [db_index = False, max_length = 1024, null = True, blank = True];
+     required manytoone owner->Slice:ownedNetworks = 8 [help_text = "Slice that owns control of this Network", null = False, db_index = True, blank = False];
+     required int32 guaranteed_bandwidth = 9 [default = 0, null = False, db_index = False, blank = False];
+     required bool permit_all_slices = 10 [default = False, null = False, db_index = False, blank = True];
+     optional string topology_parameters = 11 [db_index = False, null = True, blank = True];
+     optional string controller_url = 12 [db_index = False, max_length = 1024, null = True, blank = True];
+     optional string controller_parameters = 13 [db_index = False, null = True, blank = True];
+     optional string network_id = 14 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+     optional string router_id = 15 [help_text = "Quantum router id", max_length = 256, null = True, db_index = False, blank = True];
+     optional string subnet_id = 16 [help_text = "Quantum subnet id", max_length = 256, null = True, 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];
+}
diff --git a/xos/core/models/network_header.py b/xos/core/models/network_header.py
new file mode 120000
index 0000000..1573aea
--- /dev/null
+++ b/xos/core/models/network_header.py
@@ -0,0 +1 @@
+attic/network_header.py
\ No newline at end of file
diff --git a/xos/core/models/networkparameter.xproto b/xos/core/models/networkparameter.xproto
new file mode 100644
index 0000000..81585ee
--- /dev/null
+++ b/xos/core/models/networkparameter.xproto
@@ -0,0 +1,8 @@
+
+
+message NetworkParameter (PlCoreBase){
+     required manytoone parameter->NetworkParameterType:networkparameters = 1 [help_text = "The type of the parameter", null = False, db_index = True, blank = False];
+     required string value = 2 [help_text = "The value of this parameter", max_length = 1024, null = False, db_index = False, blank = False];
+     required manytoone content_type->ContentType:networkparameter = 3 [db_index = True, null = False, blank = False];
+     required uint32 object_id = 4 [db_index = False, null = False, blank = False];
+}
diff --git a/xos/core/models/networkparametertype.xproto b/xos/core/models/networkparametertype.xproto
new file mode 100644
index 0000000..6eb7804
--- /dev/null
+++ b/xos/core/models/networkparametertype.xproto
@@ -0,0 +1,6 @@
+
+
+message NetworkParameterType (PlCoreBase){
+     required string name = 1 [help_text = "The name of this parameter", max_length = 128, null = False, db_index = True, blank = False];
+     required string description = 2 [db_index = False, max_length = 1024, null = False, blank = False];
+}
diff --git a/xos/core/models/networkslice.xproto b/xos/core/models/networkslice.xproto
new file mode 100644
index 0000000..7ae04a2
--- /dev/null
+++ b/xos/core/models/networkslice.xproto
@@ -0,0 +1,6 @@
+
+
+message NetworkSlice (PlCoreBase){
+     required manytoone network->Network:networkslices = 1 [db_index = True, null = False, blank = False];
+     required manytoone slice->Slice:networkslices = 2 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/networktemplate.xproto b/xos/core/models/networktemplate.xproto
new file mode 100644
index 0000000..3d17277
--- /dev/null
+++ b/xos/core/models/networktemplate.xproto
@@ -0,0 +1,15 @@
+
+
+message NetworkTemplate (PlCoreBase,ParameterMixin){
+     required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+     optional string description = 2 [db_index = False, max_length = 1024, null = True, blank = True];
+     required int32 guaranteed_bandwidth = 3 [default = 0, null = False, db_index = False, blank = False];
+     required string visibility = 4 [default = "private", choices = "(('public', 'public'), ('private', 'private'))", max_length = 30, blank = False, null = False, db_index = False];
+     required string translation = 5 [default = "none", choices = "(('none', 'none'), ('NAT', 'NAT'))", max_length = 30, blank = False, null = False, db_index = False];
+     optional string access = 6 [choices = "((None, 'None'), ('indirect', 'Indirect'), ('direct', 'Direct'))", max_length = 30, blank = True, help_text = "Advertise this network as a means for other slices to contact this slice", null = True, db_index = False];
+     optional string shared_network_name = 7 [db_index = False, max_length = 30, null = True, blank = True];
+     optional string shared_network_id = 8 [help_text = "Quantum network", max_length = 256, null = True, db_index = False, blank = True];
+     required string topology_kind = 9 [default = "bigswitch", choices = "(('bigswitch', 'BigSwitch'), ('physical', 'Physical'), ('custom', 'Custom'))", max_length = 30, blank = False, null = False, db_index = False];
+     optional string controller_kind = 10 [blank = True, max_length = 30, null = True, db_index = False, choices = "((None, 'None'), ('onos', 'ONOS'), ('custom', 'Custom'))"];
+     optional string vtn_kind = 11 [default = "PRIVATE", choices = "(('PRIVATE', 'Private'), ('PUBLIC', 'Public'), ('MANAGEMENT_LOCAL', 'Management Local'), ('MANAGEMENT_HOST', 'Management Host'), ('VSG', 'VSG'), ('ACCESS_AGENT', 'Access Agent'))", max_length = 30, blank = True, null = True, db_index = False];
+}
diff --git a/xos/core/models/node.py b/xos/core/models/node.py
deleted file mode 100644
index 5cfc9b8..0000000
--- a/xos/core/models/node.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase,ModelLink
-from core.models.plcorebase import StrippedCharField
-from core.models.site import Site, SiteDeployment, SitePrivilege
-from core.models import Tag
-from django.contrib.contenttypes.fields import GenericRelation
-
-# Create your models here.
-
-class Node(PlCoreBase):
-    name = StrippedCharField(max_length=200, unique=True, help_text="Name of the Node")
-    site_deployment = models.ForeignKey(SiteDeployment, related_name='nodes')
-    site = models.ForeignKey(Site, null=True, blank=True, related_name='nodes')
-    tags = GenericRelation(Tag)
-    xos_links = [ModelLink(Site,'site')]
-
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-    def __init__(self, *args, **kwargs):
-        super(Node, self).__init__(*args, **kwargs)
-        self.no_sync=True
-
-    def save(self, *args, **kwds):
-        if self.site is None and self.site_deployment is not None:
-            self.site = self.site_deployment.site
-
-        super(Node, self).save(*args, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_site(self.site, allow=['tech'])
-
-class NodeLabel(PlCoreBase):
-    name = StrippedCharField(max_length=200, help_text="label name", unique=True)
-    node = models.ManyToManyField(Node, related_name="nodelabels", blank=True)
-
-    def __unicode__(self): return u'%s' % (self.name)
diff --git a/xos/core/models/node.xproto b/xos/core/models/node.xproto
new file mode 100644
index 0000000..3a88b6a
--- /dev/null
+++ b/xos/core/models/node.xproto
@@ -0,0 +1,8 @@
+
+
+message Node (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the Node", null = False, db_index = False];
+     required manytoone site_deployment->SiteDeployment:nodes = 2 [db_index = True, null = False, blank = False];
+     optional manytoone site->Site:nodes = 3 [db_index = True, null = True, blank = True];
+     required manytomany tags->Tag = 4 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/nodelabel.xproto b/xos/core/models/nodelabel.xproto
new file mode 100644
index 0000000..fc89a46
--- /dev/null
+++ b/xos/core/models/nodelabel.xproto
@@ -0,0 +1,6 @@
+
+
+message NodeLabel (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "label name", null = False, db_index = False];
+     required manytomany node->Node/NodeLabel_node:nodelabels = 2 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/payment.xproto b/xos/core/models/payment.xproto
new file mode 100644
index 0000000..f9723ad
--- /dev/null
+++ b/xos/core/models/payment.xproto
@@ -0,0 +1,7 @@
+
+
+message Payment (PlCoreBase){
+     required manytoone account->Account:payments = 1 [db_index = True, null = False, blank = False];
+     required float amount = 2 [default = 0.0, null = False, db_index = False, blank = False];
+     required string date = 3 [default = "now()", null = False, db_index = False, content_type = "date", blank = False];
+}
diff --git a/xos/core/models/port.xproto b/xos/core/models/port.xproto
new file mode 100644
index 0000000..a3a9cdc
--- /dev/null
+++ b/xos/core/models/port.xproto
@@ -0,0 +1,10 @@
+
+
+message Port (PlCoreBase,ParameterMixin){
+     required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False];
+     optional manytoone instance->Instance:ports = 2 [db_index = True, null = True, blank = True];
+     optional string ip = 3 [max_length = 39, content_type = "ip", blank = True, help_text = "Instance ip address", null = True, db_index = False];
+     optional string port_id = 4 [help_text = "Neutron port id", max_length = 256, null = True, db_index = False, blank = True];
+     optional string mac = 5 [help_text = "MAC address associated with this port", max_length = 256, null = True, db_index = False, blank = True];
+     required bool xos_created = 6 [default = False, null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/program.py b/xos/core/models/program.py
deleted file mode 100644
index 01e17fa..0000000
--- a/xos/core/models/program.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from django.db import models
-from core.models import PlCoreBase,SingletonModel,PlCoreBaseManager,User
-from core.models.plcorebase import StrippedCharField
-from xos.exceptions import *
-from operator import attrgetter
-import json
-
-class Program(PlCoreBase):
-    KIND_CHOICES = (('tosca', 'Tosca'), )
-    COMMAND_CHOICES = (('run', 'Run'), ('destroy', 'Destroy'), )
-
-    name = StrippedCharField(max_length=30, help_text="Service Name")
-    description = models.TextField(max_length=254,null=True, blank=True,help_text="Description of Service")
-    kind = StrippedCharField(max_length=30, help_text="Kind of service", choices=KIND_CHOICES)
-    command = StrippedCharField(blank=True, null=True, max_length=30, help_text="Command to run", choices=COMMAND_CHOICES)
-
-    owner = models.ForeignKey(User, null=True, related_name="programs")
-
-    contents = models.TextField(blank=True, null=True, help_text="Contents of Program")
-    output = models.TextField(blank=True, null=True, help_text="Output of Program")
-    messages = models.TextField(blank=True, null=True, help_text="Debug messages")
-    status = models.TextField(blank=True, null=True, max_length=30, help_text="Status of program")
-
-    @classmethod
-    def select_by_user(cls, user):
-        return cls.objects.all()
-
-    def __unicode__(self): return u'%s' % (self.name)
-
-    def can_update(self, user):
-        return True
-
-    def save(self, *args, **kwargs):
-        # set creator on first save
-        if not self.owner and hasattr(self, 'caller'):
-            self.owner = self.caller
-
-        if (self.command in ["run", "destroy"]) and (self.status in ["complete", "exception"]):
-            self.status = "queued"
-
-        super(Program, self).save(*args, **kwargs)
-
diff --git a/xos/core/models/program.xproto b/xos/core/models/program.xproto
new file mode 100644
index 0000000..38b083e
--- /dev/null
+++ b/xos/core/models/program.xproto
@@ -0,0 +1,13 @@
+
+
+message Program (PlCoreBase){
+     required string name = 1 [max_length = 30, content_type = "stripped", blank = False, help_text = "Service Name", null = False, db_index = False];
+     optional string description = 2 [help_text = "Description of Service", max_length = 254, null = True, db_index = False, blank = True];
+     required string kind = 3 [choices = "(('tosca', 'Tosca'),)", max_length = 30, content_type = "stripped", blank = False, help_text = "Kind of service", null = False, db_index = False];
+     optional string command = 4 [choices = "(('run', 'Run'), ('destroy', 'Destroy'))", max_length = 30, content_type = "stripped", blank = True, help_text = "Command to run", null = True, db_index = False];
+     optional manytoone owner->User:programs = 5 [db_index = True, null = True, blank = False];
+     optional string contents = 6 [help_text = "Contents of Program", null = True, db_index = False, blank = True];
+     optional string output = 7 [help_text = "Output of Program", null = True, db_index = False, blank = True];
+     optional string messages = 8 [help_text = "Debug messages", null = True, db_index = False, blank = True];
+     optional string status = 9 [help_text = "Status of program", max_length = 30, null = True, db_index = False, blank = True];
+}
diff --git a/xos/core/models/project.py b/xos/core/models/project.py
deleted file mode 100644
index 9cc2927..0000000
--- a/xos/core/models/project.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase
-from core.models.plcorebase import StrippedCharField
-
-# Create your models here.
-
-class Project(PlCoreBase):
-    name = StrippedCharField(max_length=200, unique=True, help_text="Name of Project")
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
diff --git a/xos/core/models/project.xproto b/xos/core/models/project.xproto
new file mode 100644
index 0000000..d857de9
--- /dev/null
+++ b/xos/core/models/project.xproto
@@ -0,0 +1,5 @@
+
+
+message Project (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of Project", null = False, db_index = False];
+}
diff --git a/xos/core/models/reservation.py b/xos/core/models/reservation.py
deleted file mode 100644
index 6b6e7a1..0000000
--- a/xos/core/models/reservation.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import os
-import datetime
-from django.db import models
-from core.models import PlCoreBase, ModelLink
-from core.models import Instance
-from core.models import Slice
-from core.models import ServiceResource
-
-# Create your models here.
-
-class Reservation(PlCoreBase):
-    startTime = models.DateTimeField()
-    slice = models.ForeignKey(Slice, related_name="reservations")
-    duration = models.IntegerField(default=1)
-
-    xos_links = [ModelLink(Slice,via='slice')]
-
-    def __unicode__(self):  return u'%s to %s' % (self.startTime, self.endTime)
-
-    @property
-    def endTime(self):
-        return self.startTime + datetime.timedelta(hours=self.duration)
-
-    def can_update(self, user):
-        return user.can_update_slice(self.slice)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = Reservation.objects.all()
-        else:
-            slice_ids = [s.id for s in Slice.select_by_user(user)]
-            qs = Reservation.objects.filter(id__in=slice_ids)
-        return qs
-
-class ReservedResource(PlCoreBase):
-    instance = models.ForeignKey(Instance, related_name="reservedresources")
-    resource = models.ForeignKey(ServiceResource, related_name="reservedresources")
-    quantity = models.IntegerField(default=1)
-    reservationSet = models.ForeignKey(Reservation, related_name="reservedresources")
-    
-    xos_links = [ModelLink(Instance,'instance'),ModelLink(ServiceResource,'resource')]
-
-    class Meta(PlCoreBase.Meta):
-       verbose_name_plural = "Reserved Resources"
-
-    def __unicode__(self):  return u'%d %s on %s' % (self.quantity, self.resource, self.instance)
-
-    def can_update(self, user):
-        return user.can_update(self.instance.slice)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = ReservedResource.objects.all()
-        else:
-            instance_ids = [s.id for s in Instance.select_by_user(user)]
-            qs = ReservedResource.objects.filter(id__in=instance_ids)
-        return qs
-
-
diff --git a/xos/core/models/reservation.xproto b/xos/core/models/reservation.xproto
new file mode 100644
index 0000000..be00e7f
--- /dev/null
+++ b/xos/core/models/reservation.xproto
@@ -0,0 +1,7 @@
+
+
+message Reservation (PlCoreBase){
+     required string startTime = 1 [db_index = False, null = False, content_type = "date", blank = False];
+     required manytoone slice->Slice:reservations = 2 [db_index = True, null = False, blank = False];
+     required int32 duration = 3 [default = 1, null = False, db_index = False, blank = False];
+}
diff --git a/xos/core/models/reservedresource.xproto b/xos/core/models/reservedresource.xproto
new file mode 100644
index 0000000..d3baae7
--- /dev/null
+++ b/xos/core/models/reservedresource.xproto
@@ -0,0 +1,8 @@
+
+
+message ReservedResource (PlCoreBase){
+     required manytoone instance->Instance:reservedresources = 1 [db_index = True, null = False, blank = False];
+     required manytoone resource->ServiceResource:reservedresources = 2 [db_index = True, null = False, blank = False];
+     required int32 quantity = 3 [default = 1, null = False, db_index = False, blank = False];
+     required manytoone reservationSet->Reservation:reservedresources = 4 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/role.py b/xos/core/models/role.py
deleted file mode 100644
index b259410..0000000
--- a/xos/core/models/role.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import os
-import datetime
-from django.db import models
-from django.contrib.contenttypes.models import ContentType
-from core.models import PlCoreBase
-from core.models.plcorebase import StrippedCharField
-
-class Role(PlCoreBase):
-
-    role_type = StrippedCharField(max_length=80, verbose_name="Name")
-    role = StrippedCharField(max_length=80, verbose_name="Keystone role id", null=True, blank=True)
-    description = StrippedCharField(max_length=120, verbose_name="Description")
-    content_type = models.ForeignKey(ContentType, verbose_name="Role Scope")
-
-    def __unicode__(self):  return u'%s:%s' % (self.content_type,self.role_type)
-
-
-    def save(self, *args, **kwds):
-        super(Role, self).save(*args, **kwds)
-    
-    def delete(self, *args, **kwds):
-        super(Role, self).delete(*args, **kwds)
-            
diff --git a/xos/core/models/role.xproto b/xos/core/models/role.xproto
new file mode 100644
index 0000000..2ebde68
--- /dev/null
+++ b/xos/core/models/role.xproto
@@ -0,0 +1,8 @@
+
+
+message Role (PlCoreBase){
+     required string role_type = 1 [db_index = False, max_length = 80, null = False, content_type = "stripped", blank = False];
+     optional string role = 2 [db_index = False, max_length = 80, null = True, content_type = "stripped", blank = True];
+     required string description = 3 [db_index = False, max_length = 120, null = False, content_type = "stripped", blank = False];
+     required manytoone content_type->ContentType:role = 4 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/router.xproto b/xos/core/models/router.xproto
new file mode 100644
index 0000000..0e17b20
--- /dev/null
+++ b/xos/core/models/router.xproto
@@ -0,0 +1,8 @@
+
+
+message Router (PlCoreBase){
+     required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
+     required manytoone owner->Slice:routers = 2 [db_index = True, null = False, blank = False];
+     required manytomany permittedNetworks->Network/Router_permittedNetworks:availableRouters = 3 [db_index = False, null = False, blank = True];
+     required manytomany networks->Network/Router_networks:routers = 4 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
deleted file mode 100644
index d3cfc04..0000000
--- a/xos/core/models/service.py
+++ /dev/null
@@ -1,1112 +0,0 @@
-import json
-import operator
-
-from operator import attrgetter
-from distutils.version import LooseVersion
-
-from core.models import PlCoreBase, PlCoreBaseManager, SingletonModel, XOS
-from core.models.plcorebase import StrippedCharField
-from django.db import models
-from django.core.validators import URLValidator
-from xos.exceptions import *
-import urlparse
-
-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 LoadableModule(PlCoreBase):
-    xos = models.ForeignKey(XOS, related_name='loadable_modules', help_text="Pointer to XOS", default=get_xos)
-    name = StrippedCharField(max_length=30, help_text="Service Name")
-    base_url = StrippedCharField(max_length=1024, help_text="Base URL, allows use of relative URLs for resources", null=True, blank=True)
-
-    version = StrippedCharField(blank=True, null=True,
-        max_length=30, help_text="Version of Service Controller", default = "1.0.0")
-    provides = StrippedCharField(blank=True, null=True,
-        max_length=254, help_text="Comma-separated list of things provided")
-    requires = StrippedCharField(blank=True, null=True,
-        max_length=254, help_text="Comma-separated list of required Service Controllers")
-
-    def __unicode__(self): return u'%s' % (self.name)
-
-    def save(self, *args, **kwargs):
-       super(LoadableModule, self).save(*args, **kwargs)
-
-       # This is necessary, as the XOS syncstep handles rerunning the docker-
-       # compose.
-       # TODO: Update synchronizer and replace with watcher functionality
-       if self.xos:
-           # force XOS to rebuild
-           self.xos.save(update_fields=["updated"])
-
-    def get_provides_list(self):
-        prov_list = []
-        if self.provides and self.provides.strip():
-            for prov in self.provides.split(","):
-                prov=prov.strip()
-                if "=" in prov:
-                    (name, version) = prov.split("=",1)
-                    name = name.strip()
-                    version = version.strip()
-                else:
-                    name = prov
-                    version = "1.0.0"
-                prov_list.append( {"name": name, "version": version} )
-
-        # every controller provides itself
-        prov_list.append( {"name": self.name, "version": self.version} )
-
-        return prov_list
-
-
-    @classmethod
-    def dependency_check(cls, dep_list):
-        missing = []
-        satisfied = []
-        operators = {">=": operator.ge,
-                     "<=": operator.le,
-                     ">": operator.gt,
-                     "<": operator.lt,
-                     "!=": operator.ne,
-                     "=": operator.eq}
-        for dep in dep_list:
-            dep = dep.strip()
-            name = dep
-            version = None
-            this_op = None
-            for op in operators.keys():
-                if op in dep:
-                    (name, version) = dep.split(op,1)
-                    name = name.strip()
-                    version = version.strip()
-                    this_op = operators[op]
-                    break
-            found=False
-            scs = ServiceController.objects.all()
-            for sc in scs:
-                for provide in sc.get_provides_list():
-                    if (provide["name"] != name):
-                        continue
-                    if not this_op:
-                        satisfied.append(sc)
-                        found=True
-                        break
-                    elif this_op(LooseVersion(provide["version"]), LooseVersion(version)):
-                        satisfied.append(sc)
-                        found=True
-                        break
-            if not found:
-                missing.append(dep)
-
-        return (satisfied, missing)
-
-class LoadableModuleResource(PlCoreBase):
-    KIND_CHOICES = (('xproto', 'XProto'),
-                    ('models', 'Models'),
-                    ('admin', 'Admin'),
-                    ('admin_template', 'Admin Template'),
-                    ('django_library', 'Django Library'),
-                    ('synchronizer', 'Synchronizer'),
-                    ('rest_service', 'REST API (service)'),
-                    ('rest_tenant', 'REST API (tenant)'),
-                    ('tosca_custom_types', 'Tosca Custom Types'),
-                    ('tosca_resource', 'Tosca Resource'),
-                    ('private_key', 'Private Key'),
-                    ('public_key', 'Public Key'),
-                    ('vendor_js', 'Vendor Javascript'))
-
-    FORMAT_CHOICES = (('xproto', 'XProto'),
-                      ('python', 'Python'),
-                      ('manifest', 'Manifest'),
-                      ('docker', 'Docker Container'),
-                      ('yaml', 'YAML'),
-                      ('raw', 'raw'),
-                      ('javascript', 'Javascript'))
-
-    loadable_module = models.ForeignKey(LoadableModule, related_name='loadable_module_resources',
-                                help_text="The Loadable Module this resource is associated with")
-
-    name = StrippedCharField(max_length=30, help_text="Object Name")
-    subdirectory = StrippedCharField(max_length=1024, help_text="optional subdirectory", null=True, blank=True)
-    kind = StrippedCharField(choices=KIND_CHOICES, max_length=30)
-    format = StrippedCharField(choices=FORMAT_CHOICES, max_length=30)
-    url = StrippedCharField(max_length=1024, help_text="URL of resource", null=True, blank=True)
-
-    def __unicode__(self): return u'%s' % (self.name)
-
-    @property
-    def full_url(self):
-        if self.loadable_module and self.loadable_module.base_url:
-            return urlparse.urljoin(self.loadable_module.base_url, self.url)
-        else:
-            return self.url
-
-class Library(LoadableModule):
-    # for now, it's exactly like a LoadableModule
-    pass
-
-
-class XOSComponent(LoadableModule):
-    # this will define loadable XOS component in the form of containers
-    image = StrippedCharField(max_length=200, help_text="docker image name")
-    command = StrippedCharField(max_length=1024, help_text="docker run command", null=True, blank=True)
-    ports = StrippedCharField(max_length=200, help_text="port binding", null=True, blank=True)
-    extra = StrippedCharField(max_length=200, help_text="extra information needed by containers", null=True, blank=True)
-
-    no_start = models.BooleanField(help_text="Do not start the Component", default=False)
-
-
-class XOSComponentLink(PlCoreBase):
-
-    LINK_KIND = (
-        ('internal', 'Internal'),
-        ('external', 'External')
-    )
-
-    component = models.ForeignKey(XOSComponent, related_name='links', help_text="The Component object for this Link")
-    container = StrippedCharField(max_length=200, help_text="container to link")
-    alias = StrippedCharField(max_length=200, help_text="alias for the link")
-    kind = models.CharField(max_length=20, choices=LINK_KIND, default='internal')
-
-    def save(self, *args, **kwds):
-        # If this is a new object, then check to make sure it doesn't already exist
-        if not self.pk:
-            existing = XOSComponentLink.objects.filter(container=self.container, alias=self.alias)
-            if len(existing) > 0:
-                raise XOSValidationError('XOSComponentLink for %s:%s already defined' % (self.container, self.alias))
-        super(XOSComponentLink, self).save(*args, **kwds)
-
-
-# NOTE can this be the same of XOSVolume??
-class XOSComponentVolume(PlCoreBase):
-    component = models.ForeignKey(XOSComponent, related_name='volumes', help_text="The Component object for this Volume")
-    name = StrippedCharField(max_length=300, help_text="Volume Name")
-    container_path = StrippedCharField(max_length=1024, unique=True, help_text="Path of Volume in Container")
-    host_path = StrippedCharField(max_length=1024, help_text="Path of Volume in Host")
-    read_only = models.BooleanField(default=False, help_text="True if mount read-only")
-
-    def save(self, *args, **kwds):
-        # If this is a new object, then check to make sure it doesn't already exist
-        if not self.pk:
-            existing = XOSComponentVolume.objects.filter(container_path=self.container_path, host_path=self.host_path)
-            if len(existing) > 0:
-                raise XOSValidationError('XOSComponentVolume for %s:%s already defined' % (self.container_path, self.host_path))
-        super(XOSComponentVolume, self).save(*args, **kwds)
-
-
-class XOSComponentVolumeContainer(PlCoreBase):
-    component = models.ForeignKey(XOSComponent, related_name='volumecontainers', help_text="The Component object for this VolumeContainer")
-    name = StrippedCharField(max_length=300, help_text="Volume Name")
-    container = StrippedCharField(max_length=300, help_text="Volume Name")
-
-    def save(self, *args, **kwds):
-        # If this is a new object, then check to make sure it doesn't already exist
-        if not self.pk:
-            existing = XOSComponentVolumeContainer.objects.filter(name=self.name)
-            if len(existing) > 0:
-                raise XOSValidationError('XOSComponentVolumeContainer for %s:%s already defined' % (self.container_path, self.host_path))
-        super(XOSComponentVolumeContainer, self).save(*args, **kwds)
-
-
-class ServiceController(LoadableModule):
-    synchronizer_run = StrippedCharField(max_length=1024, help_text="synchronizer run command", null=True, blank=True)
-    synchronizer_config = StrippedCharField(max_length=1024, help_text="synchronizer config file", null=True, blank=True)
-
-    image = StrippedCharField(max_length=200, help_text="docker image name", null=True, blank=True)
-
-    no_start = models.BooleanField(help_text="Do not start the Synchronizer", default=False)
-    no_build = models.BooleanField(help_text="Do not build the Synchronizer container", default=False)
-    no_deploy = models.BooleanField(help_text="Do not add synchronizer container to onboarding-docker-compose", default=False)
-
-class Service(PlCoreBase, AttributeMixin):
-    # when subclassing a service, redefine KIND to describe the new service
-    KIND = "generic"
-
-    description = models.TextField(
-        max_length=254, null=True, blank=True, help_text="Description of Service")
-    enabled = models.BooleanField(default=True)
-    kind = StrippedCharField(
-        max_length=30, help_text="Kind of service", default=KIND)
-    name = StrippedCharField(max_length=30, help_text="Service Name")
-    versionNumber = StrippedCharField(blank=True, null=True,
-        max_length=30, help_text="Version of Service Definition")   # deprecated
-    published = models.BooleanField(default=True)
-    view_url = StrippedCharField(blank=True, null=True, max_length=1024)
-    icon_url = StrippedCharField(blank=True, null=True, max_length=1024)
-    public_key = models.TextField(
-        null=True, blank=True, max_length=1024, help_text="Public key string")
-    private_key_fn = StrippedCharField(blank=True, null=True, max_length=1024)
-
-    # Service_specific_attribute and service_specific_id are opaque to XOS
-    service_specific_id = StrippedCharField(
-        max_length=30, blank=True, null=True)
-    service_specific_attribute = models.TextField(blank=True, null=True)
-
-    controller = models.ForeignKey(ServiceController, related_name='services',
-                                help_text="The Service Controller this Service uses",
-                                null=True, blank=True)
-
-    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)
-
-    @classmethod
-    def get_service_objects(cls):
-        return cls.objects.filter(kind=cls.KIND)
-
-    @classmethod
-    def get_deleted_service_objects(cls):
-        return cls.deleted_objects.filter(kind=cls.KIND)
-
-    @classmethod
-    def get_service_objects_by_user(cls, user):
-        return cls.select_by_user(user).filter(kind=cls.KIND)
-
-    @classmethod
-    def select_by_user(cls, user):
-        if user.is_admin:
-            return cls.objects.all()
-        else:
-            service_ids = [
-                sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
-            return cls.objects.filter(id__in=service_ids)
-
-    @property
-    def serviceattribute_dict(self):
-        attrs = {}
-        for attr in self.serviceattributes.all():
-            attrs[attr.name] = attr.value
-        return attrs
-
-    def __unicode__(self): return u'%s' % (self.name)
-
-    def can_update(self, user):
-        return user.can_update_service(self, allow=['admin'])
-
-    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
-
-
-class ServiceAttribute(PlCoreBase):
-    name = models.CharField(help_text="Attribute Name", max_length=128)
-    value = models.TextField(help_text="Attribute Value")
-    service = models.ForeignKey(Service, related_name='serviceattributes',
-                                help_text="The Service this attribute is associated with")
-
-
-class ServiceRole(PlCoreBase):
-    ROLE_CHOICES = (('admin', 'Admin'),)
-    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
-
-    def __unicode__(self): return u'%s' % (self.role)
-
-
-class ServicePrivilege(PlCoreBase):
-    user = models.ForeignKey('User', related_name='serviceprivileges')
-    service = models.ForeignKey('Service', related_name='serviceprivileges')
-    role = models.ForeignKey('ServiceRole', related_name='serviceprivileges')
-
-    class Meta:
-        unique_together = ('user', 'service', 'role')
-
-    def __unicode__(self): return u'%s %s %s' % (
-        self.service, self.user, self.role)
-
-    def can_update(self, user):
-        if not self.service.enabled:
-            raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
-        return self.service.can_update(user)
-
-    def save(self, *args, **kwds):
-        if not self.service.enabled:
-            raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
-        super(ServicePrivilege, self).save(*args, **kwds)
-
-    def delete(self, *args, **kwds):
-        if not self.service.enabled:
-            raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
-        super(ServicePrivilege, self).delete(*args, **kwds)
-
-    @classmethod
-    def select_by_user(cls, user):
-        if user.is_admin:
-            qs = cls.objects.all()
-        else:
-            qs = cls.objects.filter(user=user)
-        return qs
-
-#Model that defines the information needed to enable monitoring agents of a service
-class ServiceMonitoringAgentInfo(PlCoreBase):
-    name = models.CharField(help_text="Monitoring Agent Name", max_length=128)
-    service = models.ForeignKey(Service, related_name='servicemonitoringagents', blank=True, null=True,
-                                help_text="The Service this attribute is associated with")
-    target_uri = models.TextField(validators=[URLValidator()], help_text="Monitoring collector URI to be used by agents to publish the data")
-
-
-class TenantRoot(PlCoreBase, AttributeMixin):
-    """ A tenantRoot is one of the things that can sit at the root of a chain
-        of tenancy. This object represents a node.
-    """
-
-    KIND = "generic"
-    kind = StrippedCharField(max_length=30, default=KIND)
-    name = StrippedCharField(
-        max_length=255, help_text="name", blank=True, null=True)
-
-    service_specific_attribute = models.TextField(blank=True, null=True)
-    service_specific_id = StrippedCharField(
-        max_length=30, blank=True, null=True)
-
-    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 __unicode__(self):
-        if not self.name:
-            return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
-        else:
-            return self.name
-
-    def can_update(self, user):
-        return user.can_update_tenant_root(self, allow=['admin'])
-
-    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]
-
-    @classmethod
-    def get_tenant_objects(cls):
-        return cls.objects.filter(kind=cls.KIND)
-
-    @classmethod
-    def get_tenant_objects_by_user(cls, user):
-        return cls.select_by_user(user).filter(kind=cls.KIND)
-
-    @classmethod
-    def select_by_user(cls, user):
-        if user.is_admin:
-            return cls.objects.all()
-        else:
-            tr_ids = [
-                trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
-            return cls.objects.filter(id__in=tr_ids)
-
-    # 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.get_tenant_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"})
-
-
-class Tenant(PlCoreBase, AttributeMixin):
-    """ A tenant is a relationship between two entities, a subscriber and a
-        provider. This object represents an edge.
-
-        The subscriber can be a User, a Service, or a Tenant.
-
-        The provider is always a Service.
-
-        TODO: rename "Tenant" to "Tenancy"
-    """
-
-    CONNECTIVITY_CHOICES = (('public', 'Public'),
-                            ('private', 'Private'),
-                            ('private-unidirectional', 'Private Unidirectional'),
-                            ('na', 'Not Applicable'))
-
-    # when subclassing a service, redefine KIND to describe the new service
-    KIND = "generic"
-
-    name = StrippedCharField(max_length=200, blank=True, null=True)
-    kind = StrippedCharField(max_length=30, default=KIND)
-    provider_service = models.ForeignKey(
-        Service, related_name='provided_tenants')
-
-    # The next four things are the various type of objects that can be subscribers of this Tenancy
-    # relationship. One and only one can be used at a time.
-    # XXX these should really be changed to GenericForeignKey
-    subscriber_service = models.ForeignKey(
-        Service, related_name='subscribed_tenants', blank=True, null=True)
-    subscriber_tenant = models.ForeignKey(
-        "Tenant", related_name='subscribed_tenants', blank=True, null=True)
-    subscriber_user = models.ForeignKey(
-        "User", related_name='subscribed_tenants', blank=True, null=True)
-    subscriber_root = models.ForeignKey(
-        "TenantRoot", related_name="subscribed_tenants", blank=True, null=True)
-    subscriber_network = models.ForeignKey(
-        "Network", related_name="subscribed_tenants", blank=True, null=True)
-
-    # Service_specific_attribute and service_specific_id are opaque to XOS
-    service_specific_id = StrippedCharField(
-        max_length=30, blank=True, null=True)
-    service_specific_attribute = models.TextField(blank=True, null=True)
-
-    # Connect_method is only used by Coarse tenants
-    connect_method = models.CharField(
-        null=False, blank=False, max_length=30, choices=CONNECTIVITY_CHOICES, default="na")
-
-    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)
-
-    def __unicode__(self):
-        return u"%s-tenant-%s" % (str(self.kind), str(self.id))
-
-    @classmethod
-    def get_tenant_objects(cls):
-        return cls.objects.filter(kind=cls.KIND)
-
-    @classmethod
-    def get_tenant_objects_by_user(cls, user):
-        return cls.select_by_user(user).filter(kind=cls.KIND)
-
-    @classmethod
-    def get_deleted_tenant_objects(cls):
-        return cls.deleted_objects.filter(kind=cls.KIND)
-
-    @property
-    def tenantattribute_dict(self):
-        attrs = {}
-        for attr in self.tenantattributes.all():
-            attrs[attr.name] = attr.value
-        return attrs
-
-    # helper function to be used in subclasses that want to ensure
-    # service_specific_id is unique
-    def validate_unique_service_specific_id(self):
-        if self.pk is None:
-            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.get_tenant_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 save(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")
-
-        super(Tenant, self).save(*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]
-
-
-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 TenantWithContainer(Tenant):
-    """ A tenant that manages an Instance
-        Note: Should probably be called TenantWithInstance instead of TenantWithContainer
-    """
-
-    instance = models.ForeignKey("Instance", related_name='+', help_text="Instance used by this Tenant", null=True, blank=True)
-    creator = models.ForeignKey("User", related_name='+', help_text="Creator of this Tenant", null=True, blank=True)
-    external_hostname = StrippedCharField(max_length=30, help_text="External host name", null=True, blank=True) # is this still used?
-    external_container = StrippedCharField(max_length=30, help_text="External host name", null=True, blank=True) # is this still used?
-
-    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.get_tenant_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)
-
-
-class ServiceDependency(Tenant):
-    KIND = COARSE_KIND
-
-    def save(self, *args, **kwargs):
-        if (not self.subscriber_service):
-            raise XOSValidationError("subscriber_service cannot be null")
-        if (self.subscriber_tenant or self.subscriber_user):
-            raise XOSValidationError(
-                "subscriber_tenant and subscriber_user must be null")
-
-        super(ServiceDependency, self).save()
-
-
-class TenantAttribute(PlCoreBase):
-    name = models.CharField(help_text="Attribute Name", max_length=128)
-    value = models.TextField(help_text="Attribute Value")
-    tenant = models.ForeignKey(Tenant, related_name='tenantattributes',
-                               help_text="The Tenant this attribute is associated with")
-
-    def __unicode__(self): return u'%s-%s' % (self.name, self.id)
-
-
-class TenantRootRole(PlCoreBase):
-    ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
-
-    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
-
-    def __unicode__(self): return u'%s' % (self.role)
-
-
-class TenantRootPrivilege(PlCoreBase):
-    user = models.ForeignKey('User', related_name="tenant_root_privileges")
-    tenant_root = models.ForeignKey(
-        'TenantRoot', related_name="tenant_root_privileges")
-    role = models.ForeignKey(
-        'TenantRootRole', related_name="tenant_root_privileges")
-
-    class Meta:
-        unique_together = ('user', 'tenant_root', 'role')
-
-    def __unicode__(self): return u'%s %s %s' % (
-        self.tenant_root, self.user, self.role)
-
-    def save(self, *args, **kwds):
-        if not self.user.is_active:
-            raise PermissionDenied, "Cannot modify role(s) of a disabled user"
-        super(TenantRootPrivilege, self).save(*args, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_tenant_root_privilege(self)
-
-    @classmethod
-    def select_by_user(cls, user):
-        if user.is_admin:
-            return cls.objects.all()
-        else:
-            # User can see his own privilege
-            trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
-
-            # A slice admin can see the SlicePrivileges for his Slice
-            for priv in cls.objects.filter(user=user, role__role="admin"):
-                trp_ids.extend(
-                    [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)])
-
-            return cls.objects.filter(id__in=trp_ids)
-
-
-class TenantRole(PlCoreBase):
-    """A TenantRole option."""
-    ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
-    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
-
-    def __unicode__(self): return u'%s' % (self.role)
-
-
-class TenantPrivilege(PlCoreBase):
-    """"A TenantPrivilege which defines how users can access a particular Tenant.
-
-    Attributes:
-        id (models.AutoField): The ID of the privilege.
-        user (models.ForeignKey): A Foreign Key to the a User.
-        tenant (models.ForeignKey): A ForeignKey to the Tenant.
-        role (models.ForeignKey): A ForeignKey to the TenantRole.
-    """
-    id = models.AutoField(primary_key=True)
-    user = models.ForeignKey('User', related_name="tenantprivileges")
-    tenant = models.ForeignKey('Tenant', related_name="tenantprivileges")
-    role = models.ForeignKey('TenantRole', related_name="tenantprivileges")
-
-    def __unicode__(self): return u'%s %s %s' % (
-        self.tenant, self.user, self.role)
-
-    def save(self, *args, **kwds):
-        if not self.user.is_active:
-            raise PermissionDenied, "Cannot modify role(s) of a disabled user"
-        super(TenantPrivilege, self).save(*args, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_tenant_privilege(self)
-
-    @classmethod
-    def select_by_user(cls, user):
-        if user.is_admin:
-            return cls.objects.all()
-        else:
-            # User can see his own privilege
-            trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
-
-            # A tenant admin can see the TenantPrivileges for their Tenants
-            for priv in cls.objects.filter(user=user, role__role="admin"):
-                trp_ids.extend(
-                    [trp.id for trp in cls.objects.filter(tenant=priv.tenant)])
-
-            return cls.objects.filter(id__in=trp_ids)
diff --git a/xos/core/models/service.xproto b/xos/core/models/service.xproto
new file mode 100644
index 0000000..e3ba5bb
--- /dev/null
+++ b/xos/core/models/service.xproto
@@ -0,0 +1,17 @@
+
+
+message Service (PlCoreBase,AttributeMixin){
+     optional string description = 1 [help_text = "Description of Service", max_length = 254, null = True, db_index = False, blank = True];
+     required bool enabled = 2 [default = True, null = False, db_index = False, blank = True];
+     required string kind = 3 [default = "generic", max_length = 30, content_type = "stripped", blank = False, help_text = "Kind of service", null = False, db_index = False];
+     required string name = 4 [max_length = 30, content_type = "stripped", blank = False, help_text = "Service Name", null = False, db_index = False];
+     optional string versionNumber = 5 [max_length = 30, content_type = "stripped", blank = True, help_text = "Version of Service Definition", null = True, db_index = False];
+     required bool published = 6 [default = True, null = False, db_index = False, blank = True];
+     optional string view_url = 7 [db_index = False, max_length = 1024, null = True, content_type = "stripped", blank = True];
+     optional string icon_url = 8 [db_index = False, max_length = 1024, null = True, content_type = "stripped", blank = True];
+     optional string public_key = 9 [help_text = "Public key string", max_length = 1024, null = True, db_index = False, blank = True];
+     optional string private_key_fn = 10 [db_index = False, max_length = 1024, null = True, content_type = "stripped", blank = True];
+     optional string service_specific_id = 11 [db_index = False, max_length = 30, null = True, content_type = "stripped", blank = True];
+     optional string service_specific_attribute = 12 [db_index = False, null = True, blank = True];
+     optional manytoone controller->ServiceController:services = 13 [help_text = "The Service Controller this Service uses", null = True, db_index = True, blank = True];
+}
diff --git a/xos/core/models/service_header.py b/xos/core/models/service_header.py
new file mode 120000
index 0000000..a90002d
--- /dev/null
+++ b/xos/core/models/service_header.py
@@ -0,0 +1 @@
+attic/service_header.py
\ No newline at end of file
diff --git a/xos/core/models/serviceattribute.xproto b/xos/core/models/serviceattribute.xproto
new file mode 100644
index 0000000..d333aa2
--- /dev/null
+++ b/xos/core/models/serviceattribute.xproto
@@ -0,0 +1,7 @@
+
+
+message ServiceAttribute (PlCoreBase){
+     required string name = 1 [help_text = "Attribute Name", max_length = 128, null = False, db_index = False, blank = False];
+     required string value = 2 [help_text = "Attribute Value", null = False, db_index = False, blank = False];
+     required manytoone service->Service:serviceattributes = 3 [help_text = "The Service this attribute is associated with", null = False, db_index = True, blank = False];
+}
diff --git a/xos/core/models/serviceclass.py b/xos/core/models/serviceclass.py
deleted file mode 100644
index a51b476..0000000
--- a/xos/core/models/serviceclass.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase
-from core.models.plcorebase import StrippedCharField
-
-def get_default_serviceclass():
-    try:
-        return ServiceClass.objects.get(name="Best Effort")
-    except ServiceClass.DoesNotExist:
-        return None
-
-class ServiceClass(PlCoreBase):
-    name = StrippedCharField(max_length=32)
-    description = StrippedCharField(max_length=255)
-    commitment = models.IntegerField(default=365)
-    membershipFee = models.IntegerField(default=0)
-    membershipFeeMonths = models.IntegerField(default=12)
-    upgradeRequiresApproval = models.BooleanField(default=False)
-    upgradeFrom = models.ManyToManyField('self', blank=True)
-
-    class Meta(PlCoreBase.Meta):
-       verbose_name_plural = "Service classes"
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-    def save_by_user(self, user, *args, **kwds):
-        if self.can_update(user):
-            super(ServiceClass, self).save(*args, **kwds)
diff --git a/xos/core/models/serviceclass.xproto b/xos/core/models/serviceclass.xproto
new file mode 100644
index 0000000..9f1e4cf
--- /dev/null
+++ b/xos/core/models/serviceclass.xproto
@@ -0,0 +1,11 @@
+
+
+message ServiceClass (PlCoreBase){
+     required string name = 1 [db_index = False, max_length = 32, null = False, content_type = "stripped", blank = False];
+     required string description = 2 [db_index = False, max_length = 255, null = False, content_type = "stripped", blank = False];
+     required int32 commitment = 3 [default = 365, null = False, db_index = False, blank = False];
+     required int32 membershipFee = 4 [default = 0, null = False, db_index = False, blank = False];
+     required int32 membershipFeeMonths = 5 [default = 12, null = False, db_index = False, blank = False];
+     required bool upgradeRequiresApproval = 6 [default = False, null = False, db_index = False, blank = True];
+     required manytomany upgradeFrom->ServiceClass/ServiceClass_upgradeFrom:upgradeFrom_rel_+ = 7 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/servicecontroller.xproto b/xos/core/models/servicecontroller.xproto
new file mode 100644
index 0000000..aa4baaa
--- /dev/null
+++ b/xos/core/models/servicecontroller.xproto
@@ -0,0 +1,10 @@
+
+
+message ServiceController (LoadableModule){
+     optional string synchronizer_run = 1 [max_length = 1024, content_type = "stripped", blank = True, help_text = "synchronizer run command", null = True, db_index = False];
+     optional string synchronizer_config = 2 [max_length = 1024, content_type = "stripped", blank = True, help_text = "synchronizer config file", null = True, db_index = False];
+     optional string image = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "docker image name", null = True, db_index = False];
+     required bool no_start = 4 [help_text = "Do not start the Synchronizer", default = False, null = False, db_index = False, blank = True];
+     required bool no_build = 5 [help_text = "Do not build the Synchronizer container", default = False, null = False, db_index = False, blank = True];
+     required bool no_deploy = 6 [help_text = "Do not add synchronizer container to onboarding-docker-compose", default = False, null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/servicedependency.xproto b/xos/core/models/servicedependency.xproto
new file mode 100644
index 0000000..09248fe
--- /dev/null
+++ b/xos/core/models/servicedependency.xproto
@@ -0,0 +1,4 @@
+
+
+message ServiceDependency (Tenant){
+}
diff --git a/xos/core/models/servicemonitoringagentinfo.xproto b/xos/core/models/servicemonitoringagentinfo.xproto
new file mode 100644
index 0000000..4a4e466
--- /dev/null
+++ b/xos/core/models/servicemonitoringagentinfo.xproto
@@ -0,0 +1,7 @@
+
+
+message ServiceMonitoringAgentInfo (PlCoreBase){
+     required string name = 1 [help_text = "Monitoring Agent Name", max_length = 128, null = False, db_index = False, blank = False];
+     optional manytoone service->Service:servicemonitoringagents = 2 [help_text = "The Service this attribute is associated with", null = True, db_index = True, blank = True];
+     required string target_uri = 3 [help_text = "Monitoring collector URI to be used by agents to publish the data", null = False, db_index = False, blank = False];
+}
diff --git a/xos/core/models/serviceprivilege.xproto b/xos/core/models/serviceprivilege.xproto
new file mode 100644
index 0000000..f88acef
--- /dev/null
+++ b/xos/core/models/serviceprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message ServicePrivilege (PlCoreBase){
+     required manytoone user->User:serviceprivileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone service->Service:serviceprivileges = 2 [db_index = True, null = False, blank = False];
+     required manytoone role->ServiceRole:serviceprivileges = 3 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/serviceresource.py b/xos/core/models/serviceresource.py
deleted file mode 100644
index 416627a..0000000
--- a/xos/core/models/serviceresource.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase
-from core.models import ServiceClass
-from core.models.plcorebase import StrippedCharField
-
-# Create your models here.
-
-class ServiceResource(PlCoreBase):
-    serviceClass = models.ForeignKey(ServiceClass, related_name = "serviceresources")
-    name = StrippedCharField(max_length=32)
-    maxUnitsDeployment = models.IntegerField(default=1)
-    maxUnitsNode = models.IntegerField(default=1)
-    maxDuration = models.IntegerField(default=1)
-    bucketInRate = models.IntegerField(default=0)
-    bucketMaxSize = models.IntegerField(default=0)
-    cost = models.IntegerField(default=0)
-    calendarReservable = models.BooleanField(default=True)
-
-    def __unicode__(self):  return u'%s' % (self.name)
diff --git a/xos/core/models/serviceresource.xproto b/xos/core/models/serviceresource.xproto
new file mode 100644
index 0000000..46cb5c4
--- /dev/null
+++ b/xos/core/models/serviceresource.xproto
@@ -0,0 +1,13 @@
+
+
+message ServiceResource (PlCoreBase){
+     required manytoone serviceClass->ServiceClass:serviceresources = 1 [db_index = True, null = False, blank = False];
+     required string name = 2 [db_index = False, max_length = 32, null = False, content_type = "stripped", blank = False];
+     required int32 maxUnitsDeployment = 3 [default = 1, null = False, db_index = False, blank = False];
+     required int32 maxUnitsNode = 4 [default = 1, null = False, db_index = False, blank = False];
+     required int32 maxDuration = 5 [default = 1, null = False, db_index = False, blank = False];
+     required int32 bucketInRate = 6 [default = 0, null = False, db_index = False, blank = False];
+     required int32 bucketMaxSize = 7 [default = 0, null = False, db_index = False, blank = False];
+     required int32 cost = 8 [default = 0, null = False, db_index = False, blank = False];
+     required bool calendarReservable = 9 [default = True, null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/servicerole.xproto b/xos/core/models/servicerole.xproto
new file mode 100644
index 0000000..3661df4
--- /dev/null
+++ b/xos/core/models/servicerole.xproto
@@ -0,0 +1,5 @@
+
+
+message ServiceRole (PlCoreBase){
+     required string role = 1 [choices = "(('admin', 'Admin'),)", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/site.py b/xos/core/models/site.py
deleted file mode 100644
index 069fec9..0000000
--- a/xos/core/models/site.py
+++ /dev/null
@@ -1,342 +0,0 @@
-import os
-from django.db import models
-from django.db.models import Q
-from django.contrib.contenttypes.fields import GenericRelation
-from django.core.exceptions import PermissionDenied
-# from geoposition.fields import GeopositionField
-from core.models import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,ModelLink
-from core.models import Tag
-from core.models.plcorebase import StrippedCharField
-from core.acl import AccessControlList
-from xos.config import Config
-
-config = Config()
-
-class ControllerLinkDeletionManager(PlCoreBaseDeletionManager):
-    def get_queryset(self):
-        parent=super(ControllerLinkDeletionManager, self)
-        try:
-            backend_type = config.observer_backend_type
-        except AttributeError:
-            backend_type = None
-
-        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
-        if (backend_type):
-            return parent_queryset.filter(Q(controller__backend_type=backend_type))
-        else:
-            return parent_queryset
-
-    # deprecated in django 1.7 in favor of get_queryset().
-    def get_query_set(self):
-        return self.get_queryset()
-
-
-class ControllerDeletionManager(PlCoreBaseDeletionManager):
-    def get_queryset(self):
-        parent=super(ControllerDeletionManager, self)
-
-        try:
-            backend_type = config.observer_backend_type
-        except AttributeError:
-            backend_type = None
-
-        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
-
-        if backend_type:
-            return parent_queryset.filter(Q(backend_type=backend_type))
-        else:
-            return parent_queryset
-
-    # deprecated in django 1.7 in favor of get_queryset().
-    def get_query_set(self):
-        return self.get_queryset()
-
-class ControllerLinkManager(PlCoreBaseManager):
-    def get_queryset(self):
-        parent=super(ControllerLinkManager, self)
-
-        try:
-            backend_type = config.observer_backend_type
-        except AttributeError:
-            backend_type = None
-
-        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
-
-        if backend_type:
-            return parent_queryset.filter(Q(controller__backend_type=backend_type))
-        else:
-            return parent_queryset
-
-    # deprecated in django 1.7 in favor of get_queryset().
-    def get_query_set(self):
-        return self.get_queryset()
-
-
-class ControllerManager(PlCoreBaseManager):
-    def get_queryset(self):
-        parent=super(ControllerManager, self)
-
-        try:
-            backend_type = config.observer_backend_type
-        except AttributeError:
-            backend_type = None
-
-        parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
-
-        if backend_type:
-            return parent_queryset.filter(Q(backend_type=backend_type))
-        else:
-            return parent_queryset
-
-    # deprecated in django 1.7 in favor of get_queryset().
-    def get_query_set(self):
-        return self.get_queryset()
-
-class Site(PlCoreBase):
-    """
-        A logical grouping of Nodes that are co-located at the same geographic location, which also typically corresponds to the Nodes' location in the physical network.
-    """
-    name = StrippedCharField(max_length=200, help_text="Name for this Site")
-    site_url = models.URLField(null=True, blank=True, max_length=512, help_text="Site's Home URL Page")
-    enabled = models.BooleanField(default=True, help_text="Status for this Site")
-    hosts_nodes = models.BooleanField(default=True, help_text="Indicates whether or not the site host nodes")
-    hosts_users = models.BooleanField(default=True, help_text="Indicates whether or not the site manages user accounts")
-    # location = GeopositionField()
-    longitude = models.FloatField(null=True, blank=True)
-    latitude = models.FloatField(null=True, blank=True)
-    login_base = StrippedCharField(max_length=50, unique=True, help_text="Prefix for Slices associated with this Site")
-    is_public = models.BooleanField(default=True, help_text="Indicates the visibility of this site to other members")
-    abbreviated_name = StrippedCharField(max_length=80)
-
-    #deployments = models.ManyToManyField('Deployment', blank=True, related_name='sites')
-    deployments = models.ManyToManyField('Deployment', through='SiteDeployment', blank=True, help_text="Select which sites are allowed to host nodes in this deployment", related_name='sites')
-    tags = GenericRelation(Tag)
-
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-    def can_update(self, user):
-        return user.can_update_site(self, allow=['pi'])
-
-class SiteRole(PlCoreBase):
-
-    ROLE_CHOICES = (('admin','Admin'),('pi','PI'),('tech','Tech'),('billing','Billing'))
-    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
-
-    def __unicode__(self):  return u'%s' % (self.role)
-
-class SitePrivilege(PlCoreBase):
-
-    user = models.ForeignKey('User', related_name='siteprivileges')
-    site = models.ForeignKey('Site', related_name='siteprivileges')
-    role = models.ForeignKey('SiteRole',related_name='siteprivileges')
-
-    xos_links = [ModelLink(Site,'site'),ModelLink('User','user'),ModelLink('Role','role')]
-
-    def __unicode__(self):  return u'%s %s %s' % (self.site, self.user, self.role)
-
-    def save(self, *args, **kwds):
-        if not self.user.is_active:
-            raise PermissionDenied, "Cannot modify role(s) of a disabled user"
-        super(SitePrivilege, self).save(*args, **kwds)
-
-    def delete(self, *args, **kwds):
-        super(SitePrivilege, self).delete(*args, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_site(self, allow=['pi'])
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = SitePrivilege.objects.all()
-        else:
-            sp_ids = [sp.id for sp in SitePrivilege.objects.filter(user=user)]
-            qs = SitePrivilege.objects.filter(id__in=sp_ids)
-        return qs
-
-class Deployment(PlCoreBase):
-    #objects = Controllermanager()
-    #deleted_objects = DeploymentDeletionManager()
-    name = StrippedCharField(max_length=200, unique=True, help_text="Name of the Deployment")
-    #admin_user = StrippedCharField(max_length=200, null=True, blank=True, help_text="Username of an admin user at this deployment")
-    #admin_password = StrippedCharField(max_length=200, null=True, blank=True, help_text="Password of theadmin user at this deployment")
-    #admin_tenant = StrippedCharField(max_length=200, null=True, blank=True, help_text="Name of the tenant the admin user belongs to")
-    #auth_url = StrippedCharField(max_length=200, null=True, blank=True, help_text="Auth url for the deployment")
-    #backend_type = StrippedCharField(max_length=200, null=True, blank=True, help_text="Type of deployment, e.g. EC2, OpenStack, or OpenStack version")
-    #availability_zone = StrippedCharField(max_length=200, null=True, blank=True, help_text="OpenStack availability zone")
-
-    # smbaker: the default of 'allow all' is intended for evolutions of existing
-    #    deployments. When new deployments are created via the GUI, they are
-    #    given a default of 'allow site <site_of_creator>'
-    accessControl = models.TextField(max_length=200, blank=False, null=False, default="allow all",
-                                     help_text="Access control list that specifies which sites/users may use nodes in this deployment")
-    def __init__(self, *args, **kwargs):
-        super(Deployment, self).__init__(*args, **kwargs)
-        self.no_sync=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)
-
-    def can_update(self, user):
-        return user.can_update_deployment(self)
-    
-    def __unicode__(self):  return u'%s' % (self.name)
-
-class DeploymentRole(PlCoreBase):
-    #objects = DeploymentLinkManager()
-    #deleted_objects = DeploymentLinkDeletionManager()
-    ROLE_CHOICES = (('admin','Admin'),)
-    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
-
-    def __unicode__(self):  return u'%s' % (self.role)
-
-class DeploymentPrivilege(PlCoreBase):
-    #objects = DeploymentLinkManager()
-    #deleted_objects = DeploymentLinkDeletionManager()
-
-    user = models.ForeignKey('User', related_name='deploymentprivileges')
-    deployment = models.ForeignKey('Deployment', related_name='deploymentprivileges')
-    role = models.ForeignKey('DeploymentRole',related_name='deploymentprivileges')
-
-    xos_links = [ModelLink(Deployment,'deployment'),ModelLink('User','user'),ModelLink('Role','role')]
-
-    class Meta:
-        unique_together = ('user', 'deployment', 'role')
-
-    def __unicode__(self):  return u'%s %s %s' % (self.deployment, self.user, self.role)
-
-    def can_update(self, user):
-        return user.can_update_deployment(self)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = DeploymentPrivilege.objects.all()
-        else:
-            dpriv_ids = [dp.id for dp in DeploymentPrivilege.objects.filter(user=user)]
-            qs = DeploymentPrivilege.objects.filter(id__in=dpriv_ids)
-        return qs
-
-class ControllerRole(PlCoreBase):
-    #objects = ControllerLinkManager()
-    #deleted_objects = ControllerLinkDeletionManager()
-
-    ROLE_CHOICES = (('admin','Admin'),)
-    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
-
-    def __unicode__(self):  return u'%s' % (self.role)
-
-class Controller(PlCoreBase):
-
-    objects = ControllerManager()
-    deleted_objects = ControllerDeletionManager()
-
-    name = StrippedCharField(max_length=200, unique=True, help_text="Name of the Controller")
-    backend_type = StrippedCharField(max_length=200, help_text="Type of compute controller, e.g. EC2, OpenStack, or OpenStack version")
-    version = StrippedCharField(max_length=200, help_text="Controller version")
-    auth_url = StrippedCharField(max_length=200, null=True, blank=True, help_text="Auth url for the compute controller")
-    admin_user = StrippedCharField(max_length=200, null=True, blank=True, help_text="Username of an admin user at this controller")
-    admin_password = StrippedCharField(max_length=200, null=True, blank=True, help_text="Password of theadmin user at this controller")
-    admin_tenant = StrippedCharField(max_length=200, null=True, blank=True, help_text="Name of the tenant the admin user belongs to")
-    domain = StrippedCharField(max_length=200, null=True, blank=True, help_text="Name of the domain this controller belongs to")
-    rabbit_host = StrippedCharField(max_length=200, null=True, blank=True, help_text="IP address of rabbitmq server at this controller")
-    rabbit_user = StrippedCharField(max_length=200, null=True, blank=True, help_text="Username of rabbitmq server at this controller")
-    rabbit_password = StrippedCharField(max_length=200, null=True, blank=True, help_text="Password of rabbitmq server at this controller")
-    deployment = models.ForeignKey(Deployment,related_name='controllerdeployments')
-
-    xos_links = [ModelLink(Deployment, via='deployment')]
-
-    def __init__(self, *args, **kwargs):
-        super(Controller, self).__init__(*args, **kwargs)
-        self.no_sync=True
-
-    def __unicode__(self):  return u'%s %s %s' % (self.name, self.backend_type, self.version)
-
-    @property
-    def auth_url_v3(self):
-        if self.auth_url and self.auth_url[-1] == '/':
-            return '{}/v3/'.format('/'.join(self.auth_url.split('/')[:-2]))
-        else:
-            return '{}/v3/'.format('/'.join(self.auth_url.split('/')[:-1]))
-
-    @staticmethod
-    def select_by_user(user):
-
-        if user.is_admin:
-            qs = Controller.objects.all()
-        else:
-            deployments = [dp.deployment for dp in DeploymentPrivilege.objects.filter(user=user, role__role__in=['Admin', 'admin'])]
-            qs = Controller.objects.filter(deployment__in=deployments)
-        return qs
-
-class SiteDeployment(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-
-    site = models.ForeignKey(Site,related_name='sitedeployments')
-    deployment = models.ForeignKey(Deployment,related_name='sitedeployments')
-    controller = models.ForeignKey(Controller, null=True, blank=True, related_name='sitedeployments')
-    availability_zone = StrippedCharField(max_length=200, null=True, blank=True, help_text="OpenStack availability zone")
-
-    xos_links = [ModelLink(Site,'site'),ModelLink(Deployment,'deployment'),ModelLink(Controller,'controller')]
-
-    class Meta:
-        unique_together = ('site', 'deployment', 'controller')
-
-    def __unicode__(self):  return u'%s %s' % (self.deployment, self.site)
-    
-class ControllerSite(PlCoreBase):
-     
-    site = models.ForeignKey(Site,related_name='controllersite')
-    controller = models.ForeignKey(Controller, null=True, blank=True, related_name='controllersite')
-    tenant_id = StrippedCharField(null=True, blank=True, max_length=200, db_index=True, help_text="Keystone tenant id")
-
-    xos_links = [ModelLink(Controller,via='controller'),ModelLink(Site,via='site')]
-
-    def delete(self, *args, **kwds):
-        super(ControllerSite, self).delete(*args, **kwds)
-
-    
-    class Meta:
-        unique_together = ('site', 'controller') 
-
-class Diag(PlCoreBase):
-    name = StrippedCharField(max_length=200, help_text="Name of the synchronizer")
-    
-    @property
-    def enacted(self):
-        return None
-
-    @enacted.setter
-    def enacted(self, value):
-        pass # Ignore sets, Diag objects are always pending.
diff --git a/xos/core/models/site.xproto b/xos/core/models/site.xproto
new file mode 100644
index 0000000..fe3f9f5
--- /dev/null
+++ b/xos/core/models/site.xproto
@@ -0,0 +1,16 @@
+
+
+message Site (PlCoreBase){
+     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];
+     required bool enabled = 3 [help_text = "Status for this Site", default = True, null = False, db_index = False, blank = True];
+     required bool hosts_nodes = 4 [help_text = "Indicates whether or not the site host nodes", default = True, null = False, db_index = False, blank = True];
+     required bool hosts_users = 5 [help_text = "Indicates whether or not the site manages user accounts", default = True, null = False, db_index = False, blank = True];
+     optional float longitude = 6 [db_index = False, null = True, blank = True];
+     optional float latitude = 7 [db_index = False, null = True, blank = True];
+     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];
+     required manytomany tags->Tag = 12 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/sitecredential.xproto b/xos/core/models/sitecredential.xproto
new file mode 100644
index 0000000..f6a3fec
--- /dev/null
+++ b/xos/core/models/sitecredential.xproto
@@ -0,0 +1,8 @@
+
+
+message SiteCredential (PlCoreBase){
+     required manytoone site->Site:sitecredentials = 1 [help_text = "The User this credential is associated with", null = False, db_index = True, blank = False];
+     required string name = 2 [help_text = "The credential type, e.g. ec2", max_length = 128, null = False, db_index = True, blank = False];
+     required string key_id = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "The backend id of this credential", null = False, db_index = False];
+     required string enc_value = 4 [help_text = "The key value of this credential", max_length = 1024, null = False, db_index = False, blank = False];
+}
diff --git a/xos/core/models/sitedeployment.xproto b/xos/core/models/sitedeployment.xproto
new file mode 100644
index 0000000..ae12aa1
--- /dev/null
+++ b/xos/core/models/sitedeployment.xproto
@@ -0,0 +1,8 @@
+
+
+message SiteDeployment (PlCoreBase){
+     required manytoone site->Site:sitedeployments = 1 [db_index = True, null = False, blank = False];
+     required manytoone deployment->Deployment:sitedeployments = 2 [db_index = True, null = False, blank = False];
+     optional manytoone controller->Controller:sitedeployments = 3 [db_index = True, null = True, blank = True];
+     optional string availability_zone = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "OpenStack availability zone", null = True, db_index = False];
+}
diff --git a/xos/core/models/siteprivilege.xproto b/xos/core/models/siteprivilege.xproto
new file mode 100644
index 0000000..c63677a
--- /dev/null
+++ b/xos/core/models/siteprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message SitePrivilege (PlCoreBase){
+     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];
+     required manytoone role->SiteRole:siteprivileges = 3 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/siterole.xproto b/xos/core/models/siterole.xproto
new file mode 100644
index 0000000..a42ef5c
--- /dev/null
+++ b/xos/core/models/siterole.xproto
@@ -0,0 +1,5 @@
+
+
+message SiteRole (PlCoreBase){
+     required string role = 1 [choices = "(('admin', 'Admin'), ('pi', 'PI'), ('tech', 'Tech'), ('billing', 'Billing'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/slice.py b/xos/core/models/slice.py
deleted file mode 100644
index 183241f..0000000
--- a/xos/core/models/slice.py
+++ /dev/null
@@ -1,217 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase,ModelLink
-from core.models import Site
-from core.models.site import SitePrivilege
-from core.models import User
-from core.models import Role
-from core.models import Controller,ControllerLinkManager,ControllerLinkDeletionManager
-from core.models import ServiceClass
-#from core.models.serviceclass import get_default_serviceclass
-from core.models import Tag
-from django.contrib.contenttypes.fields import GenericRelation
-from core.models import Service
-from core.models import Controller
-from core.models.node import Node
-from core.models import Flavor, Image
-from core.models.plcorebase import StrippedCharField
-from django.core.exceptions import PermissionDenied, ValidationError
-from xos.exceptions import *
-
-# Create your models here.
-
-class Slice(PlCoreBase):
-    ISOLATION_CHOICES = (('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))
-    NETWORK_CHOICES = ((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))
-
-    name = StrippedCharField(unique=True, help_text="The Name of the Slice", max_length=80)
-    enabled = models.BooleanField(default=True, help_text="Status for this Slice")
-    omf_friendly = models.BooleanField(default=False)
-    description=models.TextField(blank=True,help_text="High level description of the slice and expected activities", max_length=1024)
-    slice_url = models.URLField(blank=True, max_length=512)
-    site = models.ForeignKey(Site, related_name='slices', help_text="The Site this Slice belongs to")
-    max_instances = models.IntegerField(default=10)
-    service = models.ForeignKey(Service, related_name='slices', null=True, blank=True)
-    network = models.CharField(null=True, blank=True, max_length=256, choices=NETWORK_CHOICES)
-    exposed_ports = models.CharField(null=True, blank=True, max_length=256)
-    tags = GenericRelation(Tag)
-    serviceClass = models.ForeignKey(ServiceClass, related_name = "slices", null=True, blank=True)  # DEPRECATED
-    creator = models.ForeignKey(User, related_name='slices', blank=True, null=True)
-
-    # for tenant view
-    default_flavor = models.ForeignKey(Flavor, related_name = "slices", null=True, blank=True)
-    default_image = models.ForeignKey(Image, related_name = "slices", null=True, blank=True);
-    default_node = models.ForeignKey(Node, related_name = "slices", null=True, blank=True)
-    mount_data_sets = StrippedCharField(default="GenBank",null=True, blank=True, max_length=256)
-
-    default_isolation = models.CharField(null=False, blank=False, max_length=30, choices=ISOLATION_CHOICES, default="vm")
-    xos_links = [ModelLink(Site,'site'),ModelLink(User,'user')]
-
-    def __unicode__(self):  return u'%s' % (self.name)
-
-    @property
-    def slicename(self):
-        return "%s_%s" % (self.site.login_base, self.name)
-
-    def save(self, *args, **kwds):
-        site = Site.objects.get(id=self.site.id)
-        # allow preexisting slices to keep their original name for now
-        if not self.id and not self.name.startswith(site.login_base):
-            raise XOSValidationError('slice name must begin with %s' % site.login_base)
-
-        if self.name == site.login_base+"_":
-            raise XOSValidationError('slice name is too short')
-
-        if " " in self.name:
-            raise XOSValidationError('slice name must not contain spaces')
-
-        # 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")
-        
-        if not self.creator:
-            raise XOSValidationError('slice has no 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, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_slice(self)
-
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = Slice.objects.all()
-        else:
-            # users can see slices they belong to 
-            slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user)]
-            # pis and admins can see slices at their sites
-            sites = [sp.site for sp in SitePrivilege.objects.filter(user=user)\
-                        if (sp.role.role == 'pi') or (sp.role.role == 'admin')]
-            slice_ids.extend([s.id for s in Slice.objects.filter(site__in=sites)])
-            qs = Slice.objects.filter(id__in=slice_ids)
-        return qs
-
-    """
-    def delete(self, *args, **kwds):
-        # delete networks associated with this slice
-        from core.models.network import Network
-        nets = Network.objects.filter(slices=self)
-        nets.delete() 
-        # delete slice controllers
-        slice_controllers = ControllerSlice.objects.filter(slice=self)
-        slice_controllers.delete()
-        # delete slice privilege
-        slice_privileges = SlicePrivilege.objects.filter(slice=self)
-        slice_privileges.delete() 
-        # continue with normal delete
-        super(Slice, self).delete(*args, **kwds) 
-    """
-         
-
-class SliceRole(PlCoreBase):
-    ROLE_CHOICES = (('admin','Admin'),('default','Default'))
-
-    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
-
-    def __unicode__(self):  return u'%s' % (self.role)
-
-class SlicePrivilege(PlCoreBase):
-    user = models.ForeignKey('User', related_name='sliceprivileges')
-    slice = models.ForeignKey('Slice', related_name='sliceprivileges')
-    role = models.ForeignKey('SliceRole',related_name='sliceprivileges')
-
-    xos_links=[ModelLink(User,via='user'),ModelLink(Slice,via='slice'),ModelLink(Role,via='role')]
-
-    class Meta:
-        unique_together = ('user', 'slice', 'role')
-
-    def __unicode__(self):  return u'%s %s %s' % (self.slice, self.user, self.role)
-
-    def save(self, *args, **kwds):
-        if not self.user.is_active:
-            raise PermissionDenied, "Cannot modify role(s) of a disabled user"
-        super(SlicePrivilege, self).save(*args, **kwds)
-
-    def can_update(self, user):
-        return user.can_update_slice(self.slice)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = SlicePrivilege.objects.all()
-        else:
-            # You can see your own SlicePrivileges
-            sp_ids = [sp.id for sp in SlicePrivilege.objects.filter(user=user)]
-
-            # A site pi or site admin can see the SlicePrivileges for all slices in his Site
-            for priv in SitePrivilege.objects.filter(user=user):
-                if priv.role.role in ['pi', 'admin']:
-                    sp_ids.extend( [sp.id for sp in SlicePrivilege.objects.filter(slice__site = priv.site)] )
-
-            # A slice admin can see the SlicePrivileges for his Slice
-            for priv in SlicePrivilege.objects.filter(user=user, role__role="admin"):
-                sp_ids.extend( [sp.id for sp in SlicePrivilege.objects.filter(slice=priv.slice)] )
-
-            qs = SlicePrivilege.objects.filter(id__in=sp_ids)
-        return qs
-
-class ControllerSlice(PlCoreBase):
-    objects = ControllerLinkManager()
-    deleted_objects = ControllerLinkDeletionManager()
-
-    controller = models.ForeignKey(Controller, related_name='controllerslices')
-    slice = models.ForeignKey(Slice, related_name='controllerslices')
-    tenant_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Keystone tenant id")
-    
-    xos_links = [ModelLink(Controller,via='controller'),ModelLink(Slice,via='slice')]
-
-    class Meta:
-        unique_together = ('controller', 'slice')
-     
-    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
-
-    def __unicode__(self):  return u'%s %s'  % (self.slice, self.controller)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = ControllerSlice.objects.all()
-        else:
-            slices = Slice.select_by_user(user)
-            qs = ControllerSlice.objects.filter(slice__in=slices)
-        return qs    
-
-    def get_cpu_stats(self):
-        filter = 'project_id=%s'%self.tenant_id
-        return monitor.get_meter('cpu',filter,None)
-
-    def get_bw_stats(self):
-        filter = 'project_id=%s'%self.tenant_id
-        return monitor.get_meter('network.outgoing.bytes',filter,None)
-
-    def get_node_stats(self):
-        return len(self.slice.instances)
diff --git a/xos/core/models/slice.xproto b/xos/core/models/slice.xproto
new file mode 100644
index 0000000..5b1acaa
--- /dev/null
+++ b/xos/core/models/slice.xproto
@@ -0,0 +1,20 @@
+message Slice (PlCoreBase){
+     required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
+     required bool enabled = 2 [help_text = "Status for this Slice", default = True, null = False, db_index = False, blank = True];
+     required bool omf_friendly = 3 [default = False, null = False, db_index = False, blank = True];
+     required string description = 4 [help_text = "High level description of the slice and expected activities", max_length = 1024, null = False, db_index = False, blank = True];
+     required string slice_url = 5 [db_index = False, max_length = 512, null = False, content_type = "url", blank = True];
+     required manytoone site->Site:slices = 6 [help_text = "The Site this Slice belongs to", null = False, db_index = True, blank = False];
+     required int32 max_instances = 7 [default = 10, null = False, db_index = False, blank = False];
+     optional manytoone service->Service:slices = 8 [db_index = True, null = True, blank = True];
+     optional string network = 9 [blank = True, max_length = 256, null = True, db_index = False, choices = "((None, 'Default'), ('host', 'Host'), ('bridged', 'Bridged'), ('noauto', 'No Automatic Networks'))"];
+     optional string exposed_ports = 10 [db_index = False, max_length = 256, null = True, blank = True];
+     optional manytoone serviceClass->ServiceClass:slices = 11 [db_index = True, null = True, blank = True];
+     optional manytoone creator->User:slices = 12 [db_index = True, null = True, blank = True];
+     optional manytoone default_flavor->Flavor:slices = 13 [db_index = True, null = True, blank = True];
+     optional manytoone default_image->Image:slices = 14 [db_index = True, null = True, blank = True];
+     optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
+     optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
+     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];
+     required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
+}
diff --git a/xos/core/models/slicecredential.xproto b/xos/core/models/slicecredential.xproto
new file mode 100644
index 0000000..3ae9ed9
--- /dev/null
+++ b/xos/core/models/slicecredential.xproto
@@ -0,0 +1,8 @@
+
+
+message SliceCredential (PlCoreBase){
+     required manytoone slice->Slice:slicecredentials = 1 [help_text = "The User this credential is associated with", null = False, db_index = True, blank = False];
+     required string name = 2 [help_text = "The credential type, e.g. ec2", max_length = 128, null = False, db_index = True, blank = False];
+     required string key_id = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "The backend id of this credential", null = False, db_index = False];
+     required string enc_value = 4 [help_text = "The key value of this credential", max_length = 1024, null = False, db_index = False, blank = False];
+}
diff --git a/xos/core/models/sliceprivilege.xproto b/xos/core/models/sliceprivilege.xproto
new file mode 100644
index 0000000..587f423
--- /dev/null
+++ b/xos/core/models/sliceprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message SlicePrivilege (PlCoreBase){
+     required manytoone user->User:sliceprivileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone slice->Slice:sliceprivileges = 2 [db_index = True, null = False, blank = False];
+     required manytoone role->SliceRole:sliceprivileges = 3 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/slicerole.xproto b/xos/core/models/slicerole.xproto
new file mode 100644
index 0000000..6396518
--- /dev/null
+++ b/xos/core/models/slicerole.xproto
@@ -0,0 +1,5 @@
+
+
+message SliceRole (PlCoreBase){
+     required string role = 1 [choices = "(('admin', 'Admin'), ('default', 'Default'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/slicetag.py b/xos/core/models/slicetag.py
deleted file mode 100644
index 7c0b57a..0000000
--- a/xos/core/models/slicetag.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase,ModelLink
-from core.models import Slice
-from core.models.plcorebase import StrippedCharField
-
-class SliceTag(PlCoreBase):
-    slice = models.ForeignKey(Slice, related_name='slicetags')
-
-    NAME_CHOICES = (('privatekey', 'Private Key'), ('publickey', 'Public Key'))
-    name = StrippedCharField(help_text="The name of this tag", max_length=30, choices=NAME_CHOICES)
-    value = StrippedCharField(help_text="The value of this tag", max_length=1024)
-
-    xos_links = [ModelLink(Slice,via='slice')]
-
-    def can_update(self, user):
-        return user.can_update_slice(self.slice)
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = SliceTag.objects.all()
-        else:
-            slices = Slice.select_by_user(user)
-            qs = SliceTag.objects.filter(slice__in=slices)
-        return qs
diff --git a/xos/core/models/slicetag.xproto b/xos/core/models/slicetag.xproto
new file mode 100644
index 0000000..a2cac93
--- /dev/null
+++ b/xos/core/models/slicetag.xproto
@@ -0,0 +1,7 @@
+
+
+message SliceTag (PlCoreBase){
+     required manytoone slice->Slice:slicetags = 1 [db_index = True, null = False, blank = False];
+     required string name = 2 [choices = "(('privatekey', 'Private Key'), ('publickey', 'Public Key'))", max_length = 30, content_type = "stripped", blank = False, help_text = "The name of this tag", null = False, db_index = False];
+     required string value = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "The value of this tag", null = False, db_index = False];
+}
diff --git a/xos/core/models/tag.py b/xos/core/models/tag.py
deleted file mode 100644
index 7bd7aea..0000000
--- a/xos/core/models/tag.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import os
-from django.db import models
-from core.models import PlCoreBase
-from core.models import Service
-from core.models.plcorebase import StrippedCharField
-from django.contrib.contenttypes.models import ContentType
-from django.contrib.contenttypes.fields import GenericForeignKey
-
-# Create your models here.
-
-class Tag(PlCoreBase):
-
-    service = models.ForeignKey(Service, related_name='tags', help_text="The Service this Tag is associated with")
-
-    name = models.SlugField(help_text="The name of this tag", max_length=128)
-    value = StrippedCharField(help_text="The value of this tag", max_length=1024)
-
-    # The required fields to do a ObjectType lookup, and object_id assignment
-    content_type = models.ForeignKey(ContentType)
-    object_id = models.PositiveIntegerField()
-    content_object = GenericForeignKey('content_type', 'object_id')
-
-    def __unicode__(self):
-        return self.name
-
-
-    def can_update(self, user):
-        return user.can_update_root()
-
-    @classmethod
-    def select_by_content_object(cls, obj):
-        return cls.objects.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id)
-
-    @staticmethod
-    def select_by_user(user):
-        return Tag.objects.all()
diff --git a/xos/core/models/tag.xproto b/xos/core/models/tag.xproto
new file mode 100644
index 0000000..45662a8
--- /dev/null
+++ b/xos/core/models/tag.xproto
@@ -0,0 +1,9 @@
+
+
+message Tag (PlCoreBase){
+     required manytoone service->Service:tags = 1 [help_text = "The Service this Tag is associated with", null = False, db_index = True, blank = False];
+     required string name = 2 [help_text = "The name of this tag", max_length = 128, null = False, db_index = True, blank = False];
+     required string value = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "The value of this tag", null = False, db_index = False];
+     required manytoone content_type->ContentType:tag = 4 [db_index = True, null = False, blank = False];
+     required uint32 object_id = 5 [db_index = False, null = False, blank = False];
+}
diff --git a/xos/core/models/tenant.xproto b/xos/core/models/tenant.xproto
new file mode 100644
index 0000000..91de112
--- /dev/null
+++ b/xos/core/models/tenant.xproto
@@ -0,0 +1,15 @@
+
+
+message Tenant (PlCoreBase,AttributeMixin){
+     optional string name = 1 [db_index = False, max_length = 200, null = True, content_type = "stripped", blank = True];
+     required string kind = 2 [default = "generic", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     required manytoone provider_service->Service:provided_tenants = 3 [db_index = True, null = False, blank = False];
+     optional manytoone subscriber_service->Service:subscribed_tenants = 4 [db_index = True, null = True, blank = True];
+     optional manytoone subscriber_tenant->Tenant:subscribed_tenants = 5 [db_index = True, null = True, blank = True];
+     optional manytoone subscriber_user->User:subscribed_tenants = 6 [db_index = True, null = True, blank = True];
+     optional manytoone subscriber_root->TenantRoot:subscribed_tenants = 7 [db_index = True, null = True, blank = True];
+     optional manytoone subscriber_network->Network:subscribed_tenants = 8 [db_index = True, null = True, blank = True];
+     optional string service_specific_id = 9 [db_index = False, max_length = 30, null = True, content_type = "stripped", blank = True];
+     optional string service_specific_attribute = 10 [db_index = False, null = True, blank = True];
+     required string connect_method = 11 [default = "na", choices = "(('public', 'Public'), ('private', 'Private'), ('private-unidirectional', 'Private Unidirectional'), ('na', 'Not Applicable'))", max_length = 30, blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/tenantattribute.xproto b/xos/core/models/tenantattribute.xproto
new file mode 100644
index 0000000..f9a3abe
--- /dev/null
+++ b/xos/core/models/tenantattribute.xproto
@@ -0,0 +1,7 @@
+
+
+message TenantAttribute (PlCoreBase){
+     required string name = 1 [help_text = "Attribute Name", max_length = 128, null = False, db_index = False, blank = False];
+     required string value = 2 [help_text = "Attribute Value", null = False, db_index = False, blank = False];
+     required manytoone tenant->Tenant:tenantattributes = 3 [help_text = "The Tenant this attribute is associated with", null = False, db_index = True, blank = False];
+}
diff --git a/xos/core/models/tenantprivilege.xproto b/xos/core/models/tenantprivilege.xproto
new file mode 100644
index 0000000..9d90981
--- /dev/null
+++ b/xos/core/models/tenantprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message TenantPrivilege (PlCoreBase){
+     required manytoone user->User:tenantprivileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone tenant->Tenant:tenantprivileges = 2 [db_index = True, null = False, blank = False];
+     required manytoone role->TenantRole:tenantprivileges = 3 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/tenantrole.xproto b/xos/core/models/tenantrole.xproto
new file mode 100644
index 0000000..646809e
--- /dev/null
+++ b/xos/core/models/tenantrole.xproto
@@ -0,0 +1,5 @@
+
+
+message TenantRole (PlCoreBase){
+     required string role = 1 [choices = "(('admin', 'Admin'), ('access', 'Access'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/tenantroot.xproto b/xos/core/models/tenantroot.xproto
new file mode 100644
index 0000000..3c01492
--- /dev/null
+++ b/xos/core/models/tenantroot.xproto
@@ -0,0 +1,8 @@
+
+
+message TenantRoot (PlCoreBase,AttributeMixin){
+     required string kind = 1 [default = "generic", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+     optional string name = 2 [max_length = 255, content_type = "stripped", blank = True, help_text = "name", null = True, db_index = False];
+     optional string service_specific_attribute = 3 [db_index = False, null = True, blank = True];
+     optional string service_specific_id = 4 [db_index = False, max_length = 30, null = True, content_type = "stripped", blank = True];
+}
diff --git a/xos/core/models/tenantrootprivilege.xproto b/xos/core/models/tenantrootprivilege.xproto
new file mode 100644
index 0000000..cf86119
--- /dev/null
+++ b/xos/core/models/tenantrootprivilege.xproto
@@ -0,0 +1,7 @@
+
+
+message TenantRootPrivilege (PlCoreBase){
+     required manytoone user->User:tenant_root_privileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone tenant_root->TenantRoot:tenant_root_privileges = 2 [db_index = True, null = False, blank = False];
+     required manytoone role->TenantRootRole:tenant_root_privileges = 3 [db_index = True, null = False, blank = False];
+}
diff --git a/xos/core/models/tenantrootrole.xproto b/xos/core/models/tenantrootrole.xproto
new file mode 100644
index 0000000..87346ac
--- /dev/null
+++ b/xos/core/models/tenantrootrole.xproto
@@ -0,0 +1,5 @@
+
+
+message TenantRootRole (PlCoreBase){
+     required string role = 1 [choices = "(('admin', 'Admin'), ('access', 'Access'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/tenantwithcontainer.xproto b/xos/core/models/tenantwithcontainer.xproto
new file mode 100644
index 0000000..2bef221
--- /dev/null
+++ b/xos/core/models/tenantwithcontainer.xproto
@@ -0,0 +1,8 @@
+
+
+message TenantWithContainer (Tenant){
+     optional manytoone instance->Instance:+ = 1 [help_text = "Instance used by this Tenant", null = True, db_index = True, blank = True];
+     optional manytoone creator->User:+ = 2 [help_text = "Creator of this Tenant", null = True, db_index = True, blank = True];
+     optional string external_hostname = 3 [max_length = 30, content_type = "stripped", blank = True, help_text = "External host name", null = True, db_index = False];
+     optional string external_container = 4 [max_length = 30, content_type = "stripped", blank = True, help_text = "External host name", null = True, db_index = False];
+}
diff --git a/xos/core/models/usableobject.xproto b/xos/core/models/usableobject.xproto
new file mode 100644
index 0000000..8437318
--- /dev/null
+++ b/xos/core/models/usableobject.xproto
@@ -0,0 +1,5 @@
+
+
+message UsableObject (PlCoreBase){
+     required string name = 1 [db_index = False, max_length = 1024, null = False, content_type = "stripped", blank = False];
+}
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 89464bf..ff7bece 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -345,7 +345,7 @@
         msg.send()
 
     def can_update(self, user):
-        from core.models import SitePrivilege
+        from core.models.siteprivilege import SitePrivilege
         _cant_update_fieldName = None
         if user.can_update_root():
             return True
@@ -382,7 +382,7 @@
         return False
 
     def can_update_deployment(self, deployment):
-        from core.models.site import DeploymentPrivilege
+        from core.models.deploymentprivilege import DeploymentPrivilege
         if self.can_update_root():
             return True
 
@@ -394,7 +394,7 @@
         return False
 
     def can_update_site(self, site, allow=[]):
-        from core.models.site import SitePrivilege
+        from core.models.siteprivilege import SitePrivilege
         if self.can_update_root():
             return True
         if SitePrivilege.objects.filter(
@@ -403,7 +403,7 @@
         return False
 
     def can_update_slice(self, slice):
-        from core.models.slice import SlicePrivilege
+        from core.models.sliceprivilege import SlicePrivilege
         if self.can_update_root():
             return True
         if self == slice.creator:
@@ -417,7 +417,7 @@
         return False
 
     def can_update_service(self, service, allow=[]):
-        from core.models.service import ServicePrivilege
+        from core.models.serviceprivilege import ServicePrivilege
         if self.can_update_root():
             return True
         if ServicePrivilege.objects.filter(
@@ -426,7 +426,8 @@
         return False
 
     def can_update_tenant_root(self, tenant_root, allow=[]):
-        from core.models.service import TenantRoot, TenantRootPrivilege
+        from core.models.tenantroot import TenantRoot
+        from core.models.tenantrootprivilege import TenantRootPrivilege
         if self.can_update_root():
             return True
         if TenantRootPrivilege.objects.filter(
@@ -435,7 +436,8 @@
         return False
 
     def can_update_tenant(self, tenant, allow=[]):
-        from core.models.service import Tenant, TenantPrivilege
+        from core.models.tenant import Tenant
+        from core.models.tenantprivilege import TenantPrivilege
         if self.can_update_root():
             return True
         if TenantPrivilege.objects.filter(
@@ -568,7 +570,7 @@
             qs = User.objects.all()
         else:
             # can see all users at any site where this user has pi role
-            from core.models.site import SitePrivilege
+            from core.models.siteprivilege import SitePrivilege
             site_privs = SitePrivilege.objects.filter(user=user)
             sites = [sp.site for sp in site_privs if sp.role.role in [
                 'Admin', 'admin', 'pi']]
diff --git a/xos/core/models/usercredential.xproto b/xos/core/models/usercredential.xproto
new file mode 100644
index 0000000..22fc83c
--- /dev/null
+++ b/xos/core/models/usercredential.xproto
@@ -0,0 +1,8 @@
+
+
+message UserCredential (PlCoreBase){
+     required manytoone user->User:usercredentials = 1 [help_text = "The User this credential is associated with", null = False, db_index = True, blank = False];
+     required string name = 2 [help_text = "The credential type, e.g. ec2", max_length = 128, null = False, db_index = True, blank = False];
+     required string key_id = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "The backend id of this credential", null = False, db_index = False];
+     required string enc_value = 4 [help_text = "The key value of this credential", max_length = 1024, null = False, db_index = False, blank = False];
+}
diff --git a/xos/core/models/xos.xproto b/xos/core/models/xos.xproto
new file mode 100644
index 0000000..8f0d60f
--- /dev/null
+++ b/xos/core/models/xos.xproto
@@ -0,0 +1,15 @@
+
+
+message XOS (PlCoreBase){
+     required string name = 1 [default = "XOS", max_length = 200, content_type = "stripped", blank = False, help_text = "Name of XOS", null = False, db_index = False];
+     required int32 ui_port = 2 [help_text = "Port for XOS UI", default = 80, null = False, db_index = False, blank = False];
+     required int32 bootstrap_ui_port = 3 [help_text = "Port for XOS Bootstrap UI", default = 81, null = False, db_index = False, blank = False];
+     required string db_container_name = 4 [default = "xos_db", max_length = 200, content_type = "stripped", blank = False, help_text = "name of XOS db container", null = False, db_index = False];
+     required string redis_container_name = 5 [default = "", max_length = 200, content_type = "stripped", blank = True, help_text = "name of XOS redis container", null = False, db_index = False];
+     required string docker_project_name = 6 [max_length = 200, content_type = "stripped", blank = False, help_text = "docker project name", null = False, db_index = False];
+     required bool enable_build = 7 [help_text = "True if Onboarding Synchronizer should build XOS as necessary", default = True, null = False, db_index = False, blank = True];
+     required bool frontend_only = 8 [help_text = "If True, XOS will not start synchronizer containers", default = False, null = False, db_index = False, blank = True];
+     required string source_ui_image = 9 [default = "xosproject/xos", max_length = 200, content_type = "stripped", blank = False, null = False, db_index = False];
+     optional string extra_hosts = 10 [max_length = 1024, content_type = "stripped", blank = True, help_text = "list of hostname mappings that will be passed to docker-compose", null = True, db_index = False];
+     required bool no_start = 11 [help_text = "Do not start the XOS UI inside of the UI docker container", default = False, null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/xoscomponent.xproto b/xos/core/models/xoscomponent.xproto
new file mode 100644
index 0000000..057529a
--- /dev/null
+++ b/xos/core/models/xoscomponent.xproto
@@ -0,0 +1,9 @@
+
+
+message XOSComponent (LoadableModule){
+     required string image = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "docker image name", null = False, db_index = False];
+     optional string command = 2 [max_length = 1024, content_type = "stripped", blank = True, help_text = "docker run command", null = True, db_index = False];
+     optional string ports = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "port binding", null = True, db_index = False];
+     optional string extra = 4 [max_length = 200, content_type = "stripped", blank = True, help_text = "extra information needed by containers", null = True, db_index = False];
+     required bool no_start = 5 [help_text = "Do not start the Component", default = False, null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/xoscomponentlink.xproto b/xos/core/models/xoscomponentlink.xproto
new file mode 100644
index 0000000..5d95bf0
--- /dev/null
+++ b/xos/core/models/xoscomponentlink.xproto
@@ -0,0 +1,8 @@
+
+
+message XOSComponentLink (PlCoreBase){
+     required manytoone component->XOSComponent:links = 1 [help_text = "The Component object for this Link", null = False, db_index = True, blank = False];
+     required string container = 2 [max_length = 200, content_type = "stripped", blank = False, help_text = "container to link", null = False, db_index = False];
+     required string alias = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "alias for the link", null = False, db_index = False];
+     required string kind = 4 [default = "internal", choices = "(('internal', 'Internal'), ('external', 'External'))", max_length = 20, blank = False, null = False, db_index = False];
+}
diff --git a/xos/core/models/xoscomponentvolume.xproto b/xos/core/models/xoscomponentvolume.xproto
new file mode 100644
index 0000000..c1f7e91
--- /dev/null
+++ b/xos/core/models/xoscomponentvolume.xproto
@@ -0,0 +1,9 @@
+
+
+message XOSComponentVolume (PlCoreBase){
+     required manytoone component->XOSComponent:volumes = 1 [help_text = "The Component object for this Volume", null = False, db_index = True, blank = False];
+     required string name = 2 [max_length = 300, content_type = "stripped", blank = False, help_text = "Volume Name", null = False, db_index = False];
+     required string container_path = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Path of Volume in Container", null = False, db_index = False];
+     required string host_path = 4 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Path of Volume in Host", null = False, db_index = False];
+     required bool read_only = 5 [help_text = "True if mount read-only", default = False, null = False, db_index = False, blank = True];
+}
diff --git a/xos/core/models/xoscomponentvolumecontainer.xproto b/xos/core/models/xoscomponentvolumecontainer.xproto
new file mode 100644
index 0000000..a8c8223
--- /dev/null
+++ b/xos/core/models/xoscomponentvolumecontainer.xproto
@@ -0,0 +1,7 @@
+
+
+message XOSComponentVolumeContainer (PlCoreBase){
+     required manytoone component->XOSComponent:volumecontainers = 1 [help_text = "The Component object for this VolumeContainer", null = False, db_index = True, blank = False];
+     required string name = 2 [max_length = 300, content_type = "stripped", blank = False, help_text = "Volume Name", null = False, db_index = False];
+     required string container = 3 [max_length = 300, content_type = "stripped", blank = False, help_text = "Volume Name", null = False, db_index = False];
+}
diff --git a/xos/core/models/xosguiextension.xproto b/xos/core/models/xosguiextension.xproto
new file mode 100644
index 0000000..774de4e
--- /dev/null
+++ b/xos/core/models/xosguiextension.xproto
@@ -0,0 +1,6 @@
+
+
+message XOSGuiExtension (PlCoreBase){
+     required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the GUI Extensions", null = False, db_index = False];
+     required string files = 2 [max_length = 1024, content_type = "stripped", blank = False, help_text = "List of comma separated file composing the view", null = False, db_index = False];
+}
diff --git a/xos/core/models/xosvolume.xproto b/xos/core/models/xosvolume.xproto
new file mode 100644
index 0000000..2da83d3
--- /dev/null
+++ b/xos/core/models/xosvolume.xproto
@@ -0,0 +1,8 @@
+
+
+message XOSVolume (PlCoreBase){
+     required manytoone xos->XOS:volumes = 1 [help_text = "The XOS object for this Volume", null = False, db_index = True, blank = False];
+     required string container_path = 2 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Path of Volume in Container", null = False, db_index = False];
+     required string host_path = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Path of Volume in Host", null = False, db_index = False];
+     required bool read_only = 4 [help_text = "True if mount read-only", default = False, null = False, db_index = False, blank = True];
+}
diff --git a/xos/genx/targets/init.xtarget b/xos/genx/targets/init.xtarget
index df0abdf..4cf3c31 100644
--- a/xos/genx/targets/init.xtarget
+++ b/xos/genx/targets/init.xtarget
@@ -2,6 +2,7 @@
 # 
 
 from .plcorebase import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,PlModelMixIn,ModelLink
+from .contenttype import ContentType
 from .singletonmodel import SingletonModel
 from .site import Site
 from .dashboardview import DashboardView
diff --git a/xos/genx/targets/service.xtarget b/xos/genx/targets/service.xtarget
new file mode 100644
index 0000000..c2256d2
--- /dev/null
+++ b/xos/genx/targets/service.xtarget
@@ -0,0 +1,49 @@
+from header import *
+
+{% for m in proto.messages %}
+{% if file_exists(m.name|lower+'_header.py') -%}from {{m.name|lower }}_header import *{% endif %}
+{% if file_exists(m.name|lower+'_top.py') -%}{{ include_file(m.name|lower+'_top.py') }} {% endif %}
+
+{%- for l in m.links -%}
+{% if l.peer not in proto.message_names -%}
+from core.models import {{ l.peer }} 
+{%- endif -%}
+{%- endfor -%}
+{%- for b in m.bases -%}
+{%- if b!='PlCoreBase' and 'Mixin' not in b -%}
+#from core.models.{{b | lower}} import {{ b }}
+from core.models import {{ b }}
+{%- endif -%}
+{% endfor %}
+
+{% endfor %}
+
+{% for m in proto.messages %}
+
+class {{ m.name }}{{ xproto_base_def(m.bases) }}:
+
+  KIND = {{ xproto_first_non_empty([m.options.kind, options.kind, options.name, "Set a kind in your xproto!"]) }}
+
+  class Meta:
+      app_label = {{ xproto_first_non_empty([m.options.app_label, options.app_label, options.name, "Set an app label in your xproto!"]) | lower}}
+      # name = {{ xproto_first_non_empty([m.options.name, options.name, "Set a name in your xproto!"]) }}
+      verbose_name = {{ xproto_first_non_empty([m.options.verbose_name, options.verbose_name, "Set a verbose name in your xproto!"]) }}
+
+  # Primitive Fields (Not Relations)
+  {% for f in m.fields %}
+  {%- if not f.link -%}
+  {{ f.name }} = {{ xproto_django_type(f.type, f.options) }}( {{ xproto_django_options_str(f) }} )
+  {% endif %}
+  {%- endfor %}
+
+  # Relations
+  {% for l in m.links %}
+  {{ l.src_port }} = {{ xproto_django_link_type(l) }}( {%- if l.peer==m.name -%}'self'{%- else -%}{{ l.peer }} {%- endif -%}, {{ xproto_django_options_str(l, l.dst_port ) }} )
+  {%- endfor %}
+
+  {% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
+  pass
+
+{% if file_exists(m.name|lower+'_bottom.py') -%}{{ include_file(m.name|lower+'_bottom.py') }}{% endif %}
+{% endfor %}
++++ models.py
diff --git a/xos/genx/tool/Makefile.service b/xos/genx/tool/Makefile.service
new file mode 100644
index 0000000..a465797
--- /dev/null
+++ b/xos/genx/tool/Makefile.service
@@ -0,0 +1,18 @@
+# Replace the line below with the location of xosgen
+PREFIX=.
+XOSGEN=$(PREFIX)/tool/xosgen
+
+DJANGO_TARGET=$(PREFIX)/targets/service.xtarget
+XPROTOS_TMP := $(shell mktemp)
+
+xprotos = $(wildcard *.xproto)
+
+all: models.py
+
+models.py: $(xprotos)
+	$(XOSGEN) --attic attic --input $< --target $(DJANGO_TARGET) --output $@
+
+.PHONY: all
+	
+clean:
+	rm -f models.py
diff --git a/xos/genx/tool/lib.py b/xos/genx/tool/lib.py
index 9f6c3a9..1c5200e 100644
--- a/xos/genx/tool/lib.py
+++ b/xos/genx/tool/lib.py
@@ -40,6 +40,10 @@
     else:
         return '(' + ','.join(base) + ')'
 
+def xproto_first_non_empty(lst):
+    for l in lst:
+        if l: return l
+
 def xproto_django_type(xptype, xptags):
     if (xptype=='string'):
         return django_string_type(xptags)
diff --git a/xos/genx/tool/xos2jinja.py b/xos/genx/tool/xos2jinja.py
index 9cbc00a..5af2272 100644
--- a/xos/genx/tool/xos2jinja.py
+++ b/xos/genx/tool/xos2jinja.py
@@ -7,6 +7,20 @@
 import jinja2
 import os
 
+def count_messages(body):
+    count = 0
+    for e in body:
+        if (type(e)==m.MessageDefinition):
+            count+=1
+    return count
+
+def count_fields(body):
+    count = 0
+    for e in body:
+        if (type(e) in [m.LinkDefinition,m.FieldDefinition,m.LinkSpec]):
+            count+=1
+    return count
+
 class Stack(list):
     def push(self,x):
         self.append(x)
@@ -15,12 +29,15 @@
     in addition to traversing it '''
 class XOS2Jinja(m.Visitor):
     stack = Stack()
+    options = {}
+    message_options = {}
     count_stack = Stack()
     content=""
     offset=0
     doNameSanitization=False
     statementsChanged=0
     prefix=""
+    current_message_name = None
 
     def get_stack(self):
         return stack
@@ -41,7 +58,11 @@
         return True
 
     def visit_OptionStatement(self, obj):
-        '''Ignore'''
+        if (self.current_message_name):
+            self.message_options[obj.name.value.pval] = obj.value.value.pval
+        else:
+            self.options[obj.name.value.pval] = obj.value.value.pval
+
         return True
 
     def visit_LU(self, obj):
@@ -148,7 +169,9 @@
         return True
 
     def visit_MessageDefinition(self, obj):
-        self.count_stack.push(len(obj.body))
+        self.current_message_name = obj.name.value.pval
+        self.message_options = {}
+        self.count_stack.push(count_fields(obj.body))
         return True
     
     def visit_MessageDefinition_post(self, obj):
@@ -168,7 +191,8 @@
                 fields.insert(0,f)
                 last_field = f
 
-        self.stack.push({'name':obj.name.value.pval,'fields':fields,'links':links, 'bases':obj.bases})
+        self.stack.push({'name':obj.name.value.pval,'fields':fields,'links':links, 'bases':obj.bases, 'options':self.message_options})
+        self.current_message_name = None
         return True
 
     def visit_MessageExtension(self, obj):
@@ -192,8 +216,9 @@
     def visit_DotName(self, obj):
         return True
 
+    
     def visit_Proto(self, obj):
-        self.count_stack.push(len(obj.body))
+        self.count_stack.push(count_messages(obj.body))
         return True
     
     def visit_Proto_post(self, obj):
@@ -203,7 +228,7 @@
             try:
                 m = self.stack.pop()
             except IndexError:
-                pdb.set_trace()
+                pass
 
             messages.insert(0,m)
 
diff --git a/xos/genx/tool/xosgen b/xos/genx/tool/xosgen
index 59934a7..addb6ba 100755
--- a/xos/genx/tool/xosgen
+++ b/xos/genx/tool/xosgen
@@ -68,7 +68,7 @@
         except:
             pass
 
-        rendered = template.render({"proto": {'messages':v.messages},"context":context})
+        rendered = template.render({"proto": {'messages':v.messages, 'message_names':[m['name'] for m in v.messages]},"context":context,"options":v.options})
 
         lines = rendered.splitlines()
         current_buffer = []
diff --git a/xos/tools/corebuilder/corebuilder.py b/xos/tools/corebuilder/corebuilder.py
index 6e00962..01ea525 100644
--- a/xos/tools/corebuilder/corebuilder.py
+++ b/xos/tools/corebuilder/corebuilder.py
@@ -1,3 +1,7 @@
+#!/usr/bin/python
+
+from stat import *
+
 """ CoreBuilder
 
     Read XOS Tosca Onboarding Recipes and generate a BUILD directory.
@@ -106,8 +110,13 @@
 
     def get_dest_dir(self, kind, service_name):
         xos_base = "opt/xos"
+        if service_name!='core':
+            service_subdir = 'services'
+        else:
+            service_subdir = ''
+
         base_dirs = {"models": "%s/services/%s/" % (xos_base, service_name),
-                     "xproto": "%s/services/%s/xproto/" % (xos_base, service_name),
+                     "xproto": "%s/%s/%s/" % (xos_base, service_subdir, service_name),
                      "admin": "%s/services/%s/" % (xos_base, service_name),
                      "admin_template": "%s/services/%s/templates/" % (xos_base, service_name),
                      "django_library": "%s/services/%s/" % (xos_base, service_name),
@@ -119,6 +128,7 @@
                      "private_key": "%s/services/%s/keys/" % (xos_base, service_name),
                      "public_key": "%s/services/%s/keys/" % (xos_base, service_name),
                      "vendor_js": "%s/core/xoslib/static/vendor/" % (xos_base)}
+
         dest_dir = base_dirs[kind]
 
         return dest_dir
@@ -134,6 +144,7 @@
         fixups = ( ("/opt/xos_services/olt/", "/opt/cord/onos-apps/apps/olt/"),
                    ("/opt/xos_services/vtn/", "/opt/cord/onos-apps/apps/vtn/"),
                    ("/opt/xos_services/", "/opt/cord/orchestration/xos_services/"),
+                   ("/opt/xos/core/", "/opt/cord/orchestration/xos/xos/core/"),
                    ("/opt/xos_libraries/", "/opt/cord/orchestration/xos_libraries/") )
 
         for (pattern, replace) in fixups:
@@ -197,7 +208,7 @@
 

             # If the ServiceController has models, then add it to the list of

             # django apps.

-            if (k=="models"):

+            if (k in ["models","xproto"] and service_name!="core"):

                 self.app_names.append(service_name)

 

             # filenames can be comma-separated

@@ -236,7 +247,7 @@
                 dest_dir = self.get_dest_dir(k, service_name)

                 dest_fn = os.path.join(dest_dir, subdirectory, os.path.basename(src_fn))

 

-                self.resources.append( (k, src_fn, dest_fn) )

+                self.resources.append( (k, src_fn, dest_fn, service_name) )

 

                 # add __init__.py files anywhere that we created a new

                 # directory.

@@ -258,18 +269,34 @@
                 shutil.rmtree(os.path.join(BUILD_DIR, dir))

 

         # Copy all of the resources into the build directory

-        for (kind, src_fn, dest_fn) in self.resources:

-#            if (kind == "xproto"):

-#               build_dest_dir = os.path.join(BUILD_DIR, os.path.dirname(dest_fn))

-

-                # TODO: If we wanted to statically compile xproto files, then

-                #   this is where we could do it. src_fn would be the name of

-                #   the xproto file, and build_dest_dir would be the place

-                #   to store the generated files.

-

+        for (kind, src_fn, dest_fn, service_name) in self.resources:

             build_dest_fn = os.path.join(BUILD_DIR, dest_fn)

             makedirs_if_noexist(os.path.dirname(build_dest_fn))

-            shutil.copyfile(src_fn, build_dest_fn)

+            if (os.path.isdir(src_fn)):
+                if (not os.path.isdir(build_dest_fn)):
+                    shutil.copytree(src_fn, build_dest_fn, symlinks=True)
+                else:
+                    os.system('cp -R %s/*.xproto %s/attic %s/*header.py %s'%(src_fn, src_fn, src_fn, build_dest_fn))
+            else:
+                shutil.copyfile(src_fn, build_dest_fn)

+
+            if (kind=='xproto'):
+                # Invoke xproto toolchain in the destination directory
+                xosgen_path = '/opt/cord/orchestration/xos/xos/genx'
+                is_service = service_name!='core'
+
+                if (is_service):
+                    makefile_name = 'Makefile.service'
+                else:
+                    makefile_name = 'Makefile'
+
+                xproto_makefile = '%s/tool/%s'%(xosgen_path,makefile_name)
+                xproto_makefile_dst = '/'.join([build_dest_fn, makefile_name])
+                shutil.copyfile(xproto_makefile, xproto_makefile_dst)
+
+                if (os.system('make -C %s -f %s PREFIX=%s'%(build_dest_fn, makefile_name, xosgen_path))):
+                    raise Exception('xproto build failed!')
+
 

         # Create the __init__.py files

         for fn in self.inits: