Merge branch 'master' of https://github.com/open-cloud/xos

Conflicts:
	xos/core/models/service.py
diff --git a/containers/README b/containers/README
new file mode 100644
index 0000000..9891efe
--- /dev/null
+++ b/containers/README
@@ -0,0 +1,51 @@
+
+1. Introduction
+
+  XOS is comprised of 3 core services:
+  
+  * A database backend (postgres)
+  * A webserver front end (django)
+  * A synchronizer daemon that interacts with the openstack backend.
+
+  We have created separate dockerfiles for each of these services, making it easier to
+  build the services independently and also deploy and run them in isolated environments. 
+
+2. Database Container
+  
+  To build and run the database container:
+  
+  $ cd postgres; make build && make run;
+
+3. XOS container
+  
+  To build and run the xos webserver container:
+
+  $ cd xos; make build && make run;
+
+  You should now be able to access the login page by visiting http://localhost:80 and
+  log in using the default paadmin account. It may be helpful to bootstrap xos with 
+  some sample data; deployment, controllers, sites, slices, etc. You can get started by
+  loading tosca configuration for the opencloud demo dataset:
+
+  $ cd xos; make runtosca;
+
+  Or you can create you own tosca configuraton file and customize the dataset however you
+  want. You can all load your own tosca configuration by setting the TOSCA_CONFIG_PATH 
+  environment variable before executing the make command:
+
+  $ cd xos; TOSCA_CONFIG_PATH=/path/to/tosca/config.yaml make runtosca
+
+4. Synchronizer container
+
+  The syncornonizer shares many of the same dependencies as the xos container. The synchronizer 
+  container takes advantage of this by building itself on top of the xos image. This means
+  you must build the xos image before building the synchronizer image. The XOS and 
+  synchronizer containers can run on separate hosts, but you must build the xos image
+  on the host that you plan to run the synchronizer container. Assuming you have already 
+  built the xos container, executing the following will build and run the synchronizer container:
+
+  $ cd synchronizer; make build && make run
+
+  
+
+
diff --git a/containers/observer/Makefile b/containers/observer/Makefile
deleted file mode 100644
index e7fedf5..0000000
--- a/containers/observer/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-CONTAINER_NAME:=observer-server
-
-.PHONY: build
-build: ; docker build --rm -t observer .
-
-.PHONY: run
-run: ; docker run -d --name ${CONTAINER_NAME} observer
-
-.PHONY: stop
-stop: ; docker stop ${CONTAINER_NAME}
-
-.PHONY: rm
-rm: ; docker rm ${CONTAINER_NAME}
diff --git a/containers/observer/Dockerfile b/containers/synchronizer/Dockerfile
similarity index 87%
rename from containers/observer/Dockerfile
rename to containers/synchronizer/Dockerfile
index 7ec6592..44b058e 100644
--- a/containers/observer/Dockerfile
+++ b/containers/synchronizer/Dockerfile
@@ -25,7 +25,7 @@
 # For Observer
 RUN mkdir -p /usr/local/share /bin /etc/ansible
 
-RUN cp /tmp/xos/containers/observer/conf/ansible-hosts /etc/ansible/hosts
+RUN cp /tmp/xos/containers/synchronizer/conf/ansible-hosts /etc/ansible/hosts
 
 ADD http://phantomjs.googlecode.com/files/phantomjs-1.7.0-linux-x86_64.tar.bz2 /usr/local/share/
 
@@ -39,6 +39,6 @@
 
 
 # Supervisor
-RUN cp /tmp/xos/containers/observer/conf/observer.conf /etc/supervisor/conf.d/
+RUN cp /tmp/xos/containers/synchronizer/conf/synchronizer.conf /etc/supervisor/conf.d/
 
-CMD /usr/bin/supervisord -c /etc/supervisor/conf.d/observer.conf
+CMD /usr/bin/supervisord -c /etc/supervisor/conf.d/synchronizer.conf
diff --git a/containers/synchronizer/Makefile b/containers/synchronizer/Makefile
new file mode 100644
index 0000000..14520d9
--- /dev/null
+++ b/containers/synchronizer/Makefile
@@ -0,0 +1,13 @@
+CONTAINER_NAME:=synchronizer-server
+
+.PHONY: build
+build: ; docker build --rm -t synchronizer .
+
+.PHONY: run
+run: ; docker run -d --name ${CONTAINER_NAME} synchronizer
+
+.PHONY: stop
+stop: ; docker stop ${CONTAINER_NAME}
+
+.PHONY: rm
+rm: ; docker rm ${CONTAINER_NAME}
diff --git a/containers/observer/conf/ansible-hosts b/containers/synchronizer/conf/ansible-hosts
similarity index 100%
rename from containers/observer/conf/ansible-hosts
rename to containers/synchronizer/conf/ansible-hosts
diff --git a/containers/observer/conf/observer.conf b/containers/synchronizer/conf/synchronizer.conf
similarity index 62%
rename from containers/observer/conf/observer.conf
rename to containers/synchronizer/conf/synchronizer.conf
index 48f61dd..cda6716 100644
--- a/containers/observer/conf/observer.conf
+++ b/containers/synchronizer/conf/synchronizer.conf
@@ -3,7 +3,7 @@
 pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
 nodaemon=true
 
-[program:observer]
+[program:synchronizer]
 command=python /opt/xos/xos-observer.py
