1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # Neither the name of the SONATA-NFV, Paderborn University
17 # nor the names of its contributors may be used to endorse or promote
18 # products derived from this software without specific prior written
21 # This work has been performed in the framework of the SONATA project,
22 # funded by the European Commission under Grant number 671517 through
23 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
24 # acknowledge the contributions of their colleagues of the SONATA
25 # partner consortium (www.sonata-nfv.eu).
29 from emuvim
.dcemulator
.resourcemodel
import BaseResourceModel
, NotEnoughResourcesAvailable
31 LOG
= logging
.getLogger("rm.upb.simple")
32 LOG
.setLevel(logging
.DEBUG
)
37 class UpbSimpleCloudDcRM(BaseResourceModel
):
39 This will be an example resource model that limits the overall
40 resources that can be deployed per data center.
41 No over provisioning. Resources are fixed throughout entire container
45 def __init__(self
, max_cu
=32, max_mu
=1024,
46 deactivate_cpu_limit
=False,
47 deactivate_mem_limit
=False):
50 :param max_cu: Maximum number of compute units available in this DC.
51 :param max_mu: Maximum memory of entire dc.
54 self
.dc_max_cu
= max_cu
55 self
.dc_max_mu
= max_mu
58 self
.deactivate_cpu_limit
= deactivate_cpu_limit
59 self
.deactivate_mem_limit
= deactivate_mem_limit
62 self
.cpu_op_factor
= 1.0 # over provisioning factor
63 self
.mem_op_factor
= 1.0
64 self
.raise_no_cpu_resources_left
= True
65 self
.raise_no_mem_resources_left
= True
66 super(UpbSimpleCloudDcRM
, self
).__init
__()
68 def allocate(self
, d
):
70 Allocate resources for the given container.
71 Defined by d.flavor_name
75 self
._allocated
_compute
_instances
[d
.name
] = d
76 if not self
.deactivate_cpu_limit
:
78 if not self
.deactivate_mem_limit
:
82 def _allocate_cpu(self
, d
):
84 Actually allocate (bookkeeping)
88 fl_cu
= self
._get
_flavor
(d
).get("compute")
89 # check for over provisioning
90 if self
.dc_alloc_cu
+ fl_cu
> self
.dc_max_cu
and self
.raise_no_cpu_resources_left
:
91 raise NotEnoughResourcesAvailable(
92 "Not enough compute resources left.")
93 self
.dc_alloc_cu
+= fl_cu
95 def _allocate_mem(self
, d
):
97 Actually allocate (bookkeeping)
101 fl_mu
= self
._get
_flavor
(d
).get("memory")
102 # check for over provisioning
103 if self
.dc_alloc_mu
+ fl_mu
> self
.dc_max_mu
and self
.raise_no_mem_resources_left
:
104 raise NotEnoughResourcesAvailable(
105 "Not enough memory resources left.")
106 self
.dc_alloc_mu
+= fl_mu
110 Free resources allocated to the given container.
114 del self
._allocated
_compute
_instances
[d
.name
]
115 if not self
.deactivate_cpu_limit
:
117 if not self
.deactivate_mem_limit
:
121 def _free_cpu(self
, d
):
127 self
.dc_alloc_cu
-= self
._get
_flavor
(d
).get("compute")
129 def _free_mem(self
, d
):
135 self
.dc_alloc_mu
-= self
._get
_flavor
(d
).get("memory")
137 def _apply_limits(self
):
139 Recalculate real resource limits for all allocated containers and apply them
141 We have to recalculate for all containers to allow e.g. over provisioning models.
144 for d
in self
._allocated
_compute
_instances
.itervalues():
145 if not self
.deactivate_cpu_limit
:
146 self
._apply
_cpu
_limits
(d
)
147 if not self
.deactivate_mem_limit
:
148 self
._apply
_mem
_limits
(d
)
150 def _apply_cpu_limits(self
, d
):
152 Calculate real CPU limit (CFS bandwidth) and apply.
156 number_cu
= self
._get
_flavor
(d
).get("compute")
157 # calculate cpu time fraction of a single compute unit
158 self
.single_cu
= self
._compute
_single
_cu
()
159 # calculate cpu time fraction for container with given flavor
160 cpu_time_percentage
= self
.single_cu
* number_cu
161 # calculate input values for CFS scheduler bandwidth limitation
162 cpu_period
, cpu_quota
= self
._calculate
_cpu
_cfs
_values
(
164 # apply limits to container if changed
165 if d
.resources
['cpu_period'] != cpu_period
or d
.resources
['cpu_quota'] != cpu_quota
:
166 LOG
.debug("Setting CPU limit for %r: cpu_quota = cpu_period * limit = %f * %f = %f (op_factor=%f)" % (
167 d
.name
, cpu_period
, cpu_time_percentage
, cpu_quota
, self
.cpu_op_factor
))
168 d
.updateCpuLimit(cpu_period
=int(cpu_period
),
169 cpu_quota
=int(cpu_quota
))
171 def _compute_single_cu(self
):
173 Calculate percentage of CPU time of a singe CU unit.
176 # get cpu time fraction for entire emulation
177 e_cpu
= self
.registrar
.e_cpu
180 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 # calculate the fraction of cpu time for this container
192 cpu_quota
= cpu_period
* cpu_time_percentage
193 # ATTENTION >= 1000 to avoid a invalid argument system error ... no
197 LOG
.warning("Increased CPU quota to avoid system error.")
198 return cpu_period
, cpu_quota
200 def _apply_mem_limits(self
, d
):
202 Calculate real mem limit and apply.
206 number_mu
= self
._get
_flavor
(d
).get("memory")
207 # get memory amount for entire emulation
208 e_mem
= self
.registrar
.e_mem
209 # calculate amount of memory for a single mu
210 self
.single_mu
= float(
211 e_mem
) / sum([rm
.dc_max_mu
for rm
in list(self
.registrar
.resource_models
)])
212 # calculate mem for given flavor
213 mem_limit
= self
.single_mu
* number_mu
214 mem_limit
= self
._calculate
_mem
_limit
_value
(mem_limit
)
215 # apply to container if changed
216 if d
.resources
['mem_limit'] != mem_limit
:
217 LOG
.debug("Setting MEM limit for %r: mem_limit = %f MB (op_factor=%f)" %
218 (d
.name
, mem_limit
/ 1024 / 1024, self
.mem_op_factor
))
219 d
.updateMemoryLimit(mem_limit
=mem_limit
)
221 def _calculate_mem_limit_value(self
, mem_limit
):
223 Calculate actual mem limit as input for cgroup API
224 :param mem_limit: abstract mem limit
225 :return: concrete mem limit
227 # ATTENTION minimum mem_limit per container is 4MB
230 LOG
.warning("Increased MEM limit because it was less than 4.0 MB.")
232 return int(mem_limit
* 1024 * 1024)
234 def get_state_dict(self
):
236 Return the state of the resource model as simple dict.
237 Helper method for logging functionality.
240 # collect info about all allocated instances
241 allocation_state
= dict()
242 for k
, d
in self
._allocated
_compute
_instances
.iteritems():
244 s
["cpu_period"] = d
.cpu_period
245 s
["cpu_quota"] = d
.cpu_quota
246 s
["cpu_shares"] = d
.cpu_shares
247 s
["mem_limit"] = d
.mem_limit
248 s
["memswap_limit"] = d
.memswap_limit
249 allocation_state
[k
] = s
252 r
["e_cpu"] = self
.registrar
.e_cpu
253 r
["e_mem"] = self
.registrar
.e_mem
254 r
["dc_max_cu"] = self
.dc_max_cu
255 r
["dc_max_mu"] = self
.dc_max_mu
256 r
["dc_alloc_cu"] = self
.dc_alloc_cu
257 r
["dc_alloc_mu"] = self
.dc_alloc_mu
258 r
["single_cu_percentage"] = self
.single_cu
259 r
["single_mu_percentage"] = self
.single_mu
260 r
["cpu_op_factor"] = self
.cpu_op_factor
261 r
["mem_op_factor"] = self
.mem_op_factor
262 r
["allocation_state"] = allocation_state
265 def _get_flavor(self
, d
):
267 Get flavor assigned to given container.
268 Identified by d.flavor_name.
272 if d
.flavor_name
not in self
._flavors
:
273 raise Exception("Flavor %r does not exist" % d
.flavor_name
)
274 return self
._flavors
.get(d
.flavor_name
)
276 def _write_log(self
, d
, path
, action
):
278 Helper to log RM info for experiments.
280 :param path: log path
281 :param action: allocate or free
286 # we have a path: write out RM info
288 logd
["t"] = time
.time()
289 logd
["container_state"] = d
.getStatus()
290 logd
["action"] = action
291 logd
["rm_state"] = self
.get_state_dict()
293 with
open(path
, "a") as f
:
294 f
.write("%s\n" % json
.dumps(logd
))
297 class UpbOverprovisioningCloudDcRM(UpbSimpleCloudDcRM
):
299 This will be an example resource model that limits the overall
300 resources that can be deployed per data center.
301 Allows over provisioning. Might result in reducing resources of single
302 containers whenever a data-center is over provisioned.
304 # TODO add parts for memory
306 def __init__(self
, *args
, **kvargs
):
307 super(UpbOverprovisioningCloudDcRM
, self
).__init
__(*args
, **kvargs
)
308 self
.raise_no_cpu_resources_left
= False
310 def _compute_single_cu(self
):
312 Calculate percentage of CPU time of a singe CU unit.
313 Take scale-down facte for over provisioning into account.
316 # get cpu time fraction for entire emulation
317 e_cpu
= self
.registrar
.e_cpu
318 # calculate over provisioning scale factor
319 self
.cpu_op_factor
= float(self
.dc_max_cu
) / \
320 (max(self
.dc_max_cu
, self
.dc_alloc_cu
))
322 return float(e_cpu
) / sum([rm
.dc_max_cu
for rm
in list(
323 self
.registrar
.resource_models
)]) * self
.cpu_op_factor
326 class UpbDummyRM(UpbSimpleCloudDcRM
):
328 No limits. But log allocations.
331 def __init__(self
, *args
, **kvargs
):
332 super(UpbDummyRM
, self
).__init
__(*args
, **kvargs
)
333 self
.raise_no_cpu_resources_left
= False
335 def _apply_limits(self
):