bb087e648e9570860799f73f07f6be16032a3a90
[osm/SO.git] / rwlaunchpad / plugins / rwresmgr / rift / tasklets / rwresmgrtasklet / rwresmgr_core.py
1 #
2 # Copyright 2016 RIFT.IO Inc
3 #
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
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 #
16
17 import uuid
18 import collections
19 import asyncio
20 import concurrent.futures
21
22 import gi
23 gi.require_version('RwDts', '1.0')
24 gi.require_version('RwYang', '1.0')
25 gi.require_version('RwResourceMgrYang', '1.0')
26 gi.require_version('RwLaunchpadYang', '1.0')
27 gi.require_version('RwcalYang', '1.0')
28 from gi.repository import (
29 RwDts as rwdts,
30 RwYang,
31 RwResourceMgrYang,
32 RwLaunchpadYang,
33 RwcalYang,
34 )
35
36 from gi.repository.RwTypes import RwStatus
37
38 class ResMgrCALNotPresent(Exception):
39 pass
40
41 class ResMgrCloudAccountNotFound(Exception):
42 pass
43
44 class ResMgrCloudAccountExists(Exception):
45 pass
46
47 class ResMgrCloudAccountInUse(Exception):
48 pass
49
50 class ResMgrDuplicatePool(Exception):
51 pass
52
53 class ResMgrPoolNotAvailable(Exception):
54 pass
55
56 class ResMgrPoolOperationFailed(Exception):
57 pass
58
59 class ResMgrDuplicateEventId(Exception):
60 pass
61
62 class ResMgrUnknownEventId(Exception):
63 pass
64
65 class ResMgrUnknownResourceId(Exception):
66 pass
67
68 class ResMgrResourceIdBusy(Exception):
69 pass
70
71 class ResMgrResourceIdNotAllocated(Exception):
72 pass
73
74 class ResMgrNoResourcesAvailable(Exception):
75 pass
76
77 class ResMgrResourcesInitFailed(Exception):
78 pass
79
80 class ResMgrCALOperationFailure(Exception):
81 pass
82
83
84
85 class ResourceMgrCALHandler(object):
86 def __init__(self, loop, executor, log, log_hdl, account):
87 self._log = log
88 self._loop = loop
89 self._executor = executor
90 self._account = account.cal_account_msg
91 self._rwcal = account.cal
92 if account.account_type == 'aws':
93 self._subnets = ["172.31.97.0/24", "172.31.98.0/24", "172.31.99.0/24", "172.31.100.0/24", "172.31.101.0/24"]
94 else:
95 self._subnets = ["11.0.0.0/24",
96 "12.0.0.0/24",
97 "13.0.0.0/24",
98 "14.0.0.0/24",
99 "15.0.0.0/24",
100 "16.0.0.0/24",
101 "17.0.0.0/24",
102 "18.0.0.0/24",
103 "19.0.0.0/24",
104 "20.0.0.0/24",
105 "21.0.0.0/24",
106 "22.0.0.0/24",]
107 self._subnet_ptr = 0
108
109 def _select_link_subnet(self):
110 subnet = self._subnets[self._subnet_ptr]
111 self._subnet_ptr += 1
112 if self._subnet_ptr == len(self._subnets):
113 self._subnet_ptr = 0
114 return subnet
115
116 @asyncio.coroutine
117 def create_virtual_network(self, req_params):
118 #rc, rsp = self._rwcal.get_virtual_link_list(self._account)
119 self._log.debug("Calling get_virtual_link_list API")
120 rc, rsp = yield from self._loop.run_in_executor(self._executor,
121 self._rwcal.get_virtual_link_list,
122 self._account)
123
124 assert rc == RwStatus.SUCCESS
125
126 links = [vlink for vlink in rsp.virtual_link_info_list if vlink.name == req_params.name]
127 if links:
128 self._log.debug("Found existing virtual-network with matching name in cloud. Reusing the virtual-network with id: %s" %(links[0].virtual_link_id))
129 return ('precreated', links[0].virtual_link_id)
130 elif req_params.vim_network_name:
131 self._log.error("Virtual-network-allocate operation failed for cloud account: %s Vim Network with name %s does not pre-exist",
132 self._account.name, req_params.vim_network_name)
133 raise ResMgrCALOperationFailure("Virtual-network allocate operation failed for cloud account: %s Vim Network with name %s does not pre-exist"
134 %(self._account.name, req_params.vim_network_name))
135
136 params = RwcalYang.VirtualLinkReqParams()
137 params.from_dict(req_params.as_dict())
138 params.subnet = self._select_link_subnet()
139 #rc, rs = self._rwcal.create_virtual_link(self._account, params)
140 self._log.debug("Calling create_virtual_link API with params: %s" %(str(req_params)))
141 rc, rs = yield from self._loop.run_in_executor(self._executor,
142 self._rwcal.create_virtual_link,
143 self._account,
144 params)
145 if rc.status != RwStatus.SUCCESS:
146 self._log.error("Virtual-network-allocate operation failed for cloud account: %s - error_msg: %s, Traceback: %s",
147 self._account.name, rc.error_msg, rc.traceback)
148 raise ResMgrCALOperationFailure("Virtual-network allocate operation failed for cloud account: %s (%s)"
149 %(self._account.name, rc.error_msg))
150
151 return ('dynamic',rs)
152
153 @asyncio.coroutine
154 def delete_virtual_network(self, network_id):
155 #rc = self._rwcal.delete_virtual_link(self._account, network_id)
156 self._log.debug("Calling delete_virtual_link API with id: %s" %(network_id))
157 rc = yield from self._loop.run_in_executor(self._executor,
158 self._rwcal.delete_virtual_link,
159 self._account,
160 network_id)
161 if rc != RwStatus.SUCCESS:
162 self._log.error("Virtual-network-release operation failed for cloud account: %s. ResourceID: %s",
163 self._account.name,
164 network_id)
165 raise ResMgrCALOperationFailure("Virtual-network release operation failed for cloud account: %s. ResourceId: %s" %(self._account.name, network_id))
166
167 @asyncio.coroutine
168 def get_virtual_network_info(self, network_id):
169 #rc, rs = self._rwcal.get_virtual_link(self._account, network_id)
170 self._log.debug("Calling get_virtual_link_info API with id: %s" %(network_id))
171 rc, rs = yield from self._loop.run_in_executor(self._executor,
172 self._rwcal.get_virtual_link,
173 self._account,
174 network_id)
175 if rc != RwStatus.SUCCESS:
176 self._log.error("Virtual-network-info operation failed for cloud account: %s. ResourceID: %s",
177 self._account.name,
178 network_id)
179 raise ResMgrCALOperationFailure("Virtual-network-info operation failed for cloud account: %s. ResourceID: %s" %(self._account.name, network_id))
180 return rs
181
182 @asyncio.coroutine
183 def create_virtual_compute(self, req_params):
184 #rc, rsp = self._rwcal.get_vdu_list(self._account)
185 self._log.debug("Calling get_vdu_list API")
186
187 rc, rsp = yield from self._loop.run_in_executor(self._executor,
188 self._rwcal.get_vdu_list,
189 self._account)
190 assert rc == RwStatus.SUCCESS
191 vdus = [vm for vm in rsp.vdu_info_list if vm.name == req_params.name]
192 if vdus:
193 self._log.debug("Found existing virtual-compute with matching name in cloud. Reusing the virtual-compute element with id: %s" %(vdus[0].vdu_id))
194 return vdus[0].vdu_id
195
196 params = RwcalYang.VDUInitParams()
197 params.from_dict(req_params.as_dict())
198
199 if 'image_name' in req_params:
200 image_checksum = req_params.image_checksum if req_params.has_field("image_checksum") else None
201 params.image_id = yield from self.get_image_id_from_image_info(req_params.image_name, image_checksum)
202
203 #rc, rs = self._rwcal.create_vdu(self._account, params)
204 self._log.debug("Calling create_vdu API with params %s" %(str(params)))
205 rc, rs = yield from self._loop.run_in_executor(self._executor,
206 self._rwcal.create_vdu,
207 self._account,
208 params)
209
210 if rc.status != RwStatus.SUCCESS:
211 self._log.error("Virtual-compute-create operation failed for cloud account: %s - error_msg: %s, Traceback: %s",
212 self._account.name, rc.error_msg, rc.traceback)
213 raise ResMgrCALOperationFailure("Virtual-compute-create operation failed for cloud account: %s (%s)"
214 %(self._account.name, rc.error_msg))
215
216 return rs
217
218 @asyncio.coroutine
219 def modify_virtual_compute(self, req_params):
220 #rc = self._rwcal.modify_vdu(self._account, req_params)
221 self._log.debug("Calling modify_vdu API with params: %s" %(str(req_params)))
222 rc = yield from self._loop.run_in_executor(self._executor,
223 self._rwcal.modify_vdu,
224 self._account,
225 req_params)
226 if rc != RwStatus.SUCCESS:
227 self._log.error("Virtual-compute-modify operation failed for cloud account: %s", self._account.name)
228 raise ResMgrCALOperationFailure("Virtual-compute-modify operation failed for cloud account: %s" %(self._account.name))
229
230 @asyncio.coroutine
231 def delete_virtual_compute(self, compute_id):
232 #rc = self._rwcal.delete_vdu(self._account, compute_id)
233 self._log.debug("Calling delete_vdu API with id: %s" %(compute_id))
234 rc = yield from self._loop.run_in_executor(self._executor,
235 self._rwcal.delete_vdu,
236 self._account,
237 compute_id)
238 if rc != RwStatus.SUCCESS:
239 self._log.error("Virtual-compute-release operation failed for cloud account: %s. ResourceID: %s",
240 self._account.name,
241 compute_id)
242 raise ResMgrCALOperationFailure("Virtual-compute-release operation failed for cloud account: %s. ResourceID: %s" %(self._account.name, compute_id))
243
244 @asyncio.coroutine
245 def get_virtual_compute_info(self, compute_id, mgmt_network = None):
246 #rc, rs = self._rwcal.get_vdu(self._account, compute_id)
247 self._log.debug("Calling get_vdu API with id: %s" %(compute_id))
248 rc, rs = yield from self._loop.run_in_executor(self._executor,
249 self._rwcal.get_vdu,
250 self._account,
251 compute_id,
252 mgmt_network)
253 if rc != RwStatus.SUCCESS:
254 self._log.error("Virtual-compute-info operation failed for cloud account: %s. ResourceID: %s",
255 self._account.name,
256 compute_id)
257 raise ResMgrCALOperationFailure("Virtual-compute-info operation failed for cloud account: %s. ResourceID: %s" %(self._account.name, compute_id))
258 return rs
259
260 @asyncio.coroutine
261 def get_compute_flavor_info_list(self):
262 #rc, rs = self._rwcal.get_flavor_list(self._account)
263 self._log.debug("Calling get_flavor_list API")
264 rc, rs = yield from self._loop.run_in_executor(self._executor,
265 self._rwcal.get_flavor_list,
266 self._account)
267 if rc != RwStatus.SUCCESS:
268 self._log.error("Get-flavor-info-list operation failed for cloud account: %s",
269 self._account.name)
270 raise ResMgrCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(self._account.name))
271 return rs.flavorinfo_list
272
273 @asyncio.coroutine
274 def create_compute_flavor(self, request):
275 flavor = RwcalYang.FlavorInfoItem()
276 flavor.name = str(uuid.uuid4())
277 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate']
278 epa_dict = {k: v for k, v in request.as_dict().items() if k in epa_types}
279 flavor.from_dict(epa_dict)
280
281 self._log.info("Creating flavor: %s", flavor)
282 #rc, rs = self._rwcal.create_flavor(self._account, flavor)
283 self._log.debug("Calling create_flavor API")
284 rc, rs = yield from self._loop.run_in_executor(self._executor,
285 self._rwcal.create_flavor,
286 self._account,
287 flavor)
288 if rc != RwStatus.SUCCESS:
289 self._log.error("Create-flavor operation failed for cloud account: %s",
290 self._account.name)
291 raise ResMgrCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(self._account.name))
292 return rs
293
294 @asyncio.coroutine
295 def get_image_info_list(self):
296 #rc, rs = self._rwcal.get_image_list(self._account)
297 self._log.debug("Calling get_image_list API")
298 rc, rs = yield from self._loop.run_in_executor(self._executor,
299 self._rwcal.get_image_list,
300 self._account)
301 if rc != RwStatus.SUCCESS:
302 self._log.error("Get-image-info-list operation failed for cloud account: %s",
303 self._account.name)
304 raise ResMgrCALOperationFailure("Get-image-info-list operation failed for cloud account: %s" %(self._account.name))
305 return rs.imageinfo_list
306
307 @asyncio.coroutine
308 def get_image_id_from_image_info(self, image_name, image_checksum=None):
309 self._log.debug("Looking up image id for image name %s and checksum %s on cloud account: %s",
310 image_name, image_checksum, self._account.name
311 )
312
313 image_list = yield from self.get_image_info_list()
314 matching_images = [i for i in image_list if i.name == image_name]
315
316 # If the image checksum was filled in then further filter the images by the checksum
317 if image_checksum is not None:
318 matching_images = [i for i in matching_images if i.checksum == image_checksum]
319 else:
320 self._log.warning("Image checksum not provided. Lookup using image name (%s) only.",
321 image_name)
322
323 if len(matching_images) == 0:
324 raise ResMgrCALOperationFailure("Could not find image name {} (using checksum: {}) for cloud account: {}".format(
325 image_name, image_checksum, self._account.name
326 ))
327
328 elif len(matching_images) > 1:
329 unique_checksums = {i.checksum for i in matching_images}
330 if len(unique_checksums) > 1:
331 msg = ("Too many images with different checksums matched "
332 "image name of %s for cloud account: %s" % (image_name, self._account.name))
333 raise ResMgrCALOperationFailure(msg)
334
335 return matching_images[0].id
336
337 @asyncio.coroutine
338 def get_image_info(self, image_id):
339 #rc, rs = self._rwcal.get_image(self._account, image_id)
340 self._log.debug("Calling get_image API for id: %s" %(image_id))
341 rc, rs = yield from self._loop.run_in_executor(self._executor,
342 self._rwcal.get_image,
343 self._account,
344 image_id)
345 if rc != RwStatus.SUCCESS:
346 self._log.error("Get-image-info-list operation failed for cloud account: %s",
347 self._account.name)
348 raise ResMgrCALOperationFailure("Get-image-info operation failed for cloud account: %s" %(self._account.name))
349 return rs.imageinfo_list
350
351 def dynamic_flavor_supported(self):
352 return getattr(self._account, self._account.account_type).dynamic_flavor_support
353
354
355 class Resource(object):
356 def __init__(self, resource_id, resource_type, request):
357 self._id = resource_id
358 self._type = resource_type
359 self._request = request
360
361 @property
362 def resource_id(self):
363 return self._id
364
365 @property
366 def resource_type(self):
367 return self._type
368
369 @property
370 def request(self):
371 return self._request
372
373 def cleanup(self):
374 pass
375
376
377 class ComputeResource(Resource):
378 pass
379
380
381 class NetworkResource(Resource):
382 pass
383
384
385 class ResourcePoolInfo(object):
386 def __init__(self, name, pool_type, resource_type, max_size):
387 self.name = name
388 self.pool_type = pool_type
389 self.resource_type = resource_type
390 self.max_size = max_size
391
392 @classmethod
393 def from_dict(cls, pool_dict):
394 return cls(
395 pool_dict["name"],
396 pool_dict["pool_type"],
397 pool_dict["resource_type"],
398 pool_dict["max_size"],
399 )
400
401
402 class ResourcePool(object):
403 def __init__(self, log, loop, pool_info, resource_class, cal):
404 self._log = log
405 self._loop = loop
406 self._name = pool_info.name
407 self._pool_type = pool_info.pool_type
408 self._resource_type = pool_info.resource_type
409 self._cal = cal
410 self._resource_class = resource_class
411
412 self._max_size = pool_info.max_size
413
414 self._status = 'unlocked'
415 ### A Dictionary of all the resources in this pool, keyed by CAL resource-id
416 self._all_resources = {}
417 ### A List of free resources in this pool
418 self._free_resources = []
419 ### A Dictionary of all the allocated resources in this pool, keyed by CAL resource-id
420 self._allocated_resources = {}
421
422 @property
423 def name(self):
424 return self._name
425
426 @property
427 def cal(self):
428 """ This instance's ResourceMgrCALHandler """
429 return self._cal
430
431 @property
432 def pool_type(self):
433 return self._pool_type
434
435 @property
436 def resource_type(self):
437 return self._resource_type
438
439 @property
440 def max_size(self):
441 return self._max_size
442
443 @property
444 def status(self):
445 return self._status
446
447 def in_use(self):
448 if len(self._allocated_resources) != 0:
449 return True
450 else:
451 return False
452
453 def update_cal_handler(self, cal):
454 if self.in_use():
455 raise ResMgrPoolOperationFailed(
456 "Cannot update CAL plugin for in use pool"
457 )
458
459 self._cal = cal
460
461 def lock_pool(self):
462 self._log.info("Locking the pool :%s", self.name)
463 self._status = 'locked'
464
465 def unlock_pool(self):
466 self._log.info("Unlocking the pool :%s", self.name)
467 self._status = 'unlocked'
468
469 def add_resource(self, resource_info):
470 self._log.info("Adding static resource to Pool: %s, Resource-id: %s Resource-Type: %s",
471 self.name,
472 resource_info.resource_id,
473 self.resource_type)
474
475 ### Add static resources to pool
476 resource = self._resource_class(resource_info.resource_id, 'static')
477 assert resource.resource_id == resource_info.resource_id
478 self._all_resources[resource.resource_id] = resource
479 self._free_resources.append(resource)
480
481 def delete_resource(self, resource_id):
482 if resource_id not in self._all_resources:
483 self._log.error("Resource Id: %s not present in pool: %s. Delete operation failed", resource_id, self.name)
484 raise ResMgrUnknownResourceId("Resource Id: %s requested for release is not found" %(resource_id))
485
486 if resource_id in self._allocated_resources:
487 self._log.error("Resource Id: %s in use. Delete operation failed", resource_id)
488 raise ResMgrResourceIdBusy("Resource Id: %s requested for release is in use" %(resource_id))
489
490 self._log.info("Deleting resource: %s from pool: %s, Resource-Type",
491 resource_id,
492 self.name,
493 self.resource_type)
494
495 resource = self._all_resources.pop(resource_id)
496 self._free_resources.remove(resource)
497 resource.cleanup()
498 del resource
499
500 @asyncio.coroutine
501 def read_resource_info(self, resource_id):
502 if resource_id not in self._all_resources:
503 self._log.error("Resource Id: %s not present in pool: %s. Read operation failed", resource_id, self.name)
504 raise ResMgrUnknownResourceId("Resource Id: %s requested for read is not found" %(resource_id))
505
506 if resource_id not in self._allocated_resources:
507 self._log.error("Resource Id: %s not in use. Read operation failed", resource_id)
508 raise ResMgrResourceIdNotAllocated("Resource Id: %s not in use. Read operation failed" %(resource_id))
509
510 resource = self._allocated_resources[resource_id]
511 resource_info = yield from self.get_resource_info(resource)
512 return resource_info
513
514 def get_pool_info(self):
515 info = RwResourceMgrYang.ResourceRecordInfo()
516 self._log.info("Providing info for pool: %s", self.name)
517 info.name = self.name
518 if self.pool_type:
519 info.pool_type = self.pool_type
520 if self.resource_type:
521 info.resource_type = self.resource_type
522 if self.status:
523 info.pool_status = self.status
524
525 info.total_resources = len(self._all_resources)
526 info.free_resources = len(self._free_resources)
527 info.allocated_resources = len(self._allocated_resources)
528 return info
529
530 def cleanup(self):
531 for _, v in self._all_resources.items():
532 v.cleanup()
533
534 @asyncio.coroutine
535 def _allocate_static_resource(self, request, resource_type):
536 unit_type = {'compute': 'VDU', 'network':'VirtualLink'}
537 match_found = False
538 resource = None
539 self._log.info("Doing resource match from pool :%s", self._free_resources)
540 for resource in self._free_resources:
541 resource_info = yield from self.get_resource_info(resource)
542 self._log.info("Attempting to match %s-requirements for %s: %s with resource-id :%s",
543 resource_type, unit_type[resource_type],request.name, resource.resource_id)
544 if self.match_epa_params(resource_info, request):
545 if self.match_image_params(resource_info, request):
546 match_found = True
547 self._log.info("%s-requirements matched for %s: %s with resource-id :%s",
548 resource_type, unit_type[resource_type],request.name, resource.resource_id)
549 yield from self.initialize_resource_in_cal(resource, request)
550 break
551
552 if not match_found:
553 self._log.error("No match found for %s-requirements for %s: %s in pool: %s. %s instantiation failed",
554 resource_type,
555 unit_type[resource_type],
556 request.name,
557 self.name,
558 unit_type[resource_type])
559 return None
560 else:
561 ### Move resource from free-list into allocated-list
562 self._log.info("Allocating the static resource with resource-id: %s for %s: %s",
563 resource.resource_id,
564 unit_type[resource_type],request.name)
565 self._free_resources.remove(resource)
566 self._allocated_resources[resource.resource_id] = resource
567
568 return resource
569
570 @asyncio.coroutine
571 def allocate_resource(self, request):
572 resource = yield from self.allocate_resource_in_cal(request)
573 resource_info = yield from self.get_resource_info(resource)
574 return resource.resource_id, resource_info
575
576 @asyncio.coroutine
577 def release_resource(self, resource_id):
578 self._log.debug("Releasing resource_id %s in pool %s", resource_id, self.name)
579 if resource_id not in self._allocated_resources:
580 self._log.error("Failed to release a resource with resource-id: %s in pool: %s. Resource not known",
581 resource_id,
582 self.name)
583 raise ResMgrUnknownResourceId("Failed to release resource with resource-id: %s. Unknown resource-id" %(resource_id))
584
585 ### Get resource object
586 resource = self._allocated_resources.pop(resource_id)
587 yield from self.uninitialize_resource_in_cal(resource)
588 yield from self.release_cal_resource(resource)
589
590
591 class NetworkPool(ResourcePool):
592 def __init__(self, log, loop, pool_info, cal):
593 super(NetworkPool, self).__init__(log, loop, pool_info, NetworkResource, cal)
594
595 @asyncio.coroutine
596 def allocate_resource_in_cal(self, request):
597 resource = None
598 if self.pool_type == 'static':
599 self._log.info("Attempting network resource allocation from static pool: %s", self.name)
600 ### Attempt resource allocation from static pool
601 resource = yield from self._allocate_static_resource(request, 'network')
602 elif self.pool_type == 'dynamic':
603 ### Attempt resource allocation from dynamic pool
604 self._log.info("Attempting network resource allocation from dynamic pool: %s", self.name)
605 if len(self._free_resources) != 0:
606 self._log.info("Dynamic pool: %s has %d static resources, Attempting resource allocation from static resources",
607 self.name, len(self._free_resources))
608 resource = yield from self._allocate_static_resource(request, 'network')
609 if resource is None:
610 self._log.info("Could not resource from static resources. Going for dynamic resource allocation")
611 ## Not static resource available. Attempt dynamic resource from pool
612 resource = yield from self.allocate_dynamic_resource(request)
613 if resource is None:
614 raise ResMgrNoResourcesAvailable("No matching resource available for allocation from pool: %s" %(self.name))
615 return resource
616
617 @asyncio.coroutine
618 def allocate_dynamic_resource(self, request):
619 resource_type, resource_id = yield from self._cal.create_virtual_network(request)
620 if resource_id in self._all_resources:
621 self._log.error("Resource with id %s name %s of type %s is already used", resource_id, request.name, resource_type)
622 raise ResMgrNoResourcesAvailable("Resource with name %s of type network is already used" %(resource_id))
623 resource = self._resource_class(resource_id, resource_type, request)
624 self._all_resources[resource_id] = resource
625 self._allocated_resources[resource_id] = resource
626 self._log.info("Successfully allocated virtual-network resource from CAL with resource-id: %s", resource_id)
627 return resource
628
629 @asyncio.coroutine
630 def release_cal_resource(self, resource):
631 if resource.resource_type == 'dynamic':
632 self._log.debug("Deleting virtual network with network_id: %s", resource.resource_id)
633 yield from self._cal.delete_virtual_network(resource.resource_id)
634 self._all_resources.pop(resource.resource_id)
635 self._log.info("Successfully released virtual-network resource in CAL with resource-id: %s", resource.resource_id)
636 elif resource.resource_type == 'precreated':
637 self._all_resources.pop(resource.resource_id)
638 self._log.info("Successfully removed precreated virtual-network resource from allocated list: %s", resource.resource_id)
639 else:
640 self._log.info("Successfully released virtual-network resource with resource-id: %s into available-list", resource.resource_id)
641 self._free_resources.append(resource)
642
643 @asyncio.coroutine
644 def get_resource_info(self, resource):
645 info = yield from self._cal.get_virtual_network_info(resource.resource_id)
646 self._log.info("Successfully retrieved virtual-network information from CAL with resource-id: %s. Info: %s",
647 resource.resource_id, str(info))
648 response = RwResourceMgrYang.VirtualLinkEventData_ResourceInfo()
649 response.from_dict(info.as_dict())
650 response.pool_name = self.name
651 response.resource_state = 'active'
652 return response
653
654 @asyncio.coroutine
655 def get_info_by_id(self, resource_id):
656 info = yield from self._cal.get_virtual_network_info(resource_id)
657 self._log.info("Successfully retrieved virtual-network information from CAL with resource-id: %s. Info: %s",
658 resource_id, str(info))
659 return info
660
661 def match_image_params(self, resource_info, request_params):
662 return True
663
664 def match_epa_params(self, resource_info, request_params):
665 if not hasattr(request_params, 'provider_network'):
666 ### Its a match if nothing is requested
667 return True
668 else:
669 required = getattr(request_params, 'provider_network')
670
671 if not hasattr(resource_info, 'provider_network'):
672 ### Its no match
673 return False
674 else:
675 available = getattr(resource_info, 'provider_network')
676
677 self._log.debug("Matching Network EPA params. Required: %s, Available: %s", required, available)
678
679 if required.has_field('name') and required.name!= available.name:
680 self._log.debug("Provider Network mismatch. Required: %s, Available: %s",
681 required.name,
682 available.name)
683 return False
684
685 self._log.debug("Matching EPA params physical network name")
686
687 if required.has_field('physical_network') and required.physical_network != available.physical_network:
688 self._log.debug("Physical Network mismatch. Required: %s, Available: %s",
689 required.physical_network,
690 available.physical_network)
691 return False
692
693 self._log.debug("Matching EPA params overlay type")
694 if required.has_field('overlay_type') and required.overlay_type != available.overlay_type:
695 self._log.debug("Overlay type mismatch. Required: %s, Available: %s",
696 required.overlay_type,
697 available.overlay_type)
698 return False
699
700 self._log.debug("Matching EPA params SegmentationID")
701 if required.has_field('segmentation_id') and required.segmentation_id != available.segmentation_id:
702 self._log.debug("Segmentation-Id mismatch. Required: %s, Available: %s",
703 required.segmentation_id,
704 available.segmentation_id)
705 return False
706 return True
707
708 @asyncio.coroutine
709 def initialize_resource_in_cal(self, resource, request):
710 pass
711
712 @asyncio.coroutine
713 def uninitialize_resource_in_cal(self, resource):
714 pass
715
716
717 class ComputePool(ResourcePool):
718 def __init__(self, log, loop, pool_info, cal):
719 super(ComputePool, self).__init__(log, loop, pool_info, ComputeResource, cal)
720
721 @asyncio.coroutine
722 def allocate_resource_in_cal(self, request):
723 resource = None
724 if self.pool_type == 'static':
725 self._log.info("Attempting compute resource allocation from static pool: %s", self.name)
726 ### Attempt resource allocation from static pool
727 resource = yield from self._allocate_static_resource(request, 'compute')
728 elif self.pool_type == 'dynamic':
729 ### Attempt resource allocation from dynamic pool
730 self._log.info("Attempting compute resource allocation from dynamic pool: %s", self.name)
731 if len(self._free_resources) != 0:
732 self._log.info("Dynamic pool: %s has %d static resources, Attempting resource allocation from static resources",
733 len(self._free_resources),
734 self.name)
735 resource = yield from self._allocate_static_resource(request, 'compute')
736 if resource is None:
737 self._log.info("Attempting for dynamic resource allocation")
738 resource = yield from self.allocate_dynamic_resource(request)
739 if resource is None:
740 raise ResMgrNoResourcesAvailable("No matching resource available for allocation from pool: %s" %(self.name))
741
742 requested_params = RwcalYang.VDUInitParams()
743 requested_params.from_dict(request.as_dict())
744 resource.requested_params = requested_params
745 return resource
746
747 @asyncio.coroutine
748 def allocate_dynamic_resource(self, request):
749 #request.flavor_id = yield from self.select_resource_flavor(request)
750 resource_id = yield from self._cal.create_virtual_compute(request)
751 resource = self._resource_class(resource_id, 'dynamic', request)
752 self._all_resources[resource_id] = resource
753 self._allocated_resources[resource_id] = resource
754 self._log.info("Successfully allocated virtual-compute resource from CAL with resource-id: %s", resource_id)
755 return resource
756
757 @asyncio.coroutine
758 def release_cal_resource(self, resource):
759 if hasattr(resource, 'requested_params'):
760 delattr(resource, 'requested_params')
761 if resource.resource_type == 'dynamic':
762 yield from self._cal.delete_virtual_compute(resource.resource_id)
763 self._all_resources.pop(resource.resource_id)
764 self._log.info("Successfully released virtual-compute resource in CAL with resource-id: %s", resource.resource_id)
765 else:
766 self._log.info("Successfully released virtual-compute resource with resource-id: %s into available-list", resource.resource_id)
767 self._free_resources.append(resource)
768
769 @asyncio.coroutine
770 def get_resource_info(self, resource):
771 info = yield from self._cal.get_virtual_compute_info(resource.resource_id, resource.requested_params.mgmt_network)
772
773 self._log.info("Successfully retrieved virtual-compute information from CAL with resource-id: %s. Info: %s",
774 resource.resource_id, str(info))
775 response = RwResourceMgrYang.VDUEventData_ResourceInfo()
776 response.from_dict(info.as_dict())
777 response.pool_name = self.name
778 response.resource_state = self._get_resource_state(info, resource.requested_params)
779 return response
780
781 @asyncio.coroutine
782 def get_info_by_id(self, resource_id):
783 info = yield from self._cal.get_virtual_compute_info(resource_id)
784 self._log.info("Successfully retrieved virtual-compute information from CAL with resource-id: %s. Info: %s",
785 resource_id, str(info))
786 return info
787
788 def _get_resource_state(self, resource_info, requested_params):
789
790
791 def conn_pts_len_equal():
792 # if explicit mgmt network is defined then the allocated ports might
793 # one more than the expected.
794 allocated_ports = len(resource_info.connection_points)
795 requested_ports = len(requested_params.connection_points)
796
797 if not requested_params.mgmt_network:
798 allocated_ports -= 1
799
800 return allocated_ports == requested_ports
801
802 if resource_info.state == 'failed':
803 self._log.error("<Compute-Resource: %s> Reached failed state.",
804 resource_info.name)
805 return 'failed'
806
807 if resource_info.state != 'active':
808 self._log.info("<Compute-Resource: %s> Not reached active state.",
809 resource_info.name)
810 return 'pending'
811
812 if not resource_info.has_field('management_ip') or resource_info.management_ip == '':
813 self._log.info("<Compute-Resource: %s> Management IP not assigned.",
814 resource_info.name)
815 return 'pending'
816
817 if (requested_params.has_field('allocate_public_address')) and (requested_params.allocate_public_address == True):
818 if not resource_info.has_field('public_ip'):
819 self._log.warning("<Compute-Resource: %s> Management IP not assigned- waiting for public ip, %s",
820 resource_info.name, requested_params)
821 return 'pending'
822
823 if not conn_pts_len_equal():
824 self._log.warning("<Compute-Resource: %s> Waiting for requested number of ports to be assigned to virtual-compute, requested: %d, assigned: %d",
825 resource_info.name,
826 len(requested_params.connection_points),
827 len(resource_info.connection_points))
828 return 'pending'
829
830 #not_active = [c for c in resource_info.connection_points
831 # if c.state != 'active']
832
833 #if not_active:
834 # self._log.warning("<Compute-Resource: %s> Management IP not assigned- waiting for connection_points , %s",
835 # resource_info.name, resource_info)
836 # return 'pending'
837
838 ## Find the connection_points which are in active state but does not have IP address
839 no_address = [c for c in resource_info.connection_points
840 if (c.state == 'active') and (not c.has_field('ip_address'))]
841
842 if no_address:
843 self._log.warning("<Compute-Resource: %s> Management IP not assigned- waiting for connection_points , %s",
844 resource_info.name, resource_info)
845 return 'pending'
846
847 return 'active'
848
849 @asyncio.coroutine
850 def select_resource_flavor(self, request):
851 flavors = yield from self._cal.get_compute_flavor_info_list()
852 self._log.debug("Received %d flavor information from RW.CAL", len(flavors))
853 flavor_id = None
854 match_found = False
855 for flv in flavors:
856 self._log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
857 request.name, flv)
858 if self.match_epa_params(flv, request):
859 self._log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
860 request.name, flv.name, flv.id)
861 match_found = True
862 flavor_id = flv.id
863 break
864
865 if not match_found:
866 ### Check if CAL account allows dynamic flavor creation
867 if self._cal.dynamic_flavor_supported():
868 self._log.info("Attempting to create a new flavor for required compute-requirement for VDU: %s", request.name)
869 flavor_id = yield from self._cal.create_compute_flavor(request)
870 else:
871 ### No match with existing flavors and CAL does not support dynamic flavor creation
872 self._log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", request.name)
873 raise ResMgrNoResourcesAvailable("No resource available with matching EPA attributes")
874 else:
875 ### Found flavor
876 self._log.info("Found flavor with id: %s for compute requirement for VDU: %s",
877 flavor_id, request.name)
878 return flavor_id
879
880 def _match_vm_flavor(self, required, available):
881 self._log.info("Matching VM Flavor attributes")
882 if available.vcpu_count != required.vcpu_count:
883 self._log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
884 required.vcpu_count,
885 available.vcpu_count)
886 return False
887 if available.memory_mb != required.memory_mb:
888 self._log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
889 required.memory_mb,
890 available.memory_mb)
891 return False
892 if available.storage_gb != required.storage_gb:
893 self._log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
894 required.storage_gb,
895 available.storage_gb)
896 return False
897 self._log.debug("VM Flavor match found")
898 return True
899
900 def _match_guest_epa(self, required, available):
901 self._log.info("Matching Guest EPA attributes")
902 if required.has_field('pcie_device'):
903 self._log.debug("Matching pcie_device")
904 if available.has_field('pcie_device') == False:
905 self._log.debug("Matching pcie_device failed. Not available in flavor")
906 return False
907 else:
908 for dev in required.pcie_device:
909 if not [ d for d in available.pcie_device
910 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
911 self._log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
912 return False
913 elif available.has_field('pcie_device'):
914 self._log.debug("Rejecting available flavor because pcie_device not required but available")
915 return False
916
917
918 if required.has_field('mempage_size'):
919 self._log.debug("Matching mempage_size")
920 if available.has_field('mempage_size') == False:
921 self._log.debug("Matching mempage_size failed. Not available in flavor")
922 return False
923 else:
924 if required.mempage_size != available.mempage_size:
925 self._log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
926 return False
927 elif available.has_field('mempage_size'):
928 self._log.debug("Rejecting available flavor because mempage_size not required but available")
929 return False
930
931 if required.has_field('cpu_pinning_policy'):
932 self._log.debug("Matching cpu_pinning_policy")
933 if required.cpu_pinning_policy != 'ANY':
934 if available.has_field('cpu_pinning_policy') == False:
935 self._log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
936 return False
937 else:
938 if required.cpu_pinning_policy != available.cpu_pinning_policy:
939 self._log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
940 return False
941 elif available.has_field('cpu_pinning_policy'):
942 self._log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
943 return False
944
945 if required.has_field('cpu_thread_pinning_policy'):
946 self._log.debug("Matching cpu_thread_pinning_policy")
947 if available.has_field('cpu_thread_pinning_policy') == False:
948 self._log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
949 return False
950 else:
951 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
952 self._log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
953 return False
954 elif available.has_field('cpu_thread_pinning_policy'):
955 self._log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
956 return False
957
958 if required.has_field('trusted_execution'):
959 self._log.debug("Matching trusted_execution")
960 if required.trusted_execution == True:
961 if available.has_field('trusted_execution') == False:
962 self._log.debug("Matching trusted_execution failed. Not available in flavor")
963 return False
964 else:
965 if required.trusted_execution != available.trusted_execution:
966 self._log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
967 return False
968 elif available.has_field('trusted_execution'):
969 self._log.debug("Rejecting available flavor because trusted_execution not required but available")
970 return False
971
972 if required.has_field('numa_node_policy'):
973 self._log.debug("Matching numa_node_policy")
974 if available.has_field('numa_node_policy') == False:
975 self._log.debug("Matching numa_node_policy failed. Not available in flavor")
976 return False
977 else:
978 if required.numa_node_policy.has_field('node_cnt'):
979 self._log.debug("Matching numa_node_policy node_cnt")
980 if available.numa_node_policy.has_field('node_cnt') == False:
981 self._log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
982 return False
983 else:
984 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
985 self._log.debug("Matching numa_node_policy node_cnt failed. Required: %s, Available: %s",required.numa_node_policy.node_cnt, available.numa_node_policy.node_cnt)
986 return False
987 elif available.numa_node_policy.has_field('node_cnt'):
988 self._log.debug("Rejecting available flavor because numa node count not required but available")
989 return False
990
991 if required.numa_node_policy.has_field('mem_policy'):
992 self._log.debug("Matching numa_node_policy mem_policy")
993 if available.numa_node_policy.has_field('mem_policy') == False:
994 self._log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
995 return False
996 else:
997 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
998 self._log.debug("Matching numa_node_policy mem_policy failed. Required: %s, Available: %s", required.numa_node_policy.mem_policy, available.numa_node_policy.mem_policy)
999 return False
1000 elif available.numa_node_policy.has_field('mem_policy'):
1001 self._log.debug("Rejecting available flavor because num node mem_policy not required but available")
1002 return False
1003
1004 if required.numa_node_policy.has_field('node'):
1005 self._log.debug("Matching numa_node_policy nodes configuration")
1006 if available.numa_node_policy.has_field('node') == False:
1007 self._log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1008 return False
1009 for required_node in required.numa_node_policy.node:
1010 self._log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1011 numa_match = False
1012 for available_node in available.numa_node_policy.node:
1013 if required_node.id != available_node.id:
1014 self._log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1015 continue
1016 if required_node.vcpu != available_node.vcpu:
1017 self._log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1018 continue
1019 if required_node.memory_mb != available_node.memory_mb:
1020 self._log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1021 continue
1022 numa_match = True
1023 if numa_match == False:
1024 return False
1025 elif available.numa_node_policy.has_field('node'):
1026 self._log.debug("Rejecting available flavor because numa nodes not required but available")
1027 return False
1028 elif available.has_field('numa_node_policy'):
1029 self._log.debug("Rejecting available flavor because numa_node_policy not required but available")
1030 return False
1031 self._log.info("Successful match for Guest EPA attributes")
1032 return True
1033
1034 def _match_vswitch_epa(self, required, available):
1035 self._log.debug("VSwitch EPA match found")
1036 return True
1037
1038 def _match_hypervisor_epa(self, required, available):
1039 self._log.debug("Hypervisor EPA match found")
1040 return True
1041
1042 def _match_host_epa(self, required, available):
1043 self._log.info("Matching Host EPA attributes")
1044 if required.has_field('cpu_model'):
1045 self._log.debug("Matching CPU model")
1046 if available.has_field('cpu_model') == False:
1047 self._log.debug("Matching CPU model failed. Not available in flavor")
1048 return False
1049 else:
1050 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1051 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1052 self._log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1053 return False
1054 elif available.has_field('cpu_model'):
1055 self._log.debug("Rejecting available flavor because cpu_model not required but available")
1056 return False
1057
1058 if required.has_field('cpu_arch'):
1059 self._log.debug("Matching CPU architecture")
1060 if available.has_field('cpu_arch') == False:
1061 self._log.debug("Matching CPU architecture failed. Not available in flavor")
1062 return False
1063 else:
1064 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1065 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1066 self._log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1067 return False
1068 elif available.has_field('cpu_arch'):
1069 self._log.debug("Rejecting available flavor because cpu_arch not required but available")
1070 return False
1071
1072 if required.has_field('cpu_vendor'):
1073 self._log.debug("Matching CPU vendor")
1074 if available.has_field('cpu_vendor') == False:
1075 self._log.debug("Matching CPU vendor failed. Not available in flavor")
1076 return False
1077 else:
1078 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1079 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1080 self._log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1081 return False
1082 elif available.has_field('cpu_vendor'):
1083 self._log.debug("Rejecting available flavor because cpu_vendor not required but available")
1084 return False
1085
1086 if required.has_field('cpu_socket_count'):
1087 self._log.debug("Matching CPU socket count")
1088 if available.has_field('cpu_socket_count') == False:
1089 self._log.debug("Matching CPU socket count failed. Not available in flavor")
1090 return False
1091 else:
1092 if required.cpu_socket_count != available.cpu_socket_count:
1093 self._log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1094 return False
1095 elif available.has_field('cpu_socket_count'):
1096 self._log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1097 return False
1098
1099 if required.has_field('cpu_core_count'):
1100 self._log.debug("Matching CPU core count")
1101 if available.has_field('cpu_core_count') == False:
1102 self._log.debug("Matching CPU core count failed. Not available in flavor")
1103 return False
1104 else:
1105 if required.cpu_core_count != available.cpu_core_count:
1106 self._log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1107 return False
1108 elif available.has_field('cpu_core_count'):
1109 self._log.debug("Rejecting available flavor because cpu_core_count not required but available")
1110 return False
1111
1112 if required.has_field('cpu_core_thread_count'):
1113 self._log.debug("Matching CPU core thread count")
1114 if available.has_field('cpu_core_thread_count') == False:
1115 self._log.debug("Matching CPU core thread count failed. Not available in flavor")
1116 return False
1117 else:
1118 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1119 self._log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1120 return False
1121 elif available.has_field('cpu_core_thread_count'):
1122 self._log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1123 return False
1124
1125 if required.has_field('cpu_feature'):
1126 self._log.debug("Matching CPU feature list")
1127 if available.has_field('cpu_feature') == False:
1128 self._log.debug("Matching CPU feature list failed. Not available in flavor")
1129 return False
1130 else:
1131 for feature in required.cpu_feature:
1132 if feature not in available.cpu_feature:
1133 self._log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1134 return False
1135 elif available.has_field('cpu_feature'):
1136 self._log.debug("Rejecting available flavor because cpu_feature not required but available")
1137 return False
1138 self._log.info("Successful match for Host EPA attributes")
1139 return True
1140
1141
1142 def _match_placement_group_inputs(self, required, available):
1143 self._log.info("Matching Host aggregate attributes")
1144
1145 if not required and not available:
1146 # Host aggregate not required and not available => success
1147 self._log.info("Successful match for Host Aggregate attributes")
1148 return True
1149 if required and available:
1150 # Host aggregate requested and available => Do a match and decide
1151 xx = [ x.as_dict() for x in required ]
1152 yy = [ y.as_dict() for y in available ]
1153 for i in xx:
1154 if i not in yy:
1155 self._log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1156 return False
1157 self._log.info("Successful match for Host Aggregate attributes")
1158 return True
1159 else:
1160 # Either of following conditions => Failure
1161 # - Host aggregate required but not available
1162 # - Host aggregate not required but available
1163 self._log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1164 return False
1165
1166
1167 def match_image_params(self, resource_info, request_params):
1168 return True
1169
1170 def match_epa_params(self, resource_info, request_params):
1171 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1172 getattr(resource_info, 'vm_flavor'))
1173 if result == False:
1174 self._log.debug("VM Flavor mismatched")
1175 return False
1176
1177 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1178 getattr(resource_info, 'guest_epa'))
1179 if result == False:
1180 self._log.debug("Guest EPA mismatched")
1181 return False
1182
1183 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1184 getattr(resource_info, 'vswitch_epa'))
1185 if result == False:
1186 self._log.debug("Vswitch EPA mismatched")
1187 return False
1188
1189 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1190 getattr(resource_info, 'hypervisor_epa'))
1191 if result == False:
1192 self._log.debug("Hypervisor EPA mismatched")
1193 return False
1194
1195 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1196 getattr(resource_info, 'host_epa'))
1197 if result == False:
1198 self._log.debug("Host EPA mismatched")
1199 return False
1200
1201 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1202 getattr(resource_info, 'host_aggregate'))
1203
1204 if result == False:
1205 self._log.debug("Host Aggregate mismatched")
1206 return False
1207
1208 return True
1209
1210 @asyncio.coroutine
1211 def initialize_resource_in_cal(self, resource, request):
1212 self._log.info("Initializing the compute-resource with id: %s in RW.CAL", resource.resource_id)
1213 modify_params = RwcalYang.VDUModifyParams()
1214 modify_params.vdu_id = resource.resource_id
1215 modify_params.image_id = request.image_id
1216
1217 for c_point in request.connection_points:
1218 self._log.debug("Adding connection point for VDU: %s to virtual-compute with id: %s Connection point Name: %s",
1219 request.name,resource.resource_id,c_point.name)
1220 point = modify_params.connection_points_add.add()
1221 point.name = c_point.name
1222 point.virtual_link_id = c_point.virtual_link_id
1223 yield from self._cal.modify_virtual_compute(modify_params)
1224
1225 @asyncio.coroutine
1226 def uninitialize_resource_in_cal(self, resource):
1227 self._log.info("Un-initializing the compute-resource with id: %s in RW.CAL", resource.resource_id)
1228 modify_params = RwcalYang.VDUModifyParams()
1229 modify_params.vdu_id = resource.resource_id
1230 resource_info = yield from self.get_resource_info(resource)
1231 for c_point in resource_info.connection_points:
1232 self._log.debug("Removing connection point: %s from VDU: %s ",
1233 c_point.name,resource_info.name)
1234 point = modify_params.connection_points_remove.add()
1235 point.connection_point_id = c_point.connection_point_id
1236 yield from self._cal.modify_virtual_compute(modify_params)
1237
1238
1239 class ResourceMgrCore(object):
1240 def __init__(self, dts, log, log_hdl, loop, parent):
1241 self._log = log
1242 self._log_hdl = log_hdl
1243 self._dts = dts
1244 self._loop = loop
1245 self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
1246 self._parent = parent
1247 self._cloud_cals = {}
1248 # Dictionary of pool objects keyed by name
1249 self._cloud_pool_table = {}
1250 # Dictionary of tuples (resource_id, cloud_account_name, pool_name) keyed by event_id
1251 self._resource_table = {}
1252 self._pool_class = {'compute': ComputePool,
1253 'network': NetworkPool}
1254
1255 def _get_cloud_pool_table(self, cloud_account_name):
1256 if cloud_account_name not in self._cloud_pool_table:
1257 msg = "Cloud account %s not found" % cloud_account_name
1258 self._log.error(msg)
1259 raise ResMgrCloudAccountNotFound(msg)
1260
1261 return self._cloud_pool_table[cloud_account_name]
1262
1263 def _get_cloud_cal_plugin(self, cloud_account_name):
1264 if cloud_account_name not in self._cloud_cals:
1265 msg = "Cloud account %s not found" % cloud_account_name
1266 self._log.error(msg)
1267 raise ResMgrCloudAccountNotFound(msg)
1268
1269 return self._cloud_cals[cloud_account_name]
1270
1271 def _add_default_cloud_pools(self, cloud_account_name):
1272 self._log.debug("Adding default compute and network pools for cloud account %s",
1273 cloud_account_name)
1274 default_pools = [
1275 {
1276 'name': '____default_compute_pool',
1277 'resource_type': 'compute',
1278 'pool_type': 'dynamic',
1279 'max_size': 128,
1280 },
1281 {
1282 'name': '____default_network_pool',
1283 'resource_type': 'network',
1284 'pool_type': 'dynamic',
1285 'max_size': 128,
1286 },
1287 ]
1288
1289 for pool_dict in default_pools:
1290 pool_info = ResourcePoolInfo.from_dict(pool_dict)
1291 self._log.info("Applying configuration for cloud account %s pool: %s",
1292 cloud_account_name, pool_info.name)
1293
1294 self.add_resource_pool(cloud_account_name, pool_info)
1295 self.unlock_resource_pool(cloud_account_name, pool_info.name)
1296
1297 def get_cloud_account_names(self):
1298 """ Returns a list of configured cloud account names """
1299 return self._cloud_cals.keys()
1300
1301 def add_cloud_account(self, account):
1302 self._log.debug("Received CAL account. Account Name: %s, Account Type: %s",
1303 account.name, account.account_type)
1304
1305 ### Add cal handler to all the pools
1306 if account.name in self._cloud_cals:
1307 raise ResMgrCloudAccountExists("Cloud account already exists in res mgr: %s",
1308 account.name)
1309
1310 self._cloud_pool_table[account.name] = {}
1311
1312 cal = ResourceMgrCALHandler(self._loop, self._executor, self._log, self._log_hdl, account)
1313 self._cloud_cals[account.name] = cal
1314
1315 self._add_default_cloud_pools(account.name)
1316
1317 def update_cloud_account(self, account):
1318 raise NotImplementedError("Update cloud account not implemented")
1319
1320 def delete_cloud_account(self, account_name, dry_run=False):
1321 cloud_pool_table = self._get_cloud_pool_table(account_name)
1322 for pool in cloud_pool_table.values():
1323 if pool.in_use():
1324 raise ResMgrCloudAccountInUse("Cannot delete cloud which is currently in use")
1325
1326 # If dry_run is specified, do not actually delete the cloud account
1327 if dry_run:
1328 return
1329
1330 for pool in list(cloud_pool_table):
1331 self.delete_resource_pool(account_name, pool)
1332
1333 del self._cloud_pool_table[account_name]
1334 del self._cloud_cals[account_name]
1335
1336 def add_resource_pool(self, cloud_account_name, pool_info):
1337 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1338 if pool_info.name in cloud_pool_table:
1339 raise ResMgrDuplicatePool("Pool with name: %s already exists", pool_info.name)
1340
1341 cloud_cal = self._get_cloud_cal_plugin(cloud_account_name)
1342 pool = self._pool_class[pool_info.resource_type](self._log, self._loop, pool_info, cloud_cal)
1343
1344 cloud_pool_table[pool_info.name] = pool
1345
1346 def delete_resource_pool(self, cloud_account_name, pool_name):
1347 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1348 if pool_name not in cloud_pool_table:
1349 self._log.error("Pool: %s not found for deletion", pool_name)
1350 return
1351 pool = cloud_pool_table[pool_name]
1352
1353 if pool.in_use():
1354 # Can't delete a pool in use
1355 self._log.error("Pool: %s in use. Can not delete in-use pool", pool.name)
1356 return
1357
1358 pool.cleanup()
1359 del cloud_pool_table[pool_name]
1360 self._log.info("Resource Pool: %s successfully deleted", pool_name)
1361
1362 def modify_resource_pool(self, cloud_account_name, pool):
1363 pass
1364
1365 def lock_resource_pool(self, cloud_account_name, pool_name):
1366 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1367 if pool_name not in cloud_pool_table:
1368 self._log.info("Pool: %s is not available for lock operation")
1369 return
1370
1371 pool = cloud_pool_table[pool_name]
1372 pool.lock_pool()
1373
1374 def unlock_resource_pool(self, cloud_account_name, pool_name):
1375 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1376 if pool_name not in cloud_pool_table:
1377 self._log.info("Pool: %s is not available for unlock operation")
1378 return
1379
1380 pool = cloud_pool_table[pool_name]
1381 pool.unlock_pool()
1382
1383 def get_resource_pool_info(self, cloud_account_name, pool_name):
1384 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1385 if pool_name in cloud_pool_table:
1386 pool = cloud_pool_table[pool_name]
1387 return pool.get_pool_info()
1388 else:
1389 return None
1390
1391 def get_resource_pool_list(self, cloud_account_name):
1392 return [v for _, v in self._get_cloud_pool_table(cloud_account_name).items()]
1393
1394 def _select_resource_pools(self, cloud_account_name, resource_type):
1395 pools = [pool for pool in self.get_resource_pool_list(cloud_account_name) if pool.resource_type == resource_type and pool.status == 'unlocked']
1396 if not pools:
1397 raise ResMgrPoolNotAvailable("No %s pool found for resource allocation", resource_type)
1398
1399 return pools[0]
1400
1401 @asyncio.coroutine
1402 def allocate_virtual_resource(self, event_id, cloud_account_name, request, resource_type):
1403 ### Check if event_id is unique or already in use
1404 if event_id in self._resource_table:
1405 r_id, cloud_account_name, pool_name = self._resource_table[event_id]
1406 self._log.warning("Requested event-id :%s for resource-allocation already active with pool: %s",
1407 event_id, pool_name)
1408 # If resource-type matches then return the same resource
1409 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1410 pool = cloud_pool_table[pool_name]
1411 if pool.resource_type == resource_type:
1412
1413 info = yield from pool.read_resource_info(r_id)
1414 return info
1415 else:
1416 self._log.error("Event-id conflict. Duplicate event-id: %s", event_id)
1417 raise ResMgrDuplicateEventId("Requested event-id :%s already active with pool: %s" %(event_id, pool_name))
1418
1419 ### All-OK, lets go ahead with resource allocation
1420 pool = self._select_resource_pools(cloud_account_name, resource_type)
1421 self._log.info("Selected pool %s for resource allocation", pool.name)
1422
1423 r_id, r_info = yield from pool.allocate_resource(request)
1424
1425 self._resource_table[event_id] = (r_id, cloud_account_name, pool.name)
1426 return r_info
1427
1428 @asyncio.coroutine
1429 def reallocate_virtual_resource(self, event_id, cloud_account_name, request, resource_type, resource):
1430 ### Check if event_id is unique or already in use
1431 if event_id in self._resource_table:
1432 r_id, cloud_account_name, pool_name = self._resource_table[event_id]
1433 self._log.warning("Requested event-id :%s for resource-allocation already active with pool: %s",
1434 event_id, pool_name)
1435 # If resource-type matches then return the same resource
1436 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1437 pool = cloud_pool_table[pool_name]
1438 if pool.resource_type == resource_type:
1439 info = yield from pool.read_resource_info(r_id)
1440 return info
1441 else:
1442 self._log.error("Event-id conflict. Duplicate event-id: %s", event_id)
1443 raise ResMgrDuplicateEventId("Requested event-id :%s already active with pool: %s" %(event_id, pool_name))
1444
1445 r_info = None
1446 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1447 pool = cloud_pool_table[resource.pool_name]
1448 if pool.resource_type == resource_type:
1449 if resource_type == 'network':
1450 r_id = resource.virtual_link_id
1451 r_info = yield from pool.get_info_by_id(resource.virtual_link_id)
1452 elif resource_type == 'compute':
1453 r_id = resource.vdu_id
1454 r_info = yield from pool.get_info_by_id(resource.vdu_id)
1455
1456 if r_info is None:
1457 r_id, r_info = yield from pool.allocate_resource(request)
1458 self._resource_table[event_id] = (r_id, cloud_account_name, resource.pool_name)
1459 return r_info
1460
1461 self._resource_table[event_id] = (r_id, cloud_account_name, resource.pool_name)
1462 new_resource = pool._resource_class(r_id, 'dynamic', request)
1463 if resource_type == 'compute':
1464 requested_params = RwcalYang.VDUInitParams()
1465 requested_params.from_dict(request.as_dict())
1466 new_resource.requested_params = requested_params
1467 pool._all_resources[r_id] = new_resource
1468 pool._allocated_resources[r_id] = new_resource
1469 return r_info
1470
1471 @asyncio.coroutine
1472 def release_virtual_resource(self, event_id, resource_type):
1473 ### Check if event_id exists
1474 if event_id not in self._resource_table:
1475 self._log.error("Received resource-release-request with unknown Event-id :%s", event_id)
1476 raise ResMgrUnknownEventId("Received resource-release-request with unknown Event-id :%s" %(event_id))
1477
1478 ## All-OK, lets proceed with resource release
1479 r_id, cloud_account_name, pool_name = self._resource_table.pop(event_id)
1480 self._log.debug("Attempting to release virtual resource id %s from pool %s",
1481 r_id, pool_name)
1482
1483 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1484 pool = cloud_pool_table[pool_name]
1485 yield from pool.release_resource(r_id)
1486
1487 @asyncio.coroutine
1488 def read_virtual_resource(self, event_id, resource_type):
1489 ### Check if event_id exists
1490 if event_id not in self._resource_table:
1491 self._log.error("Received resource-read-request with unknown Event-id :%s", event_id)
1492 raise ResMgrUnknownEventId("Received resource-read-request with unknown Event-id :%s" %(event_id))
1493
1494 ## All-OK, lets proceed
1495 r_id, cloud_account_name, pool_name = self._resource_table[event_id]
1496 cloud_pool_table = self._get_cloud_pool_table(cloud_account_name)
1497 pool = cloud_pool_table[pool_name]
1498 info = yield from pool.read_resource_info(r_id)
1499 return info