Merge pull request #59 from mpeuster/master
authorpeusterm <manuel.peuster@uni-paderborn.de>
Mon, 21 Mar 2016 07:48:31 +0000 (08:48 +0100)
committerpeusterm <manuel.peuster@uni-paderborn.de>
Mon, 21 Mar 2016 07:48:31 +0000 (08:48 +0100)
Resource model and integration

src/emuvim/dcemulator/node.py
src/emuvim/dcemulator/resourcemodel/__init__.py
src/emuvim/dcemulator/resourcemodel/upb/simple.py
src/emuvim/examples/resource_model_demo_topology.py
src/emuvim/test/test_resourcemodel_api.py

index 9ccdb7f..7746e9e 100755 (executable)
@@ -137,17 +137,37 @@ class Datacenter(object):
                 network.append({})
 
         # allocate in resource resource model and compute resource limits for new container
+        cpu_limit = mem_limit = disk_limit = -1
+        cpu_period = cpu_quota = None
         if self._resource_model is not None:
-            # TODO pass resource limits to new container (cf. Dockernet API) Issue #47
+            # call allocate in resource model to calculate resource limit for this container
             (cpu_limit, mem_limit, disk_limit) = alloc = self._resource_model.allocate(name, flavor_name)
             LOG.debug("Allocation result: %r" % str(alloc))
+            # check if we have a cpu_limit given by the used resource model
+            if cpu_limit > 0:
+                # calculate cpu period and quota for CFS
+                # (see: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
+                # TODO consider multi core machines etc! non trivial!
+                # Attention minimum cpu_quota is 1ms (micro)
+                cpu_period = 100000  # lets consider a fixed period of 100000 microseconds for now
+                cpu_quota = cpu_period * cpu_limit  # calculate the fraction of cpu time for this container
+                LOG.debug(
+                    "CPU limit: cpu_quota = cpu_period * cpu_limit = %f * %f = %f" % (cpu_period, cpu_limit, cpu_quota))
+                # ATTENTION >= 1000 to avoid a invalid argument system error ... no idea why
+                if cpu_quota < 1000:
+                    cpu_quota = 1000
+                    LOG.warning("Increased CPU quota for %d to avoid system error." % name)
+            # TODO add memory and disc limitations
         # create the container
         d = self.net.addDocker(
             "%s" % (name),
             dimage=image,
             dcmd=command,
             datacenter=self,
-            flavor_name=flavor_name)
+            flavor_name=flavor_name,
+            cpu_period=int(cpu_period) if cpu_limit > 0 else None,  # set cpu limits if needed
+            cpu_quota=int(cpu_quota) if cpu_limit > 0 else None,
+        )
         # connect all given networks
         for nw in network:
             # TODO we cannot use TCLink here (see: https://github.com/mpeuster/dockernet/issues/3)
@@ -192,6 +212,11 @@ class Datacenter(object):
         }
 
     def assignResourceModel(self, rm):
+        """
+        Assign a resource model to this DC.
+        :param rm: a BaseResourceModel object
+        :return:
+        """
         if self._resource_model is not None:
             raise Exception("There is already an resource model assigned to this DC.")
         self._resource_model = rm
index 52a35e5..3d05b96 100644 (file)
@@ -29,12 +29,25 @@ class ResourceModelRegistrar(object):
             raise Exception("There is already an resource model assigned to this DC.")
         self._resource_models[dc] = rm
         rm.registrar = self
+        rm.dcs.append(dc)
         LOG.info("Registrar: Added resource model: %r" % rm)
 
     @property
     def resource_models(self):
+        """
+        List of registered resource models
+        :return:
+        """
         return list(self._resource_models.itervalues())
 