-stderr_logfile=/var/log/supervisor/observer.err.log
-stdout_logfile=/var/log/supervisor/observer.out.log
+stderr_logfile=/var/log/supervisor/synchronizer.err.log
+stdout_logfile=/var/log/supervisor/synchronizer.out.log
diff --git a/xos/configurations/common/fixtures.yaml b/xos/configurations/common/fixtures.yaml
new file mode 100644
index 0000000..0d77ca2
--- /dev/null
+++ b/xos/configurations/common/fixtures.yaml
@@ -0,0 +1,20 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Some basic fixtures
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    s_tag:
+      type: tosca.nodes.NetworkParameterType
+
+    c_tag:
+      type: tosca.nodes.NetworkParameterType
+
+    next_hop:
+      type: tosca.nodes.NetworkParameterType
+
+    device:
+      type: tosca.nodes.NetworkParameterType
diff --git a/xos/configurations/cord/Makefile.inside b/xos/configurations/cord/Makefile.inside
index c412f25..b8e23f8 100644
--- a/xos/configurations/cord/Makefile.inside
+++ b/xos/configurations/cord/Makefile.inside
@@ -2,6 +2,7 @@
 
 setup_xos:
 	bash /opt/xos/scripts/docker_setup_xos
+	python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/fixtures.yaml
 	python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/cloudlab.yaml
 	python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/cloudlab-nodes.yaml
 	python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord/cord.yaml
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 9cde302..344cf40 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -7,7 +7,6 @@
 
 topology_template:
   node_templates:
-
     # CORD Services
     service_volt:
       type: tosca.nodes.Service
@@ -403,6 +402,15 @@
                 node: mysite_clients
                 relationship: tosca.relationships.MemberOfSlice
 
+    # docker image for vcpe containers
+    andybavier/docker-vcpe:
+      # TODO: need to attach this to mydeployment
+      type: tosca.nodes.Image
+      properties:
+        kind: container
+        container_format: na
+        disk_format: na
+
     # A subscriber
     My House:
        type: tosca.nodes.CORDSubscriber
diff --git a/xos/cord/admin.py b/xos/cord/admin.py
index 981f275..686d8ae 100644
--- a/xos/cord/admin.py
+++ b/xos/cord/admin.py
@@ -162,7 +162,6 @@
     bbs_account = forms.CharField(required=False)
     creator = forms.ModelChoiceField(queryset=User.objects.all())
     instance = forms.ModelChoiceField(queryset=Instance.objects.all(),required=False)
-    use_cobm = forms.BooleanField(required=False)
     last_ansible_hash = forms.CharField(required=False)
 
     def __init__(self,*args,**kwargs):

@@ -174,7 +173,6 @@
             self.fields['bbs_account'].initial = self.instance.bbs_account

             self.fields['creator'].initial = self.instance.creator

             self.fields['instance'].initial = self.instance.instance

-            self.fields['use_cobm'].initial = self.instance.use_cobm

             self.fields['last_ansible_hash'].initial = self.instance.last_ansible_hash

         if (not self.instance) or (not self.instance.pk):

             # default fields for an 'add' form

@@ -182,13 +180,11 @@
             self.fields['creator'].initial = get_request().user

             if VCPEService.get_service_objects().exists():

                self.fields["provider_service"].initial = VCPEService.get_service_objects().all()[0]

-            self.fields['use_cobm'].initial = False

 

     def save(self, commit=True):

         self.instance.creator = self.cleaned_data.get("creator")

         self.instance.instance = self.cleaned_data.get("instance")

         self.instance.last_ansible_hash = self.cleaned_data.get("last_ansible_hash")

-        self.instance.use_cobm = self.cleaned_data.get("use_cobm")

         return super(VCPETenantForm, self).save(commit=commit)

 

     class Meta:

@@ -198,7 +194,7 @@
     list_display = ('backend_status_icon', 'id', 'subscriber_tenant' )
     list_display_links = ('backend_status_icon', 'id')
     fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'provider_service', 'subscriber_tenant', 'service_specific_id', # 'service_specific_attribute',
-                                     'bbs_account', 'creator', 'use_cobm', 'instance', 'last_ansible_hash'],
+                                     'bbs_account', 'creator', 'instance', 'last_ansible_hash'],
                           'classes':['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', 'service_specific_attribute', 'bbs_account')
     form = VCPETenantForm
diff --git a/xos/cord/models.py b/xos/cord/models.py
index afbe3ef..a4e7501 100644
--- a/xos/cord/models.py
+++ b/xos/cord/models.py
@@ -1,5 +1,5 @@
 from django.db import models
-from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber
+from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, Port
 from core.models.plcorebase import StrippedCharField
 import os
 from django.db import models, transaction
@@ -351,7 +351,6 @@
             vcpe = VCPETenant(provider_service = vcpeServices[0],
                               subscriber_tenant = self)
             vcpe.caller = self.creator
-            # vcpe.use_cobm = True # XXX XXX XXX remove before checking XXX XXX XXX
             vcpe.save()
 
     def manage_subscriber(self):
@@ -679,6 +678,36 @@
                 self.bbs_account = None
                 super(VCPETenant, self).save()
 
