Re-wrote resource model API and UPB simple resource model. Due to an update of Docker...
[osm/vim-emu.git] / src / emuvim / dcemulator / resourcemodel / upb / simple.py
1 """
2 Playground for resource models created by University of Paderborn.
3 """
4 import logging
5 from emuvim.dcemulator.resourcemodel import BaseResourceModel
6
7 LOG = logging.getLogger("rm.upb.simple")
8 LOG.setLevel(logging.DEBUG)
9
10
11 class UpbSimpleCloudDcRM(BaseResourceModel):
12 """
13 This will be an example resource model that limits the overall
14 resources that can be deployed per data center.
15 No over provisioning. Resources are fixed throughout entire container
16 lifetime.
17 """
18
19 def __init__(self, max_cu=32, max_mu=1024,
20 deactivate_cpu_limit=False,
21 deactivate_mem_limit=False):
22 """
23 Initialize model.
24 :param max_cu: Maximum number of compute units available in this DC.
25 :param max_mu: Maximum memory of entire dc.
26 :return:
27 """
28 self.dc_max_cu = max_cu
29 self.dc_max_mu = max_mu
30 self.dc_alloc_cu = 0
31 self.dc_alloc_mu = 0
32 self.deactivate_cpu_limit = deactivate_cpu_limit
33 self.deactivate_mem_limit = deactivate_mem_limit
34 super(self.__class__, self).__init__()
35
36 def allocate(self, d):
37 """
38 Allocate resources for the given container.
39 Defined by d.flavor_name
40 :param d: container
41 :return:
42 """
43 self._allocated_compute_instances[d.name] = d
44 if not self.deactivate_cpu_limit:
45 self._allocate_cpu(d)
46 if not self.deactivate_mem_limit:
47 self._allocate_mem(d)
48 self._apply_limits()
49
50 def _allocate_cpu(self, d):
51 """
52 Actually allocate (bookkeeping)
53 :param d: container
54 :return:
55 """
56 fl_cu = self._get_flavor(d).get("compute")
57 # check for over provisioning
58 if self.dc_alloc_cu + fl_cu > self.dc_max_cu:
59 raise Exception("Not enough compute resources left.")
60 self.dc_alloc_cu += fl_cu
61
62 def _allocate_mem(self, d):
63 """
64 Actually allocate (bookkeeping)
65 :param d: container
66 :return:
67 """
68 fl_mu = self._get_flavor(d).get("memory")
69 # check for over provisioning
70 if self.dc_alloc_mu + fl_mu > self.dc_max_mu:
71 raise Exception("Not enough memory resources left.")
72 self.dc_alloc_mu += fl_mu
73
74 def free(self, d):
75 """
76 Free resources allocated to the given container.
77 :param d: container
78 :return:
79 """
80 del self._allocated_compute_instances[d.name]
81 if not self.deactivate_cpu_limit:
82 self._free_cpu(d)
83 if not self.deactivate_mem_limit:
84 self._free_mem(d)
85 self._apply_limits()
86
87 def _free_cpu(self, d):
88 """
89 Free resources.
90 :param d: container
91 :return:
92 """
93 self.dc_alloc_cu -= self._get_flavor(d).get("compute")
94
95 def _free_mem(self, d):
96 """
97 Free resources.
98 :param d: container
99 :return:
100 """
101 self.dc_alloc_mu -= self._get_flavor(d).get("memory")
102
103 def _apply_limits(self):
104 """
105 Recalculate real resource limits for all allocated containers and apply them
106 to their cgroups.
107 We have to recalculate for all to allow e.g. overprovisioning models.
108 :return:
109 """
110 for d in self._allocated_compute_instances.itervalues():
111 if not self.deactivate_cpu_limit:
112 self._apply_cpu_limits(d)
113 if not self.deactivate_mem_limit:
114 self._apply_mem_limits(d)
115
116 def _apply_cpu_limits(self, d):
117 """
118 Calculate real CPU limit (CFS bandwidth) and apply.
119 :param d: container
120 :return:
121 """
122 number_cu = self._get_flavor(d).get("compute")
123 # get cpu time fraction for entire emulation
124 e_cpu = self.registrar.e_cpu
125 # calculate cpu time fraction of a single compute unit
126 single_cu = float(e_cpu) / sum([rm.dc_max_cu for rm in list(self.registrar.resource_models)])
127 # calculate cpu time fraction for container with given flavor
128 cpu_time_percentage = single_cu * number_cu
129 # calculate cpu period and quota for CFS
130 # (see: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
131 # Attention minimum cpu_quota is 1ms (micro)
132 cpu_period = 1000000 # lets consider a fixed period of 1000000 microseconds for now
133 cpu_quota = cpu_period * cpu_time_percentage # calculate the fraction of cpu time for this container
134 # ATTENTION >= 1000 to avoid a invalid argument system error ... no idea why
135 if cpu_quota < 1000:
136 cpu_quota = 1000
137 LOG.warning("Increased CPU quota for %r to avoid system error." % d.name)
138 # apply to container if changed
139 if d.cpu_period != cpu_period or d.cpu_quota != cpu_quota:
140 LOG.debug("Setting CPU limit for %r: cpu_quota = cpu_period * limit = %f * %f = %f" % (
141 d.name, cpu_period, cpu_time_percentage, cpu_quota))
142 d.updateCpuLimit(cpu_period=int(cpu_period), cpu_quota=int(cpu_quota))
143
144 def _apply_mem_limits(self, d):
145 """
146 Calculate real mem limit and apply.
147 :param d: container
148 :return:
149 """
150 number_mu = self._get_flavor(d).get("memory")
151 # get memory amount for entire emulation
152 e_mem = self.registrar.e_mem
153 # calculate amount of memory for a single mu
154 single_mu = float(e_mem) / sum([rm.dc_max_mu for rm in list(self.registrar.resource_models)])
155 # calculate mem for given flavor
156 mem_limit = single_mu * number_mu
157 # ATTENTION minimum mem_limit per container is 4MB
158 if mem_limit < 4:
159 mem_limit = 4
160 LOG.warning("Increased MEM limit for %r because it was less than 4.0 MB." % name)
161 # to byte!
162 mem_limit = int(mem_limit*1024*1024)
163 # apply to container if changed
164 if d.mem_limit != mem_limit:
165 LOG.debug("Setting MEM limit for %r: mem_limit = %f MB" % (d.name, mem_limit/1024/1024))
166 d.updateMemoryLimit(mem_limit=mem_limit)
167
168 def get_state_dict(self):
169 """
170 Return the state of the resource model as simple dict.
171 Helper method for logging functionality.
172 :return:
173 """
174 # TODO update
175 r = dict()
176 r["e_cpu"] = self.registrar.e_cpu
177 r["e_mem"] = self.registrar.e_mem
178 r["dc_max_cu"] = self.dc_max_cu
179 r["dc_max_mu"] = self.dc_max_mu
180 r["dc_alloc_cu"] = self.dc_alloc_cu
181 r["dc_alloc_mu"] = self.dc_alloc_mu
182 r["cu_cpu_percentage"] = -1
183 r["mu_mem_percentage"] = -1
184 r["allocated_compute_instances"] = None #self._allocated_compute_instances
185 return r
186
187 def _get_flavor(self, d):
188 """
189 Get flavor assigned to given container.
190 Identified by d.flavor_name.
191 :param d: container
192 :return:
193 """
194 if d.flavor_name not in self._flavors:
195 raise Exception("Flavor %r does not exist" % d.flavor_name)
196 return self._flavors.get(d.flavor_name)