Merge branch 'master' of github.com:open-cloud/xos
diff --git a/xos/cord/admin.py b/xos/cord/admin.py
index 6137212..9e7946e 100644
--- a/xos/cord/admin.py
+++ b/xos/cord/admin.py
@@ -1,6 +1,7 @@
 from django.contrib import admin
 
 from cord.models import *
+from core.models import Container
 from django import forms
 from django.utils.safestring import mark_safe
 from django.contrib.auth.admin import UserAdmin
@@ -159,6 +160,8 @@
     bbs_account = forms.CharField(required=False)
     creator = forms.ModelChoiceField(queryset=User.objects.all())
     instance = forms.ModelChoiceField(queryset=Instance.objects.all(),required=False)
+    container = forms.ModelChoiceField(queryset=Container.objects.all(),required=False)
+    use_cobm = forms.BooleanField(required=False)
     last_ansible_hash = forms.CharField(required=False)
 
     def __init__(self,*args,**kwargs):

@@ -170,6 +173,8 @@
             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['container'].initial = self.instance.container

+            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

@@ -177,11 +182,14 @@
             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.container = self.cleaned_data.get("container")

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

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

 

     class Meta:

@@ -191,7 +199,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', 'instance', 'last_ansible_hash'],
+                                     'bbs_account', 'creator', 'use_cobm', 'instance', 'container', '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 67ffdc7..4227f52 100644
--- a/xos/cord/models.py
+++ b/xos/cord/models.py
@@ -333,6 +333,7 @@
             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):
@@ -470,6 +471,7 @@
                        "hpc_client_ip", "hpc_client_mac")
 
     default_attributes = {"instance_id": None,
+                          "container_id": None,
                           "users": [],
                           "bbs_account": None,
                           "last_ansible_hash": None}
@@ -534,11 +536,15 @@
 
     @property
     def addresses(self):
-        if not self.instance:
+        if self.instance:
+            ports = self.instance.ports.all()
+        elif self.container:
+            ports = self.container.ports.all()
+        else:
             return {}
 
         addresses = {}
-        for ns in self.instance.ports.all():
+        for ns in ports:
             if "lan" in ns.network.name.lower():
                 addresses["lan"] = (ns.ip, ns.mac)
             elif "wan" in ns.network.name.lower():
diff --git a/xos/core/admin.py b/xos/core/admin.py
index a0cabd1..317c6a5 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1384,7 +1384,7 @@
 
 class ContainerAdmin(XOSBaseAdmin):
     fieldsets = [
-        ('Container Details', {'fields': ['backend_status_text', 'slice', 'node', 'docker_image', 'no_sync'], 'classes': ['suit-tab suit-tab-general'], })
+        ('Container Details', {'fields': ['backend_status_text', 'slice', 'node', 'docker_image', 'volumes', 'no_sync'], 'classes': ['suit-tab suit-tab-general'], })
     ]
     readonly_fields = ('backend_status_text', )
     list_display = ['backend_status_icon', 'id']
diff --git a/xos/core/models/container.py b/xos/core/models/container.py
index 151b576..ae1d198 100644
--- a/xos/core/models/container.py
+++ b/xos/core/models/container.py
@@ -28,6 +28,7 @@
     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)
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 950ce02..c263bba 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -353,6 +353,9 @@
         super(TenantWithContainer, self).__init__(*args, **kwargs)
         self.cached_instance=None
         self.orig_instance_id = self.get_initial_attribute("instance_id")
+        self.cached_container=None
+        self.orig_container_id = self.get_initial_attribute("container_id")
+
 
     @property
     def instance(self):
@@ -379,6 +382,30 @@
         self.set_attribute("instance_id", value)
 
     @property
+    def container(self):
+        from core.models import Container
+        if getattr(self, "cached_container", None):
+            return self.cached_container
+        container_id=self.get_attribute("container_id")
+        if not container_id:
+            return None
+        containers=Container.objects.filter(id=container_id)
+        if not containers:
+            return None
+        container=containers[0]
+        container.caller = self.creator
+        self.cached_container = container
+        return container
+
+    @container.setter
+    def container(self, value):
+        if value:
+            value = value.id
+        if (value != self.get_attribute("container_id", None)):
+            self.cached_container=None
+        self.set_attribute("container_id", value)
+
+    @property
     def creator(self):
         from core.models import User
         if getattr(self, "cached_creator", None):
