add ability for services to scale
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 19bcda9..8a10f37 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -29,6 +29,79 @@
def __unicode__(self): return u'%s' % (self.name)
+ def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
+ """
+ Get a list of nodes that can be used to scale up a slice.
+
+ slice - slice to scale up
+ max_per_node - maximum numbers of slivers that 'slice' can have on a single node
+ exclusive_slices - list of slices that must have no nodes in common with 'slice'.
+ """
+
+ from core.models import Node, Sliver # late import to get around order-of-imports constraint in __init__.py
+
+ nodes = list(Node.objects.all())
+
+ conflicting_slivers = Sliver.objects.filter(slice__in = exclusive_slices)
+ conflicting_nodes = Node.objects.filter(slivers__in = conflicting_slivers)
+
+ nodes = [x for x in nodes if x not in conflicting_nodes]
+
+ # If max_per_node is set, then limit the number of slivers this slice
+ # can have on a single node.
+ if max_per_node:
+ acceptable_nodes = []
+ for node in nodes:
+ existing_count = node.slivers.filter(slice=slice).count()
+ if existing_count < max_per_node:
+ acceptable_nodes.append(node)
+ nodes = acceptable_nodes
+
+ return nodes
+
+ def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
+ # Pick the best node to scale up a slice.
+
+ nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
+ nodes = sorted(nodes, key=lambda node: node.slivers.all().count())
+ if not nodes:
+ return None
+ return nodes[0]
+
+ def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
+ from core.models import Sliver # late import to get around order-of-imports constraint in __init__.py
+
+ slices = [x for x in self.slices.all() if slice_hint in x.name]
+ for slice in slices:
+ while slice.slivers.all().count() > scale:
+ s = slice.slivers.all()[0]
+ # print "drop sliver", s
+ s.delete()
+
+ while slice.slivers.all().count() < scale:
+ node = self.pick_node(slice, max_per_node, exclusive_slices)
+ if not node:
+ # no more available nodes
+ break
+
+ image = slice.default_image
+ if not image:
+ raise XOSConfigurationError("No default_image for slice %s" % slice.name)
+
+ flavor = slice.default_flavor
+ if not flavor:
+ raise XOSConfigurationError("No default_flavor for slice %s" % slice.name)
+
+ s = Sliver(slice=slice,
+ node=node,
+ creator=slice.creator,
+ image=image,
+ flavor=flavor,
+ deployment=node.site_deployment.deployment)
+ s.save()
+
+ # print "add sliver", s
+
class ServiceAttribute(PlCoreBase):
name = models.SlugField(help_text="Attribute Name", max_length=128)
value = StrippedCharField(help_text="Attribute Value", max_length=1024)