+    @property
+    def num_dcs_with_rms(self):
+        """
+        Total number of data centers that are connected to a resource model
+        :return:
+        """
+        return sum([len(rm.dcs) for rm in list(self._resource_models.itervalues())])
+
 
 class ResourceFlavor(object):
     """
@@ -60,6 +73,7 @@ class BaseResourceModel(object):
         self._flavors = dict()
         self._initDefaultFlavors()
         self.registrar = None  # pointer to registrar
+        self.dcs = list()
         self.allocated_compute_instances = dict()
         LOG.info("Resource model %r initialized" % self)
 
index 503e35c..f8b8b94 100644 (file)
@@ -8,15 +8,65 @@ LOG = logging.getLogger("rm.upb.simple")
 LOG.setLevel(logging.DEBUG)
 
 
-class UpbSimpleCloudDcApproxRM(BaseResourceModel):
+class UpbSimpleCloudDcRM(BaseResourceModel):
     """
     This will be an example resource model that limits the overall
     resources that can be deployed per data center.
+    No over provisioning. Resources are fixed throughout entire container
+    lifetime.
     """
-    # TODO Implement resource model issue #12
 
     def __init__(self, max_cu=32, max_mu=1024):
-        self._max_cu = max_cu
-        self._max_mu = max_mu
+        """
+        Initialize model.
+        :param max_cu: Maximum number of compute units available in this DC.
+        :param max_mu: Maximum memory of entire dc.
+        :return:
+        """
+        self.dc_max_cu = max_cu
+        self.dc_max_mu = max_mu
+        self.dc_alloc_cu = 0
         super(self.__class__, self).__init__()
 
+    def allocate(self, name, flavor_name):
+        """
+        Calculate resources for container with given flavor.
+        :param name: Container name.
+        :param flavor_name: Flavor name.
+        :return:
+        """
+        # TODO Add memory model calculation (split in private methods for each tuple component)
+        # bookkeeping and flavor handling
+        if flavor_name not in self._flavors:
+            raise Exception("Flavor %r does not exist" % flavor_name)
+        fl = self._flavors.get(flavor_name)
+        fl_cu = fl.get("compute")
+        self.allocated_compute_instances[name] = flavor_name
+        # check for over provisioning
+        if self.dc_alloc_cu + fl_cu > self.dc_max_cu:
+            raise Exception("Not enough compute resources left.")
+        self.dc_alloc_cu += fl_cu
+        #
+        # calculate cpu limitation:
+        #
+        # get cpu time fraction for entire emulation
+        e_cpu = self.registrar.e_cpu
+        # calculate cpu time fraction of a single compute unit
+        cu = e_cpu / sum([rm.dc_max_cu for rm in list(self.registrar.resource_models)])
+        # calculate cpu time fraction for container with given flavor
+        c_ct = cu * fl_cu
+        return c_ct, -1.0, -1.0  # return 3tuple (cpu, memory, disk)
+
+    def free(self, name):
+        """
+        Free resources of given container.
+        :param name: Container name.
+        :return:
+        """
+        if name not in self.allocated_compute_instances:
+            return False
+        # bookkeeping
+        self.dc_alloc_cu -= self._flavors.get(self.allocated_compute_instances[name]).get("compute")
+        del self.allocated_compute_instances[name]
+        # we don't have to calculate anything special here in this simple model
+        return True
index e65a8ce..ae6ba5e 100644 (file)
@@ -5,10 +5,11 @@ A simple topology to test resource model support.
 import logging
 import time
 from mininet.log import setLogLevel
+from mininet.node import Controller
 from emuvim.dcemulator.net import DCNetwork
 from emuvim.api.zerorpc.compute import ZeroRpcApiEndpoint
 from emuvim.api.sonata import SonataDummyGatekeeperEndpoint
-from emuvim.dcemulator.resourcemodel.upb.simple import UpbSimpleCloudDcApproxRM
+from emuvim.dcemulator.resourcemodel.upb.simple import UpbSimpleCloudDcRM
 
 logging.basicConfig(level=logging.INFO)
 
@@ -16,7 +17,7 @@ logging.basicConfig(level=logging.INFO)
 def create_topology1():
     # create topology
     # use a maximum of 50% cpu time for containers added to data centers
-    net = DCNetwork(dc_emulation_max_cpu=0.5)
+    net = DCNetwork(dc_emulation_max_cpu=0.5, controller=Controller)
     # add some data centers and create a topology
     dc1 = net.addDatacenter("dc1")
     dc2 = net.addDatacenter("dc2")
@@ -25,8 +26,8 @@ def create_topology1():
     net.addLink(dc2, s1, delay="20ms")
 
     # create and assign resource models for each DC
-    rm1 = UpbSimpleCloudDcApproxRM(max_cu=10, max_mu=1024)
-    rm2 = UpbSimpleCloudDcApproxRM(max_cu=20)
+    rm1 = UpbSimpleCloudDcRM(max_cu=10, max_mu=1024)
+    rm2 = UpbSimpleCloudDcRM(max_cu=20)
     dc1.assignResourceModel(rm1)
     dc2.assignResourceModel(rm2)
 
index 35b4dd5..3412ec4 100644 (file)
@@ -1,6 +1,8 @@
 import time
 from emuvim.test.base import SimpleTestTopology
 from emuvim.dcemulator.resourcemodel import BaseResourceModel, ResourceFlavor
+from emuvim.dcemulator.resourcemodel.upb.simple import UpbSimpleCloudDcRM
+from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
 
 
 class testResourceModel(SimpleTestTopology):
@@ -49,3 +51,70 @@ class testResourceModel(SimpleTestTopology):
         assert(self.net.ping([self.h[0], self.h[1]]) <= 0.0)
         # stop Mininet network
         self.stopNet()
+
+
+class testUpbSimpleCloudDcRM(SimpleTestTopology):
+    """
+    Test the UpbSimpleCloudDc resource model.
+    """
+    def testAllocation(self):
+        # config
+        E_CPU = 1.0
+        MAX_CU = 100
+        # create dummy resource model environment
+        reg = ResourceModelRegistrar(dc_emulation_max_cpu=1.0)
+        rm = UpbSimpleCloudDcRM(max_cu=100, max_mu=100)
+        reg.register("test_dc", rm)
+
+        res = rm.allocate("c1", "tiny")  # calculate allocation
+        assert(res[0] == E_CPU / MAX_CU * 1)   # validate compute result
+        assert(res[1] < 0)   # validate memory result
+        assert(res[2] < 0)   # validate disk result
+
+        res = rm.allocate("c2", "small")  # calculate allocation
+        assert(res[0] == E_CPU / MAX_CU * 4)   # validate compute result
+        assert(res[1] < 0)   # validate memory result
+        assert(res[2] < 0)   # validate disk result
+
+        res = rm.allocate("c3", "medium")  # calculate allocation
+        assert(res[0] == E_CPU / MAX_CU * 8)   # validate compute result
+        assert(res[1] < 0)   # validate memory result
+        assert(res[2] < 0)   # validate disk result
+
+        res = rm.allocate("c4", "large")  # calculate allocation
+        assert(res[0] == E_CPU / MAX_CU * 16)   # validate compute result
+        assert(res[1] < 0)   # validate memory result
+        assert(res[2] < 0)   # validate disk result
+
+        res = rm.allocate("c5", "xlarge")  # calculate allocation
+        assert(res[0] == E_CPU / MAX_CU * 32)   # validate compute result
+        assert(res[1] < 0)   # validate memory result
+        assert(res[2] < 0)   # validate disk result
+
+        # test over provisioning exeption
+        exception = False
+        try:
+            rm.allocate("c6", "xlarge")  # calculate allocation
+            rm.allocate("c7", "xlarge")  # calculate allocation
+            rm.allocate("c8", "xlarge")  # calculate allocation
+            rm.allocate("c9", "xlarge")  # calculate allocation
+        except Exception as e:
+            assert("Not enough compute" in e.message)
+            exception = True
+        assert(exception)
+
+    def testFree(self):
+        # config
+        E_CPU = 1.0
+        MAX_CU = 100
+        # create dummy resource model environment
+        reg = ResourceModelRegistrar(dc_emulation_max_cpu=1.0)
+        rm = UpbSimpleCloudDcRM(max_cu=100, max_mu=100)
+        reg.register("test_dc", rm)
+        rm.allocate("c1", "tiny")  # calculate allocation
+        assert(rm.dc_alloc_cu == 1)
+        rm.free("c1")
+        assert(rm.dc_alloc_cu == 0)
+
+
+