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