+    def find_or_make_port(self, instance, network, **kwargs):
+        port = Port.objects.filter(instance=instance, network=network)
+        if port:
+            port = port[0]
+        else:
+            port = Port(instance=instance, network=network, **kwargs)
+            port.save()
+        return port
+
+    def save_instance(self, instance):
+        with transaction.atomic():
+            instance.volumes = "/etc/dnsmasq.d"
+            super(VCPETenant, self).save_instance(instance)
+
+            if instance.isolation in ["container", "container_vm"]:
+                lan_networks = [x for x in instance.slice.networks.all() if "lan" in x.name]
+                if not lan_networks:
+                    raise XOSProgrammingError("No lan_network")
+                port = self.find_or_make_port(instance, lan_networks[0], ip="192.168.0.1", port_id="unmanaged")
+                port.set_parameter("c_tag", self.volt.c_tag)
+                port.set_parameter("s_tag", self.volt.s_tag)
+                port.set_parameter("device", "eth1")
+
+                wan_networks = [x for x in instance.slice.networks.all() if "wan" in x.name]
+                if not wan_networks:
+                    raise XOSProgrammingError("No wan_network")
+                port = self.find_or_make_port(instance, wan_networks[0])
+                port.set_parameter("next_hop", value="10.0.1.253")   # FIX ME
+                port.set_parameter("device", "eth0")
+
     def save(self, *args, **kwargs):
         if not self.creator:
             if not getattr(self, "caller", None):
diff --git a/xos/core/admin.py b/xos/core/admin.py
index 3c35768..26c6dba 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1051,7 +1051,7 @@
 
 class SliceAdmin(XOSBaseAdmin):
     form = SliceForm
-    fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_instances']
+    fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_instances', "default_isolation"]
     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
     readonly_fields = ('backend_status_text', )
     list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_instances')
@@ -1273,7 +1273,7 @@
     fields = ['backend_status_icon', 'network', 'instance', 'ip', 'mac']
     readonly_fields = ("backend_status_icon", "ip", "mac")
     model = Port
-    selflink_fieldname = "network"
+    #selflink_fieldname = "network"
     extra = 0
     verbose_name_plural = "Ports"
     verbose_name = "Port"
@@ -1764,7 +1764,7 @@
     fields = ['backend_status_icon', 'network', 'instance', 'ip', 'mac']
     readonly_fields = ("backend_status_icon", "ip", "mac")
     model = Port
-    selflink_fieldname = "instance"
+    #selflink_fieldname = "instance"
     extra = 0
     verbose_name_plural = "Ports"
     verbose_name = "Port"
@@ -1843,10 +1843,25 @@
     list_display_links = ('backend_status_icon', 'name', )
     user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
     user_readonly_inlines = []
+    inlines = [NetworkParameterInline,]
     fieldsets = [
         (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
                 'classes':['suit-tab suit-tab-general']}),]
-    suit_form_tabs = (('general','Network Template Details'), )
+    suit_form_tabs = (('general','Network Template Details'), ('netparams', 'Parameters') )
+
+class PortAdmin(XOSBaseAdmin):
+    list_display = ("backend_status_icon", "name", "id", "ip")
+    list_display_links = ('backend_status_icon', 'id')
+    readonly_fields = ("subnet", )
+    inlines = [NetworkParameterInline]
+
+    fieldsets = [
+        (None, {'fields': ['backend_status_text', 'network', 'instance', 'ip', 'port_id', 'mac'],
+                'classes':['suit-tab suit-tab-general']}),
+                ]
+
+    readonly_fields = ('backend_status_text', )
+    suit_form_tabs = (('general', 'Port Details'), ('netparams', 'Parameters'))
 
 class FlavorAdmin(XOSBaseAdmin):
     list_display = ("backend_status_icon", "name", "flavor", "order", "default")
@@ -2035,6 +2050,7 @@
 admin.site.register(Service, ServiceAdmin)
 #admin.site.register(Reservation, ReservationAdmin)
 admin.site.register(Network, NetworkAdmin)
+admin.site.register(Port, PortAdmin)
 admin.site.register(Router, RouterAdmin)
 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
 admin.site.register(Program, ProgramAdmin)
diff --git a/xos/core/models/network.py b/xos/core/models/network.py
index b12068c..6894f9f 100644
--- a/xos/core/models/network.py
+++ b/xos/core/models/network.py
@@ -64,7 +64,38 @@
     except Exception,e:
         raise ValidationError(str(e))
 
-class NetworkTemplate(PlCoreBase):
+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'))
@@ -97,7 +128,7 @@
 
     def __unicode__(self):  return u'%s' % (self.name)
 
-class Network(PlCoreBase):
+class Network(PlCoreBase, ParameterMixin):
     name = models.CharField(max_length=32)
     template = models.ForeignKey(NetworkTemplate)
     subnet = models.CharField(max_length=32, blank=True)
@@ -147,6 +178,14 @@
             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()
@@ -161,7 +200,7 @@
 
     class Meta:
         unique_together = ('network', 'controller')
-        
+
     @staticmethod
     def select_by_user(user):
         if user.is_admin:
@@ -208,14 +247,12 @@
             qs = NetworkSlice.objects.filter(Q(slice__in=slice_ids) | Q(network__in=network_ids))
         return qs
 
-class Port(PlCoreBase):
+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="Quantum port id")
+    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")
-    #unattached = models.BooleanField(default=False, help_text="create this port even if no Instance is attached")
-    segmentation_id = models.CharField(null=True, blank=True, max_length=256, help_text="GRE segmentation id for port")
 
     class Meta:
         unique_together = ('network', 'instance')
@@ -257,6 +294,14 @@
             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")
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 9daf926..662a054 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -347,6 +347,8 @@
                      "trusty-server-multi-nic", # CloudLab
                     ]
 
+    LOOK_FOR_CONTAINER_IMAGES=["andybavier/docker-vcpe"]
+
     class Meta:
         proxy = True
 
@@ -434,20 +436,23 @@
         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.
