2 Copyright (c) 2015 SONATA-NFV and Paderborn University
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
9 http://www.apache.org/licenses/LICENSE-2.0
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.
17 Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18 nor the names of its contributors may be used to endorse or promote
19 products derived from this software without specific prior written
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).
29 Playground for resource models created by University of Paderborn.
34 from emuvim
.dcemulator
.resourcemodel
import BaseResourceModel
, NotEnoughResourcesAvailable
36 LOG
= logging
.getLogger("rm.upb.simple")
37 LOG
.setLevel(logging
.DEBUG
)
42 class UpbSimpleCloudDcRM(BaseResourceModel
):
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
50 def __init__(self
, max_cu
=32, max_mu
=1024,
51 deactivate_cpu_limit
=False,
52 deactivate_mem_limit
=False):
55 :param max_cu: Maximum number of compute units available in this DC.
56 :param max_mu: Maximum memory of entire dc.
59 self
.dc_max_cu
= max_cu
60 self
.dc_max_mu
= max_mu
63 self
.deactivate_cpu_limit
= deactivate_cpu_limit
64 self
.deactivate_mem_limit
= deactivate_mem_limit
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
__()
73 def allocate(self
, d
):
75 Allocate resources for the given container.
76 Defined by d.flavor_name
80 self
._allocated
_compute
_instances
[d
.name
] = d
81 if not self
.deactivate_cpu_limit
:
83 if not self
.deactivate_mem_limit
:
87 def _allocate_cpu(self
, d
):
89 Actually allocate (bookkeeping)
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
99 def _allocate_mem(self
, d
):
101 Actually allocate (bookkeeping)
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
113 Free resources allocated to the given container.
117 del self
._allocated
_compute
_instances
[d
.name
]
118 if not self
.deactivate_cpu_limit
:
120 if not self
.deactivate_mem_limit
:
124 def _free_cpu(self
, d
):
130 self
.dc_alloc_cu
-= self
._get
_flavor
(d
).get("compute")
132 def _free_mem(self
, d
):
138 self
.dc_alloc_mu
-= self
._get
_flavor
(d
).get("memory")
140 def _apply_limits(self
):
142 Recalculate real resource limits for all allocated containers and apply them
144 We have to recalculate for all containers to allow e.g. over provisioning models.
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
)
153 def _apply_cpu_limits(self
, d
):
155 Calculate real CPU limit (CFS bandwidth) and apply.
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
.cpu_period
!= cpu_period
or d
.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
))
172 def _compute_single_cu(self
):
174 Calculate percentage of CPU time of a singe CU unit.
177 # get cpu time fraction for entire emulation
178 e_cpu
= self
.registrar
.e_cpu
180 return float(e_cpu
) / sum([rm
.dc_max_cu
for rm
in list(self
.registrar
.resource_models
)])
182 def _calculate_cpu_cfs_values(self
, cpu_time_percentage
):
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
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
195 LOG
.warning("Increased CPU quota to avoid system error.")
196 return cpu_period
, cpu_quota
198 def _apply_mem_limits(self
, d
):
200 Calculate real mem limit and apply.
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
.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
)
218 def _calculate_mem_limit_value(self
, mem_limit
):
220 Calculate actual mem limit as input for cgroup API
221 :param mem_limit: abstract mem limit
222 :return: concrete mem limit
224 # ATTENTION minimum mem_limit per container is 4MB
227 LOG
.warning("Increased MEM limit because it was less than 4.0 MB.")
229 return int(mem_limit
*1024*1024)
231 def get_state_dict(self
):
233 Return the state of the resource model as simple dict.
234 Helper method for logging functionality.
237 # collect info about all allocated instances
238 allocation_state
= dict()
239 for k
, d
in self
._allocated
_compute
_instances
.iteritems():
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
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
262 def _get_flavor(self
, d
):
264 Get flavor assigned to given container.
265 Identified by d.flavor_name.
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
)
273 def _write_log(self
, d
, path
, action
):
275 Helper to log RM info for experiments.
277 :param path: log path
278 :param action: allocate or free
283 # we have a path: write out RM info
286 l
["container_state"] = d
.getStatus()
288 l
["rm_state"] = self
.get_state_dict()
290 with
open(path
, "a") as f
:
291 f
.write("%s\n" % json
.dumps(l
))
294 class UpbOverprovisioningCloudDcRM(UpbSimpleCloudDcRM
):
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.
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
306 def _compute_single_cu(self
):
308 Calculate percentage of CPU time of a singe CU unit.
309 Take scale-down facte for over provisioning into account.
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
))
317 return float(e_cpu
) / sum([rm
.dc_max_cu
for rm
in list(self
.registrar
.resource_models
)]) * self
.cpu_op_factor
320 class UpbDummyRM(UpbSimpleCloudDcRM
):
322 No limits. But log allocations.
324 def __init__(self
, *args
, **kvargs
):
325 super(UpbDummyRM
, self
).__init
__(*args
, **kvargs
)
326 self
.raise_no_cpu_resources_left
= False
328 def _apply_limits(self
):