Fixed missing license headers
[osm/vim-emu.git] / src / emuvim / dcemulator / resourcemodel / upb / simple.py
1 """
2 Copyright (c) 2015 SONATA-NFV and Paderborn University
3 ALL RIGHTS RESERVED.
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 Neither the name of the SONATA-NFV, Paderborn University
18 nor the names of its contributors may be used to endorse or promote
19 products derived from this software without specific prior written
20 permission.
21
22 This work has been performed in the framework of the SONATA project,
23 funded by the European Commission under Grant number 671517 through
24 the Horizon 2020 and 5G-PPP programmes. The authors would like to
25 acknowledge the contributions of their colleagues of the SONATA
26 partner consortium (www.sonata-nfv.eu).
27 """
28 """
29 Playground for resource models created by University of Paderborn.
30 """
31 import time
32 import json
33 import logging
34 from emuvim.dcemulator.resourcemodel import BaseResourceModel, NotEnoughResourcesAvailable
35
36 LOG = logging.getLogger("rm.upb.simple")
37 LOG.setLevel(logging.DEBUG)
38
39 CPU_PERIOD = 1000000
40
41
42 class UpbSimpleCloudDcRM(BaseResourceModel):
43 """
44 This will be an example resource model that limits the overall
45 resources that can be deployed per data center.
46 No over provisioning. Resources are fixed throughout entire container
47 lifetime.
48 """
49
50 def __init__(self, max_cu=32, max_mu=1024,
51 deactivate_cpu_limit=False,
52 deactivate_mem_limit=False):
53 """
54 Initialize model.
55 :param max_cu: Maximum number of compute units available in this DC.
56 :param max_mu: Maximum memory of entire dc.
57 :return:
58 """
59 self.dc_max_cu = max_cu
60 self.dc_max_mu = max_mu
61 self.dc_alloc_cu = 0
62 self.dc_alloc_mu = 0
63 self.deactivate_cpu_limit = deactivate_cpu_limit
64 self.deactivate_mem_limit = deactivate_mem_limit
65 self.single_cu = 0
66 self.single_mu = 0
67 self.cpu_op_factor = 1.0 # over provisioning factor
68 self.mem_op_factor = 1.0
69 self.raise_no_cpu_resources_left = True
70 self.raise_no_mem_resources_left = True
71 super(UpbSimpleCloudDcRM, self).__init__()
72
73 def allocate(self, d):
74 """
75 Allocate resources for the given container.
76 Defined by d.flavor_name
77 :param d: container
78 :return:
79 """
80 self._allocated_compute_instances[d.name] = d
81 if not self.deactivate_cpu_limit:
82 self._allocate_cpu(d)
83 if not self.deactivate_mem_limit:
84 self._allocate_mem(d)
85 self._apply_limits()
86
87 def _allocate_cpu(self, d):
88 """
89 Actually allocate (bookkeeping)
90 :param d: container
91 :return:
92 """
93 fl_cu = self._get_flavor(d).get("compute")
94 # check for over provisioning
95 if self.dc_alloc_cu + fl_cu > self.dc_max_cu and self.raise_no_cpu_resources_left:
96 raise NotEnoughResourcesAvailable("Not enough compute resources left.")
97 self.dc_alloc_cu += fl_cu
98
99 def _allocate_mem(self, d):
100 """
101 Actually allocate (bookkeeping)
102 :param d: container
103 :return:
104 """
105 fl_mu = self._get_flavor(d).get("memory")
106 # check for over provisioning
107 if self.dc_alloc_mu + fl_mu > self.dc_max_mu and self.raise_no_mem_resources_left:
108 raise NotEnoughResourcesAvailable("Not enough memory resources left.")
109 self.dc_alloc_mu += fl_mu
110
111 def free(self, d):
112 """
113 Free resources allocated to the given container.
114 :param d: container
115 :return:
116 """
117 del self._allocated_compute_instances[d.name]
118 if not self.deactivate_cpu_limit:
119 self._free_cpu(d)
120 if not self.deactivate_mem_limit:
121 self._free_mem(d)
122 self._apply_limits()
123
124 def _free_cpu(self, d):
125 """
126 Free resources.
127 :param d: container
128 :return:
129 """
130 self.dc_alloc_cu -= self._get_flavor(d).get("compute")
131
132 def _free_mem(self, d):
133 """
134 Free resources.
135 :param d: container
136 :return:
137 """
138 self.dc_alloc_mu -= self._get_flavor(d).get("memory")
139
140 def _apply_limits(self):
141 """
142 Recalculate real resource limits for all allocated containers and apply them
143 to their cgroups.
144 We have to recalculate for all containers to allow e.g. over provisioning models.
145 :return:
146 """
147 for d in self._allocated_compute_instances.itervalues():
148 if not self.deactivate_cpu_limit:
149 self._apply_cpu_limits(d)
150 if not self.deactivate_mem_limit:
151 self._apply_mem_limits(d)
152
153 def _apply_cpu_limits(self, d):
154 """
155 Calculate real CPU limit (CFS bandwidth) and apply.
156 :param d: container
157 :return:
158 """
159 number_cu = self._get_flavor(d).get("compute")
160 # calculate cpu time fraction of a single compute unit
161 self.single_cu = self._compute_single_cu()
162 # calculate cpu time fraction for container with given flavor
163 cpu_time_percentage = self.single_cu * number_cu
164 # calculate input values for CFS scheduler bandwidth limitation
165 cpu_period, cpu_quota = self._calculate_cpu_cfs_values(cpu_time_percentage)
166 # apply limits to container if changed
167 if d.resources['cpu_period'] != cpu_period or d.resources['cpu_quota'] != cpu_quota:
168 LOG.debug("Setting CPU limit for %r: cpu_quota = cpu_period * limit = %f * %f = %f (op_factor=%f)" % (
169 d.name, cpu_period, cpu_time_percentage, cpu_quota, self.cpu_op_factor))
170 d.updateCpuLimit(cpu_period=int(cpu_period), cpu_quota=int(cpu_quota))
171
172 def _compute_single_cu(self):
173 """
174 Calculate percentage of CPU time of a singe CU unit.
175 :return:
176 """
177 # get cpu time fraction for entire emulation
178 e_cpu = self.registrar.e_cpu
179 # calculate
180 return float(e_cpu) / sum([rm.dc_max_cu for rm in list(self.registrar.resource_models)])
181
182 def _calculate_cpu_cfs_values(self, cpu_time_percentage):
183 """
184 Calculate cpu period and quota for CFS
185 :param cpu_time_percentage: percentage of overall CPU to be used
186 :return: cpu_period, cpu_quota
187 """
188 # (see: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
189 # Attention minimum cpu_quota is 1ms (micro)
190 cpu_period = CPU_PERIOD # lets consider a fixed period of 1000000 microseconds for now
191 cpu_quota = cpu_period * cpu_time_percentage # calculate the fraction of cpu time for this container
192 # ATTENTION >= 1000 to avoid a invalid argument system error ... no idea why
193 if cpu_quota < 1000:
194 cpu_quota = 1000
195 LOG.warning("Increased CPU quota to avoid system error.")
196 return cpu_period, cpu_quota
197
198 def _apply_mem_limits(self, d):
199 """
200 Calculate real mem limit and apply.
201 :param d: container
202 :return:
203 """
204 number_mu = self._get_flavor(d).get("memory")
205 # get memory amount for entire emulation
206 e_mem = self.registrar.e_mem
207 # calculate amount of memory for a single mu
208 self.single_mu = float(e_mem) / sum([rm.dc_max_mu for rm in list(self.registrar.resource_models)])
209 # calculate mem for given flavor
210 mem_limit = self.single_mu * number_mu
211 mem_limit = self._calculate_mem_limit_value(mem_limit)
212 # apply to container if changed
213 if d.resources['mem_limit'] != mem_limit:
214 LOG.debug("Setting MEM limit for %r: mem_limit = %f MB (op_factor=%f)" %
215 (d.name, mem_limit/1024/1024, self.mem_op_factor))
216 d.updateMemoryLimit(mem_limit=mem_limit)
217
218 def _calculate_mem_limit_value(self, mem_limit):
219 """
220 Calculate actual mem limit as input for cgroup API
221 :param mem_limit: abstract mem limit
222 :return: concrete mem limit
223 """
224 # ATTENTION minimum mem_limit per container is 4MB
225 if mem_limit < 4:
226 mem_limit = 4
227 LOG.warning("Increased MEM limit because it was less than 4.0 MB.")
228 # to byte!
229 return int(mem_limit*1024*1024)
230
231 def get_state_dict(self):
232 """
233 Return the state of the resource model as simple dict.
234 Helper method for logging functionality.
235 :return:
236 """
237 # collect info about all allocated instances
238 allocation_state = dict()
239 for k, d in self._allocated_compute_instances.iteritems():
240 s = dict()
241 s["cpu_period"] = d.cpu_period
242 s["cpu_quota"] = d.cpu_quota
243 s["cpu_shares"] = d.cpu_shares
244 s["mem_limit"] = d.mem_limit
245 s["memswap_limit"] = d.memswap_limit
246 allocation_state[k] = s
247 # final result
248 r = dict()
249 r["e_cpu"] = self.registrar.e_cpu
250 r["e_mem"] = self.registrar.e_mem
251 r["dc_max_cu"] = self.dc_max_cu
252 r["dc_max_mu"] = self.dc_max_mu
253 r["dc_alloc_cu"] = self.dc_alloc_cu
254 r["dc_alloc_mu"] = self.dc_alloc_mu
255 r["single_cu_percentage"] = self.single_cu
256 r["single_mu_percentage"] = self.single_mu
257 r["cpu_op_factor"] = self.cpu_op_factor
258 r["mem_op_factor"] = self.mem_op_factor
259 r["allocation_state"] = allocation_state
260 return r
261
262 def _get_flavor(self, d):
263 """
264 Get flavor assigned to given container.
265 Identified by d.flavor_name.
266 :param d: container
267 :return:
268 """
269 if d.flavor_name not in self._flavors:
270 raise Exception("Flavor %r does not exist" % d.flavor_name)
271 return self._flavors.get(d.flavor_name)
272
273 def _write_log(self, d, path, action):
274 """
275 Helper to log RM info for experiments.
276 :param d: container
277 :param path: log path
278 :param action: allocate or free
279 :return:
280 """
281 if path is None:
282 return
283 # we have a path: write out RM info
284 l = dict()
285 l["t"] = time.time()
286 l["container_state"] = d.getStatus()
287 l["action"] = action
288 l["rm_state"] = self.get_state_dict()
289 # append to logfile
290 with open(path, "a") as f:
291 f.write("%s\n" % json.dumps(l))
292
293
294 class UpbOverprovisioningCloudDcRM(UpbSimpleCloudDcRM):
295 """
296 This will be an example resource model that limits the overall
297 resources that can be deployed per data center.
298 Allows over provisioning. Might result in reducing resources of single
299 containers whenever a data-center is over provisioned.
300 """
301 # TODO add parts for memory
302 def __init__(self, *args, **kvargs):
303 super(UpbOverprovisioningCloudDcRM, self).__init__(*args, **kvargs)
304 self.raise_no_cpu_resources_left = False
305
306 def _compute_single_cu(self):
307 """
308 Calculate percentage of CPU time of a singe CU unit.
309 Take scale-down facte for over provisioning into account.
310 :return:
311 """
312 # get cpu time fraction for entire emulation
313 e_cpu = self.registrar.e_cpu
314 # calculate over provisioning scale factor
315 self.cpu_op_factor = float(self.dc_max_cu) / (max(self.dc_max_cu, self.dc_alloc_cu))
316 # calculate
317 return float(e_cpu) / sum([rm.dc_max_cu for rm in list(self.registrar.resource_models)]) * self.cpu_op_factor
318
319
320 class UpbDummyRM(UpbSimpleCloudDcRM):
321 """
322 No limits. But log allocations.
323 """
324 def __init__(self, *args, **kvargs):
325 super(UpbDummyRM, self).__init__(*args, **kvargs)
326 self.raise_no_cpu_resources_left = False
327
328 def _apply_limits(self):
329 # do nothing here
330 pass
331