-        for image_name in self.LOOK_FOR_IMAGES:
+
+        slice = self.provider_service.slices.all()
+        if not slice:
+            raise XOSProgrammingError("provider service has no slice")
+        slice = slice[0]
+
+        if slice.default_isolation in ["container", "container_vm"]:
+            look_for_images = self.LOOK_FOR_CONTAINER_IMAGES
+        else:
+            look_for_images = self.LOOK_FOR_IMAGES
+
+        for image_name in look_for_images:
             images = Image.objects.filter(name = image_name)
             if images:
                 return images[0]
 
-        raise XOSProgrammingError("No VPCE image (looked for %s)" % str(self.LOOK_FOR_IMAGES))
-
-    @property
-    def use_cobm(self):
-        return self.get_attribute("use_cobm", False)
-
-    @use_cobm.setter
-    def use_cobm(self, v):
-        self.set_attribute("use_cobm", v)
+        raise XOSProgrammingError("No VPCE image (looked for %s)" % str(self.look_for_images))
 
     @creator.setter
     def creator(self, value):
@@ -465,13 +470,14 @@
         nodes = sorted(nodes, key=lambda node: node.instances.all().count())
         return nodes[0]
 
-#    def pick_node_for_container_on_metal(self):
-#        from core.models import Node
-#        nodes = list(Node.objects.all())
-#        # TODO: logic to filter nodes by which nodes are up, and which
-#        #   nodes the slice can instantiate on.
-#        nodes = sorted(nodes, key=lambda node: node.containers.all().count())
-#        return nodes[0]
+    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_vm(self):
+        # for container-in-VM, pick a VM
+        raise "Not Implemented"
 
     def pick_least_loaded_instance_in_slice(self, slices):
         for slice in slices:
@@ -492,7 +498,7 @@
                 tenant_count += 1
         return tenant_count
 
-    def manage_container_in_instance(self):
+    def manage_container(self):
         from core.models import Instance, Flavor
 
         if self.deleted:
@@ -519,13 +525,22 @@
                     raise XOSConfigurationError("No m1.small flavor")
 
                 node =self.pick_node_for_instance()
-                instance = Instance(slice = self.provider_service.slices.all()[0],
+                slice = self.provider_service.slices.all()[0]
+
+                if slice.default_isolation == "container_vm":
+                    parent = self.pick_vm()
+                else:
+                    parent = None
+
+                instance = Instance(slice = slice,
                                 node = node,
                                 image = self.image,
                                 creator = self.creator,
                                 deployment = node.site_deployment.deployment,
-                                flavor = flavors[0])
-                instance.save()
+                                flavor = flavors[0],
+                                isolation = slice.default_isolation,
+                                parent = parent)
+                self.save_instance(instance)
                 new_instance_created = True
 
             try:
@@ -536,74 +551,6 @@
                     instance.delete()
                 raise
 
-#    def manage_container_on_metal(self):
-#        from core.models import Container, Instance, Flavor, Port
-#
-#        if self.deleted:
-#            return
-#
-#        if (self.container is not None):
-#            self.container.delete()
-#            self.container = None
-#
-#        if self.container is None:
-#            if not self.provider_service.slices.count():
-#                raise XOSConfigurationError("The VCPE service has no slices")
-#
-#            slice = self.provider_service.slices.all()[0]
-#            node = self.pick_node_for_container_on_metal()
-#
-#            # Our current docker network strategy requires that there be some
-#            # instance on the server that connects to the networks, so that
-#            # the containers can piggyback off of that configuration.
-#            instances = Instance.objects.filter(slice=slice, node=node)
-#            if not instances:
-#                flavors = Flavor.objects.filter(name="m1.small")
-#                if not flavors:
-#                    raise XOSConfigurationError("No m1.small flavor")
-#
-#                node =self.pick_node_for_instance()
-#                instance = Instance(slice = self.provider_service.slices.all()[0],
-#                                node = node,
-#                                image = self.image,
-#                                creator = self.creator,
-#                                deployment = node.site_deployment.deployment,
-#                                flavor = flavors[0])
-#                instance.save()
-#
-#            # Now make the container...
-#            container = Container(slice = slice,
-#                            node = node,
-#                            docker_image = "andybavier/docker-vcpe",
-#                            creator = self.creator,
-#                            no_sync=True)
-#            container.save()
-#
-#            # ... and add the ports for the container
-#            # XXX probably should be done in model_policy
-#            for network in slice.networks.all():
-#                if (network.name.endswith("-nat")):
-#                    continue
-#                port = Port(network = network,
-#                            container = container)
-#                port.save()
-#
-#            container.no_sync = False
-#            container.save()
-#
-#            try:
-#                self.container = container
-#                super(TenantWithContainer, self).save()
-#            except:
-#                container.delete()
-#                raise
-
-    def manage_container(self):
-#        if self.use_cobm:
-#            self.manage_container_on_metal()
-#        else:
-            self.manage_container_in_instance()
-
     def cleanup_container(self):
         if self.instance:
             if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
@@ -614,10 +561,6 @@
             else:
                 self.instance.delete()
             self.instance = None
-#        if self.container:
-#            # print "XXX cleanup container", self.container
-#            self.container.delete()
-#            self.container = None
 
 class CoarseTenant(Tenant):
     """ TODO: rename "CoarseTenant" --> "StaticTenant" """
diff --git a/xos/core/models/slice.py b/xos/core/models/slice.py
index 18d3cb6..df36b26 100644
--- a/xos/core/models/slice.py
+++ b/xos/core/models/slice.py
@@ -19,6 +19,8 @@
 # Create your models here.
 
 class Slice(PlCoreBase):