@@ -413,7 +440,23 @@
 
         raise XOSProgrammingError("No VPCE image (looked for %s)" % str(self.LOOK_FOR_IMAGES))
 
-    def pick_node(self):
+    @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)
+
+    @creator.setter
+    def creator(self, value):
+        if value:
+            value = value.id
+        if (value != self.get_attribute("creator_id", None)):
+            self.cached_creator=None
+        self.set_attribute("creator_id", value)
+
+    def pick_node_for_instance(self):
         from core.models import Node
         nodes = list(Node.objects.all())
         # TODO: logic to filter nodes by which nodes are up, and which
@@ -421,7 +464,15 @@
         nodes = sorted(nodes, key=lambda node: node.instances.all().count())
         return nodes[0]
 
-    def manage_container(self):
+    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 manage_container_in_instance(self):
         from core.models import Instance, Flavor
 
         if self.deleted:
@@ -439,7 +490,7 @@
             if not flavors:
                 raise XOSConfigurationError("No m1.small flavor")
 
-            node =self.pick_node()
+            node =self.pick_node_for_instance()
             instance = Instance(slice = self.provider_service.slices.all()[0],
                             node = node,
                             image = self.image,
@@ -455,11 +506,83 @@
                 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:
             # print "XXX cleanup instance", self.instance
             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/openstack_observer/steps/sync_container.py b/xos/openstack_observer/steps/sync_container.py
index de4a2ce..0f5dcc4 100644
--- a/xos/openstack_observer/steps/sync_container.py
+++ b/xos/openstack_observer/steps/sync_container.py
@@ -70,6 +70,9 @@
             pd["snoop_instance_id"] = instance_port.instance.instance_id
 
             ports.append(pd)
+
+            i = i + 1
+
         return ports
 
     def get_extra_attributes(self, o):
@@ -81,6 +84,7 @@
         fields["docker_image"] = o.docker_image
         fields["username"] = "root"
         fields["ports"] = self.get_ports(o)
+        fields["volumes"] = [x.strip() for x in o.volumes.split(",")]
         return fields
 
     def sync_fields(self, o, fields):
diff --git a/xos/openstack_observer/steps/sync_container.yaml b/xos/openstack_observer/steps/sync_container.yaml
index a707d0b..e005e58 100644
--- a/xos/openstack_observer/steps/sync_container.yaml
+++ b/xos/openstack_observer/steps/sync_container.yaml
@@ -16,6 +16,10 @@
          snoop_instance_mac: {{ port.snoop_instance_mac }}
          snoop_instance_id: {{ port.snoop_instance_id }}
     {% endfor %}
+    volumes:
+    {% for volume in volumes %}
+       - {{ volume }}
+    {% endfor %}
 
   tasks:
 
diff --git a/xos/openstack_observer/templates/start-container.sh.j2 b/xos/openstack_observer/templates/start-container.sh.j2
index 5656992..dc3b7cb 100644
--- a/xos/openstack_observer/templates/start-container.sh.j2
+++ b/xos/openstack_observer/templates/start-container.sh.j2
@@ -6,11 +6,17 @@
 CONTAINER={{ container_name }}
 IMAGE={{ docker_image }}
 
+{% for volume in volumes %}
+DEST_DIR=/var/container_volumes/$CONTAINER/{{ volume }}
+mkdir -p $DEST_DIR
+VOLUME_ARGS="$VOLUME_ARGS -v $DEST_DIR:{{ volume }}"
+{% endfor %}
+
 docker inspect $CONTAINER > /dev/null 2>&1
 if [ "$?" == 1 ]
 then
     docker pull $IMAGE
-    docker run -d --name=$CONTAINER --privileged=true --net=none $IMAGE
+    docker run -d --name=$CONTAINER --privileged=true --net=none $VOLUME_ARGS $IMAGE
 else
     docker start $CONTAINER
 fi