Merge branch 'master' of https://github.com/open-cloud/xos
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index b69b7fd..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,144 +0,0 @@
-FROM       ubuntu:14.04.3
-MAINTAINER Andy Bavier <acb@cs.princeton.edu>
-
-# XXX Workaround for docker bug:
-# https://github.com/docker/docker/issues/6345
-# Kernel 3.15 breaks docker, uss the line below as a workaround
-# until there is a fix
-RUN ln -s -f /bin/true /usr/bin/chfn
-# XXX End workaround
-
-# Install.
-RUN apt-get update && apt-get install -y \
-    git \
-    postgresql \
-    python-psycopg2 \
-    graphviz \
-    graphviz-dev \
-    libxslt1.1 \
-    libxslt1-dev \
-    python-pip \
-    tar \
-    gcc \
-    python-httplib2 \
-    geoip-database \
-    libgeoip1 \
-    wget \
-    curl \
-    python-dev \
-    libyaml-dev \
-    pkg-config \
-    python-pycurl
-
-RUN pip install django==1.7
-RUN pip install djangorestframework==2.4.4
-RUN pip install markdown  # Markdown support for the browseable API.
-RUN pip install pyyaml    # YAML content-type support.
-RUN pip install django-filter  # Filtering support
-RUN pip install lxml  # XML manipulation library
-RUN pip install netaddr # IP Addr library
-RUN pip install pytz
-RUN pip install django-timezones
-RUN pip install requests
-RUN pip install django-crispy-forms
-RUN pip install django-geoposition
-RUN pip install django-extensions
-RUN pip install django-suit
-RUN pip install django-bitfield
-RUN pip install django-ipware
-RUN pip install django-encrypted-fields
-RUN pip install python-keyczar
-RUN pip install pygraphviz
-RUN pip install dnslib
-
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-keystoneclient
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-novaclient
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-neutronclient
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-glanceclient
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-ceilometerclient
-
-RUN pip install django_rest_swagger
-
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-setuptools
-RUN easy_install django_evolution
-RUN easy_install python_gflags
-RUN easy_install --upgrade httplib2
-RUN easy_install google_api_python_client
-RUN easy_install httplib2.ca_certs_locater
-
-# Install custom Ansible
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-crypto
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-yaml
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-client
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-paramiko
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-jinja2
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-httplib2
-RUN git clone -b release1.8.2 git://github.com/ansible/ansible.git /opt/ansible
-RUN git clone -b release1.8.2 git://github.com/ansible/ansible-modules-extras.git /opt/ansible/lib/ansible/modules/extras
-RUN git clone -b release1.8.2 git://github.com/ansible/ansible-modules-extras.git /opt/ansible/v2/ansible/modules/extras
-RUN git clone git://github.com/sb98052/ansible-modules-core.git /opt/ansible/lib/ansible/modules/core
-RUN git clone git://github.com/sb98052/ansible-modules-core.git /opt/ansible/v2/ansible/modules/core
-ADD ansible-hosts /etc/ansible/hosts
-
-ADD http://code.jquery.com/jquery-1.9.1.min.js /usr/local/lib/python2.7/dist-packages/suit/static/suit/js/
-
-# For Observer
-RUN git clone git://git.planet-lab.org/fofum.git /tmp/fofum
-RUN cd /tmp/fofum; python setup.py install
-RUN rm -rf /tmp/fofum
-
-RUN mkdir -p /usr/local/share /bin
-ADD http://phantomjs.googlecode.com/files/phantomjs-1.7.0-linux-x86_64.tar.bz2 /usr/local/share/
-RUN tar jxvf /usr/local/share/phantomjs-1.7.0-linux-x86_64.tar.bz2 -C /usr/local/share/
-RUN rm -f /usr/local/share/phantomjs-1.7.0-linux-x86_64.tar.bz2
-RUN ln -s /usr/local/share/phantomjs-1.7.0-linux-x86_64 /usr/local/share/phantomjs
-RUN ln -s /usr/local/share/phantomjs/bin/phantomjs /bin/phantomjs
-
-# Supervisor
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -y supervisor
-ADD observer.conf /etc/supervisor/conf.d/
-
-# Get XOS
-ADD xos /opt/xos
-
-# Initscript is broken in Ubuntu
-#ADD observer-initscript /etc/init.d/xosobserver
-
-RUN chmod +x /opt/xos/scripts/opencloud
-RUN /opt/xos/scripts/opencloud genkeys
-
-# Workaround for AUFS issue
-# https://github.com/docker/docker/issues/783#issuecomment-56013588
-RUN mkdir /etc/ssl/private-copy; mv /etc/ssl/private/* /etc/ssl/private-copy/; rm -r /etc/ssl/private; mv /etc/ssl/private-copy /etc/ssl/private; chmod -R 0700 /etc/ssl/private; chown -R postgres /etc/ssl/private
-
-# Set postgres password to match default value in settings.py
-RUN service postgresql start; sudo -u postgres psql -c "alter user postgres with password 'password';"
-
-# Turn DEBUG on so that devel server will serve static files
-#    (not necessary if --insecure is passed to 'manage.py runserver')
-# RUN sed -i 's/DEBUG = False/DEBUG = True/' /opt/xos/xos/settings.py
-
-# Cruft to workaround problems with migrations, should go away...
-RUN /opt/xos/scripts/opencloud remigrate
-
-# git clone uses cached copy, doesn't pick up latest
-RUN git -C /opt/ansible pull
-RUN git -C /opt/ansible/lib/ansible/modules/core pull
-RUN git -C /opt/ansible/v2/ansible/modules/core pull
-
-# install Tosca engine
-RUN apt-get install -y m4
-RUN pip install python-dateutil
-RUN bash /opt/xos/tosca/install_tosca.sh
-
-EXPOSE 8000
-
-# Set environment variables.
-ENV HOME /root
-
-# Define working directory.
-WORKDIR /root
-
-# Define default command.
-#CMD ["/bin/bash"]
-CMD /opt/xos/scripts/docker_start_xos
diff --git a/Dockerfile.cord b/Dockerfile.cord
deleted file mode 100644
index ee0879d..0000000
--- a/Dockerfile.cord
+++ /dev/null
@@ -1,7 +0,0 @@
-FROM       xos:latest
-MAINTAINER Andy Bavier <acb@cs.princeton.edu>
-
-ADD xos/observers/vcpe/supervisor/vcpe-observer.conf /etc/supervisor/conf.d/
-RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/vcpe/vcpe_observer_config
-ADD xos/observers/monitoring_channel/supervisor/monitoring_channel_observer.conf /etc/supervisor/conf.d/
-RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/monitoring_channel/monitoring_channel_observer_config
diff --git a/README.Docker b/README.Docker
deleted file mode 100644
index 0076bca..0000000
--- a/README.Docker
+++ /dev/null
@@ -1,49 +0,0 @@
-The Dockerfile in this directory will build a Docker image for running
-XOS using the Django development server.  It copies whatever files are 
-in the local repository into the image. Here's how to do it:
-
-1. A minimal initial_data.json is provided. The login credentials
-   for this initial_data.json are username=padmin@vicci.org, 
-   password=letmein.
-
-   This initial_data.json doesn't contain any nodes and is suitable
-   for fresh installations. To obtain an initial_data.json (for demo
-   purposes) that contains an interesting set of Nodes and Slices,
-   a dump can be made on portal.opencloud.us:
-
-   1) log in to portal, and run:
-         $ sudo /opt/xos/scripts/opencloud dumpdata
-
-   2) replace the initial_data.json file with the dumpdata
-      file produced above.
-
-2. $ docker build -t xos .
-
-3. $ docker run -t -i -p 8000:8000 xos
-
-4. Now you will have a bash prompt as root inside the XOS container.
-   Start up XOS:
-   
-   # /opt/xos/scripts/opencloud runserver
-
-You can access the XOS login at http:<server>:8000, where <server> is
-the name of the server running Docker.
-
-5. From another terminal window, you can run following command to find
-the running container id
-
-   $ docker ps
-    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
-    a3b668454d21        xos:latest          "/bin/bash"         3 hours ago         Up 3 hours          0.0.0.0:8000->8000/tcp   romantic_bohr
-
-and then you can have another bash prompt (in a different TTY) as root inside the XOS container.
-
-   $ docker exec -it a3b668454d21 bash
-
-and start observer
-
-    # python /opt/xos/xos-observer.py
-
-STILL TO DO
------------
-* Test Observer
diff --git a/cloudlab-init.sh b/cloudlab-init.sh
deleted file mode 100755
index 9a2c94a..0000000
--- a/cloudlab-init.sh
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/bin/bash
-set -x
-
-# This script assumes that it is being run on the ctl node of the OpenStack
-# profile on CloudLab.
-
-XOS="http://ctl:9999/"
-AUTH="padmin@vicci.org:letmein"
-CORD=0
-IMAGE="xos"
-
-# Create public key if none present
-[ -e ~/.ssh/id_rsa ] || cat /dev/zero | ssh-keygen -q -N ""
-
-# Install Docker
-which docker > /dev/null || wget -qO- https://get.docker.com/ | sh
-sudo usermod -aG docker $(whoami)
-
-sudo apt-get -y install httpie
-
-if [ "$CORD" -ne 0 ]
-then
-    cp ~/.ssh/id_rsa.pub xos/observers/vcpe/vcpe_public_key
-    cp ~/.ssh/id_rsa     xos/observers/vcpe/vcpe_private_key
-    cp ~/.ssh/id_rsa.pub xos/observers/monitoring_channel/monitoring_channel_public_key
-    cp ~/.ssh/id_rsa     xos/observers/monitoring_channel/monitoring_channel_private_key
-fi
-
-sudo docker build -t xos .
-
-if [ "$CORD" -ne 0 ]
-then
-    sudo docker build -t cord -f Dockerfile.cord .
-    IMAGE="cord"
-fi
-
-# OpenStack is using port 8000...
-MYIP=$( hostname -i )
-MYFLATLANIF=$( sudo bash -c "netstat -i" |grep flat|awk '{print $1}' )
-MYFLATLANIP=$( ifconfig $MYFLATLANIF | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}' )
-sudo docker run -d --add-host="ctl:$MYIP" -p 9999:8000 $IMAGE
-
-echo "Waiting for XOS to come up"
-until http $XOS &> /dev/null
-do
-    sleep 1
-done
-
-# Copy public key
-# BUG: Shouldn't have to set the 'enacted' field...
-PUBKEY=$( cat ~/.ssh/id_rsa.pub )
-http --auth $AUTH PATCH $XOS/xos/users/1/ public_key="$PUBKEY" enacted=$( date "+%Y-%m-%dT%T")
-
-# Set up controller
-sudo cp /root/setup/admin-openrc.sh /tmp
-sudo chmod a+r /tmp/admin-openrc.sh
-#sudo sed -i 's/:5000/:35357/' /tmp/admin-openrc.sh
-source /tmp/admin-openrc.sh
-
-if [ "$CORD" -ne 1 ]
-then
-     http --auth $AUTH POST $XOS/xos/controllers/ name=CloudLab deployment=$XOS/xos/deployments/1/ backend_type=OpenStack version=Kilo auth_url=$OS_AUTH_URL admin_user=$OS_USERNAME admin_tenant=$OS_TENANT_NAME admin_password=$OS_PASSWORD domain=Default
-else
-     sudo cp /root/setup/settings /tmp
-     sudo chmod a+r /tmp/settings
-     source /tmp/settings
-     source /tmp/admin-openrc.sh
-     http --auth $AUTH POST $XOS/xos/controllers/ name=CloudLab deployment=$XOS/xos/deployments/1/ backend_type=OpenStack version=Kilo auth_url=$OS_AUTH_URL admin_user=$OS_USERNAME admin_tenant=$OS_TENANT_NAME admin_password=$OS_PASSWORD domain=Default rabbit_host=$MYFLATLANIP rabbit_user=$RABBIT_USER rabbit_password=$RABBIT_PASS
-fi
-
-# Add controller to site
-http --auth $AUTH PATCH $XOS/xos/sitedeployments/1/ controller=$XOS/xos/controllers/1/
-
-# Add image
-http --auth $AUTH POST $XOS/xos/images/ name=trusty-server-multi-nic disk_format=QCOW2 container_format=BARE
-
-# Activate image
-http --auth $AUTH POST $XOS/xos/imagedeploymentses/ deployment=$XOS/xos/deployments/1/ image=$XOS/xos/images/1/
-
-# Add node
-NODES=$( sudo bash -c "source /root/setup/admin-openrc.sh ; nova hypervisor-list" |grep cloudlab|awk '{print $4}' )
-for NODE in $NODES
-do
-    http --auth $AUTH POST $XOS/xos/nodes/ name=$NODE site_deployment=$XOS/xos/sitedeployments/1/
-done
-
-# Modify networktemplate/2
-# BUG: Shouldn't have to set the controller_kind field, it's invalid in the initial fixture
-FLATNET=$( sudo bash -c "source /root/setup/admin-openrc.sh ; neutron net-list" |grep flat|awk '{print $4}' )
-http --auth $AUTH PATCH $XOS/xos/networktemplates/2/ shared_network_name=$FLATNET controller_kind=""
-
-if [ "$CORD" -ne 0 ]
-then
-    DOCKER=$( sudo docker ps|grep $IMAGE|awk '{print $NF}' )
-    sudo docker exec $DOCKER bash -c "cd /opt/xos/tosca; python run.py padmin@vicci.org samples/cord-cloudlab.yaml; python run.py padmin@vicci.org samples/ceilometer.yaml"
-fi
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 67d8bb6..9cde302 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -29,6 +29,7 @@
           view_url: /admin/cord/vcpeservice/$id$/
           backend_network_label: hpc_client
           public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+          private_key_fn: /opt/xos/observers/vcpe/vcpe_private_key
       artifacts:
           pubkey: /opt/xos/observers/vcpe/vcpe_public_key
 
@@ -456,7 +457,8 @@
         type: tosca.nodes.VOLTTenant
         properties:
             service_specific_id: 123
-            vlan_id: 432
+            s_tag: 222
+            c_tag: 432
         requirements:
             - provider_service:
                 node: service_volt
diff --git a/xos/core/admin.py b/xos/core/admin.py
index aaa72cb..3c35768 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -880,7 +880,7 @@
 class ServiceAdmin(XOSBaseAdmin):
     list_display = ("backend_status_icon","name","kind","versionNumber","enabled","published")
     list_display_links = ('backend_status_icon', 'name', )
-    fieldList = ["backend_status_text","name","kind","description","versionNumber","enabled","published","view_url","icon_url","public_key","service_specific_attribute","service_specific_id"]
+    fieldList = ["backend_status_text","name","kind","description","versionNumber","enabled","published","view_url","icon_url","public_key","private_key_fn","service_specific_attribute","service_specific_id"]
     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
     inlines = [ServiceAttrAsTabInline,SliceInline,ProviderTenantInline,SubscriberTenantInline,ServicePrivilegeInline]
     readonly_fields = ('backend_status_text', )
@@ -1282,13 +1282,14 @@
 class InstanceAdmin(XOSBaseAdmin):
     form = InstanceForm
     fieldsets = [
-        ('Instance Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'isolation', 'flavor', 'image', 'node', 'all_ips_string', 'instance_id', 'instance_name', 'ssh_command'], 'classes': ['suit-tab suit-tab-general'], })
+        ('Instance Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'isolation', 'flavor', 'image', 'node', 'parent', 'all_ips_string', 'instance_id', 'instance_name', 'ssh_command', ], 'classes': ['suit-tab suit-tab-general'], }),
+        ('Container Settings', {'fields': ['volumes'], 'classes': ['suit-tab suit-tab-container'], }),
     ]
     readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
     list_display = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'isolation', 'slice', 'flavor', 'image', 'node', 'deployment']
     list_display_links = ('backend_status_icon', 'all_ips_string', 'instance_id', )
 
-    suit_form_tabs =(('general', 'Instance Details'), ('ports', 'Ports'))
+    suit_form_tabs =(('general', 'Instance Details'), ('ports', 'Ports'), ('container', 'Container Settings'))
 
     inlines = [TagInline, InstancePortInline]
 
diff --git a/xos/core/models/container.py b/xos/core/models/container.py
deleted file mode 100644
index ae1d198..0000000
--- a/xos/core/models/container.py
+++ /dev/null
@@ -1,84 +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
-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 import generic
-from xos.config import Config
-from monitor import driver as monitor
-from django.core.exceptions import PermissionDenied, ValidationError
-
-config = Config()
-
-
-# Create your models here.
-class Container(PlCoreBase):
-    name = StrippedCharField(max_length=200, help_text="Container name")
-    slice = models.ForeignKey(Slice, related_name='containers')
-    node = models.ForeignKey(Node, related_name='containers')
-    creator = models.ForeignKey(User, related_name='containers', blank=True, null=True)
-    docker_image = StrippedCharField(null=True, blank=True, max_length=200, help_text="name of docker container to instantiate")
-    volumes = models.TextField(null=True, blank=True, help_text="Comma-separated list of volumes")
-
-    def __unicode__(self):
-        return u'container-%s' % str(self.id)
-
-    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('container has no creator')
-
-        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('container 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(Container, self).save(*args, **kwds)
-
-    def can_update(self, user):
-        return True
-
-    @staticmethod
-    def select_by_user(user):
-        if user.is_admin:
-            qs = Container.objects.all()
-        else:
-            slices = Slice.select_by_user(user)
-            qs = Container.objects.filter(slice__in=slices)
-        return qs
-
-    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
-
-
diff --git a/xos/core/models/instance.py b/xos/core/models/instance.py
index af1f965..cd7dd26 100644
--- a/xos/core/models/instance.py
+++ b/xos/core/models/instance.py
@@ -80,7 +80,7 @@
 
 # Create your models here.
 class Instance(PlCoreBase):
-    ISOLATION_CHOICES = (('vm', 'Virtual Machine'), ('container', 'Container'), )
+    ISOLATION_CHOICES = (('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))
 
     objects = InstanceManager()
     deleted_objects = InstanceDeletionManager()
@@ -90,7 +90,6 @@
     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')
-    #key = models.ForeignKey(Key, 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')
@@ -100,6 +99,8 @@
     tags = generic.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")
 
     def __unicode__(self):
         if self.name and Slice.objects.filter(id=self.slice_id) and (self.name != self.slice.name):
@@ -123,6 +124,19 @@
         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.
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 9646ce8..6cb7a9d 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -52,6 +52,7 @@
     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)
diff --git a/xos/model_policies/model_policy_Instance.py b/xos/model_policies/model_policy_Instance.py
index 23761f3..40f52bb 100644
--- a/xos/model_policies/model_policy_Instance.py
+++ b/xos/model_policies/model_policy_Instance.py
@@ -44,5 +44,5 @@
 		cn.backend_register = '{}'
 		cn.save()
 
-    if (instance.isolation=="container"):
+    if (instance.isolation in ["container", "container_vm"]):
         handle_container_on_metal(instance)
diff --git a/xos/openstack_observer/steps/sync_container.py b/xos/openstack_observer/steps/sync_container.py
index fef08d2..915ef3d 100644
--- a/xos/openstack_observer/steps/sync_container.py
+++ b/xos/openstack_observer/steps/sync_container.py
@@ -29,7 +29,7 @@
 
     def fetch_pending(self, deletion=False):
         objs = super(SyncContainer, self).fetch_pending(deletion)
-        objs = [x for x in objs if x.isolation=="container"]
+        objs = [x for x in objs if x.isolation in ["container", "container_vm"]]
         return objs
 
     def get_node(self,o):
@@ -56,12 +56,20 @@
             pd["mac"] = port.mac
             pd["ip"] = port.ip
 
-            instance_port = self.get_instance_port(port)
-            if not instance_port:
-                raise Exception("No instance on slice for port on network %s" % port.network.name)
+            if o.isolation == "container":
+                # container on bare metal
+                instance_port = self.get_instance_port(port)
+                if not instance_port:
+                    raise Exception("No instance on slice for port on network %s" % port.network.name)
 
-            pd["snoop_instance_mac"] = instance_port.mac
-            pd["snoop_instance_id"] = instance_port.instance.instance_id
+                pd["snoop_instance_mac"] = instance_port.mac
+                pd["snoop_instance_id"] = instance_port.instance.instance_id
+                pd["src_device"] = ""
+            else:
+                # container in VM
+                pd["snoop_instance_mac"] = ""
+                pd["snoop_instance_id"] = ""
+                pd["src_device"] = "eth%d" % i
 
             ports.append(pd)
 
@@ -72,13 +80,13 @@
     def get_extra_attributes(self, o):
         fields={}
         fields["ansible_tag"] = "container-%s" % str(o.id)
-        fields["baremetal_ssh"] = True
-        fields["instance_name"] = "rootcontext"
         fields["container_name"] = "%s-%s" % (o.slice.name, str(o.id))
         fields["docker_image"] = o.image.name
-        fields["username"] = "root"
         fields["ports"] = self.get_ports(o)
-        fields["volumes"] = [] # XXX [x.strip() for x in o.volumes.split(",")]
+        if o.volumes:
+            fields["volumes"] = [x.strip() for x in o.volumes.split(",")]
+        else:
+            fields["volumes"] = ""
         return fields
 
     def sync_fields(self, o, fields):
@@ -87,17 +95,39 @@
     def sync_record(self, o):
         logger.info("sync'ing object %s" % str(o))
 
-        node = self.get_node(o)
-        node_key_name = self.get_node_key(node)
+        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(node_key_name):
+        if not os.path.exists(key_name):
             raise Exception("Node key %s does not exist" % node_key_name)
 
-        node_key = file(node_key_name).read()
+        key = file(key_name).read()
 
-        fields = { "hostname": node.name,
-                   "private_key": node_key,
-                 }
+        fields["private_key"] = key
 
         # If 'o' defines a 'sync_attributes' list, then we'll copy those
         # attributes into the Ansible recipe's field list automatically.
@@ -109,6 +139,9 @@
 
         self.sync_fields(o, fields)
 
+        o.instance_id = fields["container_name"]
+        o.instance_name = fields["container_name"]
+
         o.save()
 
     def run_playbook(self, o, fields):
diff --git a/xos/openstack_observer/steps/sync_container.yaml b/xos/openstack_observer/steps/sync_container.yaml
index e005e58..4eac2a1 100644
--- a/xos/openstack_observer/steps/sync_container.yaml
+++ b/xos/openstack_observer/steps/sync_container.yaml
@@ -15,6 +15,7 @@
          ip: {{ port.ip }}
          snoop_instance_mac: {{ port.snoop_instance_mac }}
          snoop_instance_id: {{ port.snoop_instance_id }}
+         src_device: {{ port.src_device }}
     {% endfor %}
     volumes:
     {% for volume in volumes %}
@@ -78,6 +79,10 @@
 #      state: running
 #      image: {{ docker_image }}
 
+  - name: check if systemd is installed
+    stat: path=/usr/bin/systemctl
+    register: systemctl
+
   - name: container upstart
     template: src=/opt/xos/openstack_observer/templates/container.conf.j2 dest=/etc/init/container-{{ container_name }}.conf
 
@@ -89,6 +94,7 @@
 
   - name: restart systemd
     shell: systemctl daemon-reload
+    when: systemctl.stat.exists == True
 
   - name: Make sure container is running
     service: name=container-{{ container_name }} state=started
diff --git a/xos/openstack_observer/templates/start-container.sh.j2 b/xos/openstack_observer/templates/start-container.sh.j2
index 86491eb..967578d 100644
--- a/xos/openstack_observer/templates/start-container.sh.j2
+++ b/xos/openstack_observer/templates/start-container.sh.j2
@@ -25,6 +25,11 @@
 
 {% if ports %}
 {% for port in ports %}
+{% 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 }}
+{% else %}
+# container-on-metal
 IP="{{ port.ip }}"
 MAC="{{ port.mac }}"
 DEVICE="{{ port.device }}"

@@ -33,7 +38,7 @@
 INSTANCE_TAP=`virsh domiflist $INSTANCE_ID | grep -i $INSTANCE_MAC | awk '{print $1}'`
 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="con`echo ${CONTAINER}_$DEVICE|md5sum|awk '{print $1}'`"
 TAP=${TAP:0:12}
 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
@@ -45,6 +50,7 @@
 fi
 
 docker exec $CONTAINER ifconfig $DEVICE >> /dev/null || pipework $TAP -i $DEVICE $CONTAINER $IP/24 $MAC
+{% endif %}
 {% endfor %}
 {% endif %}
 
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 88d8f5f..cb1cfbf 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -47,6 +47,10 @@
                 type: string
                 required: false
                 description: Public key to install into Instances to allows Services to SSH into them.
+            private_key_fn:
+                type: string
+                required: false
+                description: Location of private key file
             versionNumber:
                 type: string
                 required: false
@@ -236,10 +240,14 @@
             specific vlan_id.
         properties:
             xos_base_tenant_props
-            vlan_id:
+            s_tag:
                 type: string
                 required: false
-                description: vlan_id for connection to subscriber household.
+                description: s_tag, identifies which volt port
+            c_tag:
+                type: string
+                required: false
+                description: c_tag, identifies which subscriber within s_tag
 
     tosca.nodes.User:
         derived_from: tosca.nodes.Root
@@ -443,6 +451,10 @@
             image:
                 type: tosca.capabilities.xos.Image
         properties:
+            kind:
+                type: string
+                required: false
+                description: Type of image (container | VM)
             disk_format:
                 type: string
                 required: false
@@ -594,6 +606,31 @@
                 required: false
                 description: URL to the dashboard
 
+    tosca.nodes.Compute.Container:
+      derived_from: tosca.nodes.Compute
+      description: >
+        The TOSCA Compute node represents a container on bare metal.
+      attributes:
+        private_address:
+          type: string
+        public_address:
+          type: string
+      capabilities:
+          host:
+             type: tosca.capabilities.Container
+          binding:
+             type: tosca.capabilities.network.Bindable
+          os:
+             type: tosca.capabilities.OperatingSystem
+          scalable:
+             type: tosca.capabilities.Scalable
+      requirements:
+        - local_storage:
+            capability: tosca.capabilities.Attachment
+            node: tosca.nodes.BlockStorage
+            relationship: tosca.relationships.AttachesTo
+            occurrences: [0, UNBOUNDED]
+
     tosca.relationships.MemberOfSlice:
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Slice ]
@@ -634,6 +671,10 @@
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Network ]
 
+    tosca.relationships.UsesImage:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabilities.xos.Image ]
+
     tosca.relationships.SupportsImage:
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Image ]
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 4352ef5..22be263 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -52,6 +52,10 @@
                 type: string
                 required: false
                 description: Public key to install into Instances to allows Services to SSH into them.
+            private_key_fn:
+                type: string
+                required: false
+                description: Location of private key file
             versionNumber:
                 type: string
                 required: false
@@ -90,6 +94,10 @@
                 type: string
                 required: false
                 description: Public key to install into Instances to allows Services to SSH into them.
+            private_key_fn:
+                type: string
+                required: false
+                description: Location of private key file
             versionNumber:
                 type: string
                 required: false
@@ -191,6 +199,10 @@
                 type: string
                 required: false
                 description: Public key to install into Instances to allows Services to SSH into them.
+            private_key_fn:
+                type: string
+                required: false
+                description: Location of private key file
             versionNumber:
                 type: string
                 required: false
@@ -233,6 +245,10 @@
                 type: string
                 required: false
                 description: Public key to install into Instances to allows Services to SSH into them.
+            private_key_fn:
+                type: string
+                required: false
+                description: Location of private key file
             versionNumber:
                 type: string
                 required: false
@@ -275,6 +291,10 @@
                 type: string
                 required: false
                 description: Public key to install into Instances to allows Services to SSH into them.
+            private_key_fn:
+                type: string
+                required: false
+                description: Location of private key file
             versionNumber:
                 type: string
                 required: false
@@ -364,10 +384,14 @@
                 type: string
                 required: false
                 description: Service specific ID opaque to XOS but meaningful to service
-            vlan_id:
+            s_tag:
                 type: string
                 required: false
-                description: vlan_id for connection to subscriber household.
+                description: s_tag, identifies which volt port
+            c_tag:
+                type: string
+                required: false
+                description: c_tag, identifies which subscriber within s_tag
 
     tosca.nodes.User:
         derived_from: tosca.nodes.Root
@@ -582,6 +606,10 @@
             image:
                 type: tosca.capabilities.xos.Image
         properties:
+            kind:
+                type: string
+                required: false
+                description: Type of image (container | VM)
             disk_format:
                 type: string
                 required: false
@@ -788,6 +816,31 @@
                 required: false
                 description: URL to the dashboard
 
+    tosca.nodes.Compute.Container:
+      derived_from: tosca.nodes.Compute
+      description: >
+        The TOSCA Compute node represents a container on bare metal.
+      attributes:
+        private_address:
+          type: string
+        public_address:
+          type: string
+      capabilities:
+          host:
+             type: tosca.capabilities.Container
+          binding:
+             type: tosca.capabilities.network.Bindable
+          os:
+             type: tosca.capabilities.OperatingSystem
+          scalable:
+             type: tosca.capabilities.Scalable
+      requirements:
+        - local_storage:
+            capability: tosca.capabilities.Attachment
+            node: tosca.nodes.BlockStorage
+            relationship: tosca.relationships.AttachesTo
+            occurrences: [0, UNBOUNDED]
+
     tosca.relationships.MemberOfSlice:
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Slice ]
@@ -828,6 +881,10 @@
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Network ]
 
+    tosca.relationships.UsesImage:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabilities.xos.Image ]
+
     tosca.relationships.SupportsImage:
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Image ]
diff --git a/xos/tosca/resources/VOLTTenant.py b/xos/tosca/resources/VOLTTenant.py
index f00b515..c9eceb4 100644
--- a/xos/tosca/resources/VOLTTenant.py
+++ b/xos/tosca/resources/VOLTTenant.py
@@ -7,14 +7,14 @@
 import pdb
 
 from core.models import User
-from cord.models import VOLTTenant, VOLTService, CordSubscriberRoot
+from cord.models import VOLTTenant, VOLTService, CordSubscriberRoot, VOLT_KIND
 
 from xosresource import XOSResource
 
 class XOSVOLTTenant(XOSResource):
     provides = "tosca.nodes.VOLTTenant"
     xos_model = VOLTTenant
-    copyin_props = ["service_specific_id", "vlan_id"]
+    copyin_props = ["service_specific_id", "s_tag", "c_tag"]
     name_field = None
 
     def get_xos_args(self, throw_exception=True):
@@ -32,10 +32,10 @@
 
     def get_existing_objs(self):
         args = self.get_xos_args(throw_exception=False)
-        provider_service = args.get("provider", None)
+        provider_service = args.get("provider_service", None)
         service_specific_id = args.get("service_specific_id", None)
         if (provider_service) and (service_specific_id):
-            return [ self.get_xos_object(provider_service=provider_service, service_specific_id=service_specific_id) ]
+            return [ self.get_xos_object(VOLTTenant, kind=VOLT_KIND, provider_service=provider_service, service_specific_id=service_specific_id) ]
         return []
 
     def postprocess(self, obj):
diff --git a/xos/tosca/resources/compute.py b/xos/tosca/resources/compute.py
index f01a401..37ba390 100644
--- a/xos/tosca/resources/compute.py
+++ b/xos/tosca/resources/compute.py
@@ -13,7 +13,7 @@
 from xosresource import XOSResource
 
 class XOSCompute(XOSResource):
-    provides = "tosca.nodes.Compute"
+    provides = ["tosca.nodes.Compute", "tosca.nodes.Compute.Container"]
     xos_model = Instance
 
     def select_compute_node(self, user, v, hostname=None):
@@ -60,11 +60,15 @@
             colocate_host = colocate_instances[0].node.name
             self.info("colocating on %s" % colocate_host)
 
+        imageName = self.get_requirement("tosca.relationships.UsesImage", throw_exception=False)
+        if imageName:
+            image = self.get_xos_object(Image, name=imageName)
+
         capabilities = nodetemplate.get_capabilities()
         for (k,v) in capabilities.items():
-            if (k=="host"):
+            if (k=="host") and (not host):
                 (compute_node, flavor) = self.select_compute_node(self.user, v, hostname=colocate_host)
-            elif (k=="os"):
+            elif (k=="os") and (not image):
                 image = self.select_image(self.user, v)
 
         if not compute_node:
@@ -80,6 +84,9 @@
         args["node"] = compute_node
         args["deployment"] = compute_node.site_deployment.deployment
 
+        if nodetemplate.type == "tosca.nodes.Compute.Container":
+            args["isolation"] = "container"
+
         return args
 
     def create(self, name = None, index = None):
@@ -120,3 +127,4 @@
         else:
             return super(XOSCompute,self).get_existing_objs()
 
+
diff --git a/xos/tosca/resources/image.py b/xos/tosca/resources/image.py
index bdc66b6..938c5cd 100644
--- a/xos/tosca/resources/image.py
+++ b/xos/tosca/resources/image.py
@@ -15,7 +15,7 @@
 class XOSImage(XOSResource):
     provides = "tosca.nodes.Image"
     xos_model = Image
-    copyin_props = ["disk_format", "container_format", "path"]
+    copyin_props = ["disk_format", "container_format", "path", "kind"]
 
     def get_xos_args(self):
         args = super(XOSImage, self).get_xos_args()
diff --git a/xos/tosca/resources/service.py b/xos/tosca/resources/service.py
index 884c6db..247be08 100644
--- a/xos/tosca/resources/service.py
+++ b/xos/tosca/resources/service.py
@@ -13,7 +13,7 @@
 class XOSService(XOSResource):
     provides = "tosca.nodes.Service"
     xos_model = Service
-    copyin_props = ["view_url", "icon_url", "kind", "enabled", "published", "public_key", "versionNumber"]
+    copyin_props = ["view_url", "icon_url", "kind", "enabled", "published", "public_key", "private_key_fn", "versionNumber"]
 
     def postprocess(self, obj):
         for provider_service_name in self.get_requirements("tosca.relationships.TenantOfService"):
diff --git a/xos/tosca/resources/vcpeservice.py b/xos/tosca/resources/vcpeservice.py
index 6cc7390..8df7231 100644
--- a/xos/tosca/resources/vcpeservice.py
+++ b/xos/tosca/resources/vcpeservice.py
@@ -12,5 +12,5 @@
 class XOSVcpeService(XOSService):
     provides = "tosca.nodes.VCPEService"
     xos_model = VCPEService
-    copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "versionNumber", "backend_network_label"]
+    copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "private_key_fn", "versionNumber", "backend_network_label"]
 
diff --git a/xos/tosca/resources/xosresource.py b/xos/tosca/resources/xosresource.py
index 3553ab1..9c4f479 100644
--- a/xos/tosca/resources/xosresource.py
+++ b/xos/tosca/resources/xosresource.py
@@ -77,9 +77,6 @@
     def get_existing_objs(self):
         return self.xos_model.objects.filter(**{self.name_field: self.nodetemplate.name})
 
-    def get_xos_args(self):
-        return {}
-
     def get_model_class_name(self):
         return self.xos_model.__name__
 
diff --git a/xos/tosca/samples/container.yaml b/xos/tosca/samples/container.yaml
new file mode 100644
index 0000000..bd69fbe
--- /dev/null
+++ b/xos/tosca/samples/container.yaml
@@ -0,0 +1,42 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Template for deploying a single server with predefined properties.
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    mysite:
+      type: tosca.nodes.Site
+
+    mysite_contest:
+      type: tosca.nodes.Slice
+      requirements:
+          - slice:
+                node: mysite
+                relationship: tosca.relationships.MemberOfSite
+
+    andybavier/docker-vcpe:
+      type: tosca.nodes.Image
+      properties:
+        kind: container
+        container_format: na
+        disk_format: na
+
+    my_container:
+      type: tosca.nodes.Compute.Container
+      capabilities:
+        # Host container properties
+        host:
+         properties:
+           num_cpus: 1
+           disk_size: 10 GB
+           mem_size: 4 MB
+      requirements:
+          - slice:
+                node: mysite_contest
+                relationship: tosca.relationships.MemberOfSlice
+          - image:
+                node: andybavier/docker-vcpe
+                relationship: tosca.relationships.UsesImage
diff --git a/xos/tosca/samples/cord.yaml b/xos/tosca/samples/cord.yaml
index 477be2f..567ced0 100644
--- a/xos/tosca/samples/cord.yaml
+++ b/xos/tosca/samples/cord.yaml
@@ -125,7 +125,8 @@
         type: tosca.nodes.VOLTTenant
         properties:
             service_specific_id: 1234
-            vlan_id: 4321
+            s_tag: 222
+            c_tag: 432
         requirements:
             - provider_service:
                 node: service_volt