+    ISOLATION_CHOICES = (('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))
+
     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)
@@ -37,6 +39,8 @@
     default_image = models.ForeignKey(Image, 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")
+
     def __unicode__(self):  return u'%s' % (self.name)
 
     @property
diff --git a/xos/observers/base/SyncInstanceUsingAnsible.py b/xos/observers/base/SyncInstanceUsingAnsible.py
index 901bc97..5bb8250 100644
--- a/xos/observers/base/SyncInstanceUsingAnsible.py
+++ b/xos/observers/base/SyncInstanceUsingAnsible.py
@@ -44,9 +44,11 @@
 
         return o.instance
 
-    def run_playbook(self, o, fields):
+    def run_playbook(self, o, fields, template_name=None):
+        if not template_name:
+            template_name = self.template_name
         tStart = time.time()
-        run_template_ssh(self.template_name, fields)
+        run_template_ssh(template_name, fields)
         logger.info("playbook execution time %d" % int(time.time()-tStart))
 
     def pre_sync_hook(self, o, fields):
@@ -61,14 +63,83 @@
     def prepare_record(self, o):
         pass
 
+    def get_node(self,o):
+        return o.node
+
+    def get_node_key(self, node):
+        return "/root/setup/node_key"
+
+    def get_ansible_fields(self, instance):
+        # return all of the fields that tell Ansible how to talk to the context
+        # that's setting up the container.
+
+        if (instance.isolation == "vm"):
+            # legacy where container was configured by sync_vcpetenant.py
+
+            fields = { "instance_name": instance.name,
+                       "hostname": instance.node.name,
+                       "instance_id": instance.instance_id,
+                       "username": "ubuntu",
+                     }
+            key_name = self.service_key_name
+        elif (instance.isolation == "container"):
+            # container on bare metal
+            node = self.get_node(instance)
+            hostname = node.name
+            fields = { "hostname": hostname,
+                       "baremetal_ssh": True,
+                       "instance_name": "rootcontext",
+                       "username": "root",
+                       "container_name": "%s-%s" % (instance.slice.name, str(instance.id))
+                     }
+            key_name = self.get_node_key(node)
+        else:
+            # container in a VM
+            if not instance.parent:
+                raise Exception("Container-in-VM has no parent")
+            if not instance.parent.instance_id:
+                raise Exception("Container-in-VM parent is not yet instantiated")
+            if not instance.parent.slice.service:
+                raise Exception("Container-in-VM parent has no service")
+            if not instance.parent.slice.service.private_key_fn:
+                raise Exception("Container-in-VM parent service has no private_key_fn")
+            fields = { "hostname": instance.parent.node.name,
+                       "instance_name": instance.parent.name,
+                       "instance_id": instance.parent.instance_id,
+                       "username": "ubuntu",
+                       "nat_ip": instance.parent.get_ssh_ip(),
+                       "container_name": "%s-%s" % (instance.slice.name, str(instance.id))
+                         }
+            key_name = instance.parent.slice.service.private_key_fn
+
+        if not os.path.exists(key_name):
+            raise Exception("Node key %s does not exist" % node_key_name)
+
+        key = file(key_name).read()
+
+        fields["private_key"] = key
+
+        # now the ceilometer stuff
+
+        cslice = ControllerSlice.objects.get(slice=instance.slice)
+        if not cslice:
+            raise Exception("Controller slice object for %s does not exist" % instance.slice.name)
+
+        cuser = ControllerUser.objects.get(user=instance.creator)
+        if not cuser:
+            raise Exception("Controller user object for %s does not exist" % instance.creator)
+
+        fields.update({"keystone_tenant_id": cslice.tenant_id,
+                       "keystone_user_id": cuser.kuser_id,
+                       "rabbit_user": instance.controller.rabbit_user,
+                       "rabbit_password": instance.controller.rabbit_password,
+                       "rabbit_host": instance.controller.rabbit_host})
+
+        return fields
+
     def sync_record(self, o):
         logger.info("sync'ing object %s" % str(o))
 
-        if not os.path.exists(self.service_key_name):
-            raise Exception("Service key %s does not exist" % self.service_key_name)
-
-        service_key = file(self.service_key_name).read()
-
         self.prepare_record(o)
 
         instance = self.get_instance(o)
@@ -92,25 +163,9 @@
                 self.defer_sync(o, "waiting on instance.instance_name")
                 return
 
-            cslice = ControllerSlice.objects.get(slice=instance.slice)
-            if not cslice:
-                raise Exception("Controller slice object for %s does not exist" % instance.slice.name)
+            fields = self.get_ansible_fields(instance)
 
-            cuser = ControllerUser.objects.get(user=instance.creator)
-            if not cuser:
-                raise Exception("Controller user object for %s does not exist" % instance.creator)
-
-            fields = { "instance_name": instance.name,
-                       "hostname": instance.node.name,
-                       "instance_id": instance.instance_id,
-                       "private_key": service_key,
-                       "keystone_tenant_id": cslice.tenant_id,
-                       "keystone_user_id": cuser.kuser_id,
-                       "rabbit_user": instance.controller.rabbit_user,
-                       "rabbit_password": instance.controller.rabbit_password,
-                       "rabbit_host": instance.controller.rabbit_host,
-                       "ansible_tag": o.__class__.__name__ + "_" + str(o.id)
-                     }
+            fields["ansible_tag"] =  o.__class__.__name__ + "_" + str(o.id)
 
         # If 'o' defines a 'sync_attributes' list, then we'll copy those
         # attributes into the Ansible recipe's field list automatically.
diff --git a/xos/observers/vcpe/steps/sync_vcpetenant.py b/xos/observers/vcpe/steps/sync_vcpetenant.py
index 120954b..4f3886e 100644
--- a/xos/observers/vcpe/steps/sync_vcpetenant.py
+++ b/xos/observers/vcpe/steps/sync_vcpetenant.py
@@ -131,7 +131,8 @@
                 "dnsdemux_ip": dnsdemux_ip,
                 "cdn_prefixes": cdn_prefixes,
                 "bbs_addrs": bbs_addrs,
-                "full_setup": full_setup}
+                "full_setup": full_setup,
+                "isolation": o.instance.isolation}
 
         # add in the sync_attributes that come from the SubscriberRoot object
 
