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