add support for container_vm instance type in Service; create Scheduler object framework
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 49c534a..ba54a33 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -337,6 +337,106 @@
return None
return sorted(st, key=attrgetter('id'))[0]
+class Scheduler(object):
+ # XOS Scheduler Abstract Base Class
+ # Used to implement schedulers that pick which node to put instances on
+
+ def __init__(self, slice):
+ self.slice = slice
+
+ def pick(self):
+ # this method should return a tuple (node, parent)
+ # node is the node to instantiate on
+ # parent is for container_vm instances only, and is the VM that will
+ # hold the container
+
+ raise Exception("Abstract Base")
+
+class LeastLoadedNodeScheduler(Scheduler):
+ # This scheduler always return the node with the fewest number of instances.
+
+ def __init__(self, slice):
+ super(LeastLoadedNodeScheduler, self).__init__(slice)
+
+ def pick(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.instances.all().count())
+ return [nodes[0], None]
+
+class ContainerVmScheduler(Scheduler):
+ # This scheduler picks a VM in the slice with the fewest containers inside
+ # of it. If no VMs are suitable, then it creates a VM.
+
+ # this is a hack and should be replaced by something smarter...
+ LOOK_FOR_IMAGES=["ubuntu-vcpe4", # ONOS demo machine -- preferred vcpe image
+ "Ubuntu 14.04 LTS", # portal
+ "Ubuntu-14.04-LTS", # ONOS demo machine
+ "trusty-server-multi-nic", # CloudLab
+ ]
+
+ MAX_VM_PER_CONTAINER = 10
+
+ def __init__(self, slice):
+ super(ContainerVmScheduler, self).__init__(slice)
+
+ @property
+ def image(self):
+ from core.models import Image
+
+ 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 ContainerVM image (looked for %s)" % str(look_for_images))
+
+ def make_new_instance(self):
+ from core.models import Instance, Flavor
+
+ flavors = Flavor.objects.filter(name="m1.small")
+ if not flavors:
+ raise XOSConfigurationError("No m1.small flavor")
+
+ (node,parent) = LeastLoadedNodeScheduler(self.slice).pick()
+
+ instance = Instance(slice = self.slice,
+ node = node,
+ image = self.image,
+ creator = self.slice.creator,
+ deployment = node.site_deployment.deployment,
+ flavor = flavors[0],
+ isolation = "vm",
+ parent = parent)
+ instance.save()
+ # We rely on a special naming convention to identify the VMs that will
+ # hole containers.
+ instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
+ instance.save()
+ return instance
+
+ def pick(self):
+ from core.models import Instance, Flavor
+
+ for vm in self.slice.instances.filter(isolation="vm"):
+ avail_vms = []
+ if (vm.name.startswith("%s-outer-" % self.slice.name)):
+ container_count = Instance.objects.filter(parent=vm).count()
+ if (container_count < self.MAX_VM_PER_CONTAINER):
+ avail_vms.append( (vm, container_count) )
+ # sort by least containers-per-vm
+ avail_vms = sorted(avail_vms, key = lambda x: x[1])
+ print "XXX", avail_vms
+ if avail_vms:
+ instance = avail_vms[0][0]
+ return (instance.node, instance)
+
+ instance = self.make_new_instance()
+ return (instance.node, instance)
+
class TenantWithContainer(Tenant):
""" A tenant that manages a container """
@@ -357,7 +457,6 @@
self.cached_instance=None
self.orig_instance_id = self.get_initial_attribute("instance_id")
-
@property
def instance(self):
from core.models import Instance
@@ -426,7 +525,7 @@
if images:
return images[0]
- raise XOSProgrammingError("No VPCE image (looked for %s)" % str(self.look_for_images))
+ raise XOSProgrammingError("No VPCE image (looked for %s)" % str(look_for_images))
@creator.setter
def creator(self, value):
@@ -436,23 +535,11 @@
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
- # nodes the slice can instantiate on.
- nodes = sorted(nodes, key=lambda node: node.instances.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:
if slice.instances.all().count() > 0:
@@ -498,13 +585,12 @@
if not flavors:
raise XOSConfigurationError("No m1.small flavor")
- node =self.pick_node_for_instance()
slice = self.provider_service.slices.all()[0]
if slice.default_isolation == "container_vm":
- parent = self.pick_vm()
+ (node, parent) = ContainerVmScheduler(slice).pick()
else:
- parent = None
+ (node, parent) = LeastLoadedNodeScheduler(slice).pick()
instance = Instance(slice = slice,
node = node,