@@ -209,7 +210,10 @@
         if quick_update:
             logger.info("quick_update triggered; skipping ansible recipe")
         else:
-            super(SyncVCPETenant, self).run_playbook(o, fields)
+            if o.instance.isolation in ["container", "container_vm"]:
+                super(SyncVCPETenant, self).run_playbook(o, fields, "sync_vcpetenant_new.yaml")
+            else:
+                super(SyncVCPETenant, self).run_playbook(o, fields)
 
         o.last_ansible_hash = ansible_hash
 
diff --git a/xos/observers/vcpe/steps/sync_vcpetenant_new.yaml b/xos/observers/vcpe/steps/sync_vcpetenant_new.yaml
new file mode 100644
index 0000000..e96f3c5
--- /dev/null
+++ b/xos/observers/vcpe/steps/sync_vcpetenant_new.yaml
@@ -0,0 +1,85 @@
+---
+- hosts: {{ instance_name }}
+  gather_facts: False
+  connection: ssh
+  user: {{ username }}
+  sudo: yes
+  vars:
+      container_name: {{ container_name }}
+      cdn_enable: {{ cdn_enable }}
+      dnsdemux_ip: {{ dnsdemux_ip }}
+      firewall_enable: {{ firewall_enable }}
+      url_filter_enable: {{ url_filter_enable }}
+      vlan_ids:
+        {% for vlan_id in vlan_ids %}
+        - {{ vlan_id }}
+        {% endfor %}
+      c_tags:
+        {% for c_tag in c_tags %}
+        - {{ c_tag }}
+        {% endfor %}
+      s_tags:
+        {% for s_tag in s_tags %}
+        - {{ s_tag }}
+        {% endfor %}
+      firewall_rules:
+        {% for firewall_rule in firewall_rules.split("\n") %}
+        - {{ firewall_rule }}
+        {% endfor %}
+      cdn_prefixes:
+        {% for prefix in cdn_prefixes %}
+        - {{ prefix }}
+        {% endfor %}
+      bbs_addrs:
+        {% for bbs_addr in bbs_addrs %}
+        - {{ bbs_addr }}
+        {% endfor %}
+      nat_ip: {{ nat_ip }}
+      nat_mac: {{ nat_mac }}
+      lan_ip: {{ lan_ip }}
+      lan_mac: {{ lan_mac }}
+      wan_ip: {{ wan_ip }}
+      wan_mac: {{ wan_mac }}
+      wan_container_mac: {{ wan_container_mac }}
+      wan_next_hop: 10.0.1.253   # FIX ME
+      private_ip: {{ private_ip }}
+      private_mac: {{ private_mac }}
+      hpc_client_ip: {{ hpc_client_ip }}
+      hpc_client_mac: {{ hpc_client_mac }}
+      keystone_tenant_id: {{ keystone_tenant_id }}
+      keystone_user_id: {{ keystone_user_id }}
+      rabbit_user: {{ rabbit_user }}
+      rabbit_password: {{ rabbit_password }}
+      rabbit_host: {{ rabbit_host }}
+
+  tasks:
+  - name: vCPE basic dnsmasq config
+    copy: src=/opt/xos/observers/vcpe/files/vcpe.dnsmasq dest=/var/container_volumes/{{ container_name }}/etc/dnsmasq.d/vcpe.conf owner=root group=root
+    notify:
+    - stop dnsmasq
+    - start dnsmasq
+
+  - name: dnsmasq config
+    template: src=/opt/xos/observers/vcpe/templates/dnsmasq_servers.j2 dest=/var/container_volumes/{{ container_name }}/etc/dnsmasq.d/servers.conf owner=root group=root
+    notify:
+    - stop dnsmasq
+    - start dnsmasq
+
+  handlers:
+  - name: stop dnsmasq
+    shell: docker exec {{ container_name }} /usr/bin/killall dnsmasq
+
+  - name: start dnsmasq
+    shell: docker exec {{ container_name }} /usr/sbin/service dnsmasq start
+
+  - name: restart vcpe
+    shell: service {{ container_name }} stop; sleep 1; service vcpe-{{ vlan_ids[0] }} start
+
+  - name: stop vcpe
+    service: name={{ container_name }} state=stopped
+
+  - name: remove container
+    docker: name={{ container_name }} state=absent image=docker-vcpe
+
+  - name: start vcpe
+    service: name={{ container_name }} state=started
diff --git a/xos/openstack_observer/steps/sync_container.py b/xos/openstack_observer/steps/sync_container.py
index 915ef3d..fdee9e7 100644
--- a/xos/openstack_observer/steps/sync_container.py
+++ b/xos/openstack_observer/steps/sync_container.py
@@ -6,6 +6,7 @@
 import time
 from django.db.models import F, Q
 from xos.config import Config
+from observers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
 from observer.syncstep import SyncStep
 from observer.ansible import run_template_ssh
 from core.models import Service, Slice, Instance
@@ -18,7 +19,7 @@
 
 logger = Logger(level=logging.INFO)
 
