Add container class, add segmentation_id to Port, allow ports to be attached to containers
diff --git a/xos/core/admin.py b/xos/core/admin.py
index a5b89be..dfa96b9 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1372,6 +1372,29 @@
     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
     #    obj.delete()
 
+class ContainerAdmin(XOSBaseAdmin):
+    fieldsets = [
+        ('Instance Details', {'fields': ['backend_status_text', 'slice', 'node'], 'classes': ['suit-tab suit-tab-general'], })
+    ]
+    readonly_fields = ('backend_status_text', )
+    list_display = ['backend_status_icon', 'id']
+    list_display_links = ('backend_status_icon', 'id', )
+
+    suit_form_tabs =(('general', 'Instance Details'),) # ('ports', 'Ports'))
+
+    #inlines = [TagInline, InstancePortInline]
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == 'slice':
+            kwargs['queryset'] = Slice.select_by_user(request.user)
+
+        return super(ContainerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
+
+    def queryset(self, request):
+        # admins can see all instances. Users can only see instances of
+        # the slices they belong to.
+        return Container.select_by_user(request.user)
+
 class UserCreationForm(forms.ModelForm):
     """A form for creating new users. Includes all the required
     fields, plus a repeated password."""
@@ -1727,7 +1750,7 @@
     readonly_fields = ('backend_status_icon', )
 
 class NetworkPortInline(XOSTabularInline):
-    fields = ['backend_status_icon', 'network', 'instance', 'ip', 'mac']
+    fields = ['backend_status_icon', 'network', 'instance', 'container', 'ip', 'mac']
     readonly_fields = ("backend_status_icon", "ip", "mac")
     model = Port
     selflink_fieldname = "instance"
@@ -2024,4 +2047,5 @@
     admin.site.register(TenantRoot, TenantRootAdmin)
     admin.site.register(TenantRootRole, TenantRootRoleAdmin)
     admin.site.register(TenantAttribute, TenantAttributeAdmin)
+    admin.site.register(Container, ContainerAdmin)
 
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index c380e9c..bc97dab 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -24,6 +24,7 @@
 from .node import Node
 from .slicetag import SliceTag
 from .instance import Instance
+from .container import Container
 from .reservation import ReservedResource
 from .reservation import Reservation
 from .network import Network, NetworkParameterType, NetworkParameter, Port, NetworkTemplate, Router, NetworkSlice, ControllerNetwork
diff --git a/xos/core/models/container.py b/xos/core/models/container.py
new file mode 100644
index 0000000..151b576
--- /dev/null
+++ b/xos/core/models/container.py
@@ -0,0 +1,83 @@
+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")
+
+    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/network.py b/xos/core/models/network.py
index 2bdf17f..48af5a6 100644
--- a/xos/core/models/network.py
+++ b/xos/core/models/network.py
@@ -2,7 +2,7 @@
 import socket
 import sys
 from django.db import models
-from core.models import PlCoreBase, Site, Slice, Instance, Controller
+from core.models import PlCoreBase, Site, Slice, Instance, Controller, Container
 from core.models import ControllerLinkManager,ControllerLinkDeletionManager
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes import generic
@@ -158,7 +158,7 @@
     router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum router id")
     subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum subnet id")
     subnet = models.CharField(max_length=32, blank=True)
-     
+
     class Meta:
         unique_together = ('network', 'controller')
         
@@ -211,9 +211,12 @@
 class Port(PlCoreBase):
     network = models.ForeignKey(Network,related_name='links')
     instance = models.ForeignKey(Instance, null=True, blank=True, related_name='ports')
+    container = models.ForeignKey(Container, 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")
     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')
diff --git a/xos/openstack_observer/steps/sync_ports.py b/xos/openstack_observer/steps/sync_ports.py
index 967bef9..7b20d29 100644
--- a/xos/openstack_observer/steps/sync_ports.py
+++ b/xos/openstack_observer/steps/sync_ports.py
@@ -144,9 +144,14 @@
 
         # For ports that were created by the user, find that ones
         # that don't have neutron ports, and create them.
-        for port in Port.objects.filter(port_id__isnull=True, instance__isnull=False):
-            #logger.info("XXX working on port %s" % port)
-            controller = port.instance.node.site_deployment.controller
+        for port in Port.objects.filter(Q(port_id__isnull=True), Q(instance__isnull=False) | Q(container__isnull=False)):
+            logger.info("XXX working on port %s" % port)
+            if port.instance:
+                controller = port.instance.node.site_deployment.controller
+                slice = port.instance.slice
+            else:
+                controller = port.container.node.site_deployment.controller
+                slice = port.container.slice
             if controller:
                 cn=port.network.controllernetworks.filter(controller=controller)
                 if not cn:
@@ -174,7 +179,7 @@
                     caller = port.network.owner.creator
                     auth = {'username': caller.email,
                             'password': caller.remote_password,
-                            'tenant': port.instance.slice.name} # port.network.owner.name}
+                            'tenant': slice.name}
                     client = OpenStackClient(controller=controller, **auth) # cacert=self.config.nova_ca_ssl_cert,
                     driver = OpenStackDriver(client=client)
 
@@ -183,6 +188,10 @@
                     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