-class SyncContainer(SyncStep):
+class SyncContainer(SyncInstanceUsingAnsible):
     provides=[Instance]
     observes=Instance
     requested_interval=0
@@ -32,12 +33,6 @@
         objs = [x for x in objs if x.isolation in ["container", "container_vm"]]
         return objs
 
-    def get_node(self,o):
-        return o.node
-
-    def get_node_key(self, node):
-        return "/root/setup/node_key"
-
     def get_instance_port(self, container_port):
         for p in container_port.network.links.all():
             if (p.instance) and (p.instance.isolation=="vm") and (p.instance.node == container_port.instance.node) and (p.mac):
@@ -48,13 +43,14 @@
         i=0
         ports = []
         for port in o.ports.all():
-            if not port.mac:
+            if (not port.ip):
+                # 'unmanaged' ports may have an ip, but no mac
+                # XXX: are there any ports that have a mac but no ip?
                 raise Exception("Port on network %s is not yet ready" % port.network.name)
 
             pd={}
-            pd["device"] = "eth%d" % i
-            pd["mac"] = port.mac
-            pd["ip"] = port.ip
+            pd["mac"] = port.mac or ""
+            pd["ip"] = port.ip or ""
 
             if o.isolation == "container":
                 # container on bare metal
@@ -71,9 +67,17 @@
                 pd["snoop_instance_id"] = ""
                 pd["src_device"] = "eth%d" % i
 
+            for (k,v) in port.get_parameters().items():
+                pd[k] = v
+
             ports.append(pd)
 
-            i = i + 1
+        # for any ports that don't have a device, assign one
+        used_ports = [x["device"] for x in ports if ("device" in x)]
+        avail_ports = ["eth%d"%i for i in range(0,64) if ("eth%d"%i not in used_ports)]
+        for port in ports:
+            if not port.get("device",None):
+                port["device"] = avail_ports.pop(0)
 
         return ports
 
@@ -95,39 +99,7 @@
     def sync_record(self, o):
         logger.info("sync'ing object %s" % str(o))
 
-        if o.isolation=="container":
-            # container on bare metal
-            node = self.get_node(o)
-            key_name = self.get_node_key(node)
-            hostname = node.name
-            fields = { "hostname": hostname,
-                       "baremetal_ssh": True,
-                       "instance_name": "rootcontext",
-                       "username": "root",
-                     }
-        else:
-            # container in a VM
-            if not o.parent:
-                raise Exception("Container-in-VM has no parent")
-            if not o.parent.instance_id:
-                raise Exception("Container-in-VM parent is not yet instantiated")
-            if not o.parent.slice.service:
-                raise Exception("Container-in-VM parent has no service")
-            if not o.parent.slice.service.private_key_fn:
-                raise Exception("Container-in-VM parent service has no private_key_fn")
-            key_name = o.parent.slice.service.private_key_fn
-            fields = { "hostname": o.parent.node.name,
-                       "instance_name": o.parent.name,
-                       "instance_id": o.parent.instance_id,
-                       "username": "ubuntu",
-                       "nat_ip": o.parent.get_ssh_ip() }
-
-        if not os.path.exists(key_name):
-            raise Exception("Node key %s does not exist" % node_key_name)
-
-        key = file(key_name).read()
-
-        fields["private_key"] = key
+        fields = self.get_ansible_fields(o)
 
         # If 'o' defines a 'sync_attributes' list, then we'll copy those
         # attributes into the Ansible recipe's field list automatically.
diff --git a/xos/openstack_observer/steps/sync_container.yaml b/xos/openstack_observer/steps/sync_container.yaml
index 4eac2a1..4a050a5 100644
--- a/xos/openstack_observer/steps/sync_container.yaml
+++ b/xos/openstack_observer/steps/sync_container.yaml
@@ -11,11 +11,14 @@
     ports:
     {% for port in ports %}
        - device: {{ port.device }}
-         mac: {{ port.mac }}
+         mac: {{ port.mac|default("") }}
          ip: {{ port.ip }}
          snoop_instance_mac: {{ port.snoop_instance_mac }}
          snoop_instance_id: {{ port.snoop_instance_id }}
          src_device: {{ port.src_device }}
+         s_tag: {{ port.s_tag|default("")  }}
+         c_tag: {{ port.c_tag|default("") }}
+         next_hop: {{ port.next_hop|default("") }}
     {% endfor %}
     volumes:
     {% for volume in volumes %}
diff --git a/xos/openstack_observer/steps/sync_ports.py b/xos/openstack_observer/steps/sync_ports.py
index 178fa86..bfdde8c 100644
--- a/xos/openstack_observer/steps/sync_ports.py
+++ b/xos/openstack_observer/steps/sync_ports.py
@@ -185,10 +185,6 @@
                     if neutron_port["fixed_ips"]:
                         port.ip = neutron_port["fixed_ips"][0]["ip_address"]
                     port.mac = neutron_port["mac_address"]
-
-                    neutron_network = driver.shell.quantum.list_networks(cn.net_id)["networks"][0]
-                    if "provider:segmentation_id" in neutron_network:
-                        port.segmentation_id = neutron_network["provider:segmentation_id"]
                 except:
                     logger.log_exc("failed to create neutron port for %s" % port)
                     continue
diff --git a/xos/openstack_observer/templates/start-container.sh.j2 b/xos/openstack_observer/templates/start-container.sh.j2
index 967578d..9ed7faa 100644
--- a/xos/openstack_observer/templates/start-container.sh.j2
+++ b/xos/openstack_observer/templates/start-container.sh.j2
@@ -25,13 +25,36 @@
 
 {% if ports %}
 {% for port in ports %}
+
+{% if port.next_hop %}
+NEXTHOP_ARG="@{{ port.next_hop }}"
+{% else %}
+NEXTHOP_ARG=""
+{% endif %}
+
+{% if port.c_tag %}
+CTAG_ARG="@{{ port.c_tag }}"
+{% else %}
+CTAG_ARG=""
+{% endif %}
+
 {% if port.src_device %}
 # container-in-VM
-docker exec $CONTAINER ifconfig {{ port.src_device }} >> /dev/null || pipework {{ port.src_device }} -i {{ port.device }} $CONTAINER {{ port.ip }}/24 {{ port.mac }}
+VLAN_ARG=""
+NEXTHOP_ARG=""
+CMD="docker exec $CONTAINER ifconfig {{ port.src_device }} >> /dev/null || pipework {{ port.src_device }} -i {{ port.device }} $CONTAINER {{ port.ip }}/24$NEXTHOP_ARG {{ port.mac }} $CTAG_ARG"
+echo $CMD
+eval $CMD
+
 {% else %}
 # container-on-metal
 IP="{{ port.ip }}"
+{% if port.mac %}
 MAC="{{ port.mac }}"
+{% else %}
+MAC=""
+{% endif %}
+
 DEVICE="{{ port.device }}"

 INSTANCE_MAC="{{ port.snoop_instance_mac }}"
 INSTANCE_ID="{{ port.snoop_instance_id }}"
@@ -39,7 +62,7 @@
 INSTANCE_TAP=${INSTANCE_TAP:3}
 VLAN_ID=`ovs-vsctl show | grep -i -A 1 port.*$INSTANCE_TAP | grep -i tag | awk '{print $2}'`
 TAP="con`echo ${CONTAINER}_$DEVICE|md5sum|awk '{print $1}'`"
-TAP=${TAP:0:12}
+TAP=${TAP:0:10}
 echo im=$INSTANCE_MAC ii=$INSTANCE_ID it=$INSTANCE_TAP vlan=$VLAN_ID tap=$TAP con=$CONTAINER dev=$DEVICE mac=$MAC
 ovs-vsctl show | grep -i $TAP
 if [[ $? == 1 ]]; then
@@ -49,7 +72,9 @@
     echo tap exists
 fi
 
-docker exec $CONTAINER ifconfig $DEVICE >> /dev/null || pipework $TAP -i $DEVICE $CONTAINER $IP/24 $MAC
+CMD="docker exec $CONTAINER ifconfig $DEVICE >> /dev/null || pipework $TAP -i $DEVICE $CONTAINER $IP/24$NEXTHOP_ARG $MAC $CTAG_ARG"
+echo $CMD
+eval $CMD
 {% endif %}
 {% endfor %}
 {% endif %}
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index cb1cfbf..3606601 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -296,6 +296,17 @@
                 required: false
                 description: Indicates what page the user should go to on login.
 
+    tosca.nodes.NetworkParameterType:
+        derived_from: tosca.nodes.Root
+
+        description: >
+            An XOS network parameter type. May be applied to Networks and/or
+            Ports.
+
+        capabilities:
+            network_parameter_type:
+                type: tosca.capabilities.xos.NetworkParameterType
+
     tosca.nodes.NetworkTemplate:
         derived_from: tosca.nodes.Root
 
@@ -770,3 +781,7 @@
     tosca.capabilities.xos.DashboardView:
         derived_from: tosca.capabilities.Root
         description: An XOS DashboardView
+
+    tosca.capabilities.xos.NetworkParameterType:
+        derived_from: tosca.capabilities.Root
+        description: An XOS NetworkParameterType
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 22be263..7a6030e 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -440,6 +440,17 @@
                 required: false
                 description: Indicates what page the user should go to on login.
 
+    tosca.nodes.NetworkParameterType:
+        derived_from: tosca.nodes.Root
+
+        description: >
+            An XOS network parameter type. May be applied to Networks and/or
+            Ports.
+
+        capabilities:
+            network_parameter_type:
+                type: tosca.capabilities.xos.NetworkParameterType
+
     tosca.nodes.NetworkTemplate:
         derived_from: tosca.nodes.Root
 
@@ -980,3 +991,7 @@
     tosca.capabilities.xos.DashboardView:
         derived_from: tosca.capabilities.Root
         description: An XOS DashboardView
+
+    tosca.capabilities.xos.NetworkParameterType:
+        derived_from: tosca.capabilities.Root
+        description: An XOS NetworkParameterType
diff --git a/xos/tosca/resources/networkparametertype.py b/xos/tosca/resources/networkparametertype.py
new file mode 100644
index 0000000..e0cc93e
--- /dev/null
+++ b/xos/tosca/resources/networkparametertype.py
@@ -0,0 +1,38 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import Slice,User,Network,NetworkParameterType
+
+from xosresource import XOSResource
+
+class XOSNetworkParameterType(XOSResource):
+    provides = "tosca.nodes.NetworkParameterType"
+    xos_model = NetworkParameterType
+    copyin_props = []
+
+    def get_xos_args(self):
+        args = super(XOSNetworkParameterType, self).get_xos_args()
+
+        return args
+
+    def create(self):
+        xos_args = self.get_xos_args()
+
+        networkParameterType = NetworkParameterType(**xos_args)
+        networkParameterType.caller = self.user
+        networkParameterType.save()
+
+        self.info("Created NetworkParameterType '%s' " % (str(networkParameterType), ))
+
+    def delete(self, obj):
+        if obj.networkparameters.exists():
+            return
+
+        super(XOSNetworkParameterType, self).delete(obj)
+
+
+