936db92c24e9f616998a1a4b520cdf13afefb46c
[osm/SO.git] / rwcal / plugins / vala / rwcal_openstack / rwcal_openstack.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 import contextlib
19 import logging
20 import os
21 import subprocess
22 import uuid
23
24 import rift.rwcal.openstack as openstack_drv
25 import rw_status
26 import rift.cal.rwcal_status as rwcal_status
27 import rwlogger
28 import neutronclient.common.exceptions as NeutronException
29 import keystoneclient.exceptions as KeystoneExceptions
30
31 from gi.repository import (
32 GObject,
33 RwCal,
34 RwTypes,
35 RwcalYang)
36
37 PREPARE_VM_CMD = "prepare_vm.py --auth_url {auth_url} --username {username} --password {password} --tenant_name {tenant_name} --mgmt_network {mgmt_network} --server_id {server_id} --port_metadata"
38
39 rwstatus_exception_map = { IndexError: RwTypes.RwStatus.NOTFOUND,
40 KeyError: RwTypes.RwStatus.NOTFOUND,
41 NotImplementedError: RwTypes.RwStatus.NOT_IMPLEMENTED,}
42
43 rwstatus = rw_status.rwstatus_from_exc_map(rwstatus_exception_map)
44 rwcalstatus = rwcal_status.rwcalstatus_from_exc_map(rwstatus_exception_map)
45
46
47 espec_utils = openstack_drv.OpenstackExtraSpecUtils()
48
49 class OpenstackCALOperationFailure(Exception):
50 pass
51
52 class UninitializedPluginError(Exception):
53 pass
54
55
56 class OpenstackServerGroupError(Exception):
57 pass
58
59
60 class ImageUploadError(Exception):
61 pass
62
63
64 class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud):
65 """This class implements the CAL VALA methods for openstack."""
66
67 instance_num = 1
68
69 def __init__(self):
70 GObject.Object.__init__(self)
71 self._driver_class = openstack_drv.OpenstackDriver
72 self.log = logging.getLogger('rwcal.openstack.%s' % RwcalOpenstackPlugin.instance_num)
73 self.log.setLevel(logging.DEBUG)
74
75 self._rwlog_handler = None
76 RwcalOpenstackPlugin.instance_num += 1
77
78
79 @contextlib.contextmanager
80 def _use_driver(self, account):
81 if self._rwlog_handler is None:
82 raise UninitializedPluginError("Must call init() in CAL plugin before use.")
83
84 with rwlogger.rwlog_root_handler(self._rwlog_handler):
85 try:
86 drv = self._driver_class(username = account.openstack.key,
87 password = account.openstack.secret,
88 auth_url = account.openstack.auth_url,
89 tenant_name = account.openstack.tenant,
90 mgmt_network = account.openstack.mgmt_network,
91 cert_validate = account.openstack.cert_validate )
92 except (KeystoneExceptions.Unauthorized, KeystoneExceptions.AuthorizationFailure,
93 NeutronException.NotFound) as e:
94 raise
95 except Exception as e:
96 self.log.error("RwcalOpenstackPlugin: OpenstackDriver init failed. Exception: %s" %(str(e)))
97 raise
98
99 yield drv
100
101
102 @rwstatus
103 def do_init(self, rwlog_ctx):
104 self._rwlog_handler = rwlogger.RwLogger(
105 category="rw-cal-log",
106 subcategory="openstack",
107 log_hdl=rwlog_ctx,
108 )
109 self.log.addHandler(self._rwlog_handler)
110 self.log.propagate = False
111
112 @rwstatus(ret_on_failure=[None])
113 def do_validate_cloud_creds(self, account):
114 """
115 Validates the cloud account credentials for the specified account.
116 Performs an access to the resources using Keystone API. If creds
117 are not valid, returns an error code & reason string
118 Arguments:
119 account - a cloud account to validate
120
121 Returns:
122 Validation Code and Details String
123 """
124 status = RwcalYang.CloudConnectionStatus()
125 try:
126 with self._use_driver(account) as drv:
127 drv.validate_account_creds()
128
129 except KeystoneExceptions.Unauthorized as e:
130 self.log.error("Invalid credentials given for VIM account %s" %account.name)
131 status.status = "failure"
132 status.details = "Invalid Credentials: %s" % str(e)
133
134 except KeystoneExceptions.AuthorizationFailure as e:
135 self.log.error("Bad authentication URL given for VIM account %s. Given auth url: %s" % (
136 account.name, account.openstack.auth_url))
137 status.status = "failure"
138 status.details = "Invalid auth url: %s" % str(e)
139
140 except NeutronException.NotFound as e:
141 self.log.error("Given management network %s could not be found for VIM account %s" % (
142 account.openstack.mgmt_network, account.name))
143 status.status = "failure"
144 status.details = "mgmt network does not exist: %s" % str(e)
145
146 except openstack_drv.ValidationError as e:
147 self.log.error("RwcalOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e))
148 status.status = "failure"
149 status.details = "Invalid Credentials: %s" % str(e)
150
151 except Exception as e:
152 msg = "RwcalOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e))
153 self.log.error(msg)
154 status.status = "failure"
155 status.details = msg
156
157 else:
158 status.status = "success"
159 status.details = "Connection was successful"
160
161 return status
162
163 @rwstatus(ret_on_failure=[""])
164 def do_get_management_network(self, account):
165 """
166 Returns the management network associated with the specified account.
167 Arguments:
168 account - a cloud account
169
170 Returns:
171 The management network
172 """
173 return account.openstack.mgmt_network
174
175 @rwstatus(ret_on_failure=[""])
176 def do_create_tenant(self, account, name):
177 """Create a new tenant.
178
179 Arguments:
180 account - a cloud account
181 name - name of the tenant
182
183 Returns:
184 The tenant id
185 """
186 raise NotImplementedError
187
188 @rwstatus
189 def do_delete_tenant(self, account, tenant_id):
190 """delete a tenant.
191
192 Arguments:
193 account - a cloud account
194 tenant_id - id of the tenant
195 """
196 raise NotImplementedError
197
198 @rwstatus(ret_on_failure=[[]])
199 def do_get_tenant_list(self, account):
200 """List tenants.
201
202 Arguments:
203 account - a cloud account
204
205 Returns:
206 List of tenants
207 """
208 raise NotImplementedError
209
210 @rwstatus(ret_on_failure=[""])
211 def do_create_role(self, account, name):
212 """Create a new user.
213
214 Arguments:
215 account - a cloud account
216 name - name of the user
217
218 Returns:
219 The user id
220 """
221 raise NotImplementedError
222
223 @rwstatus
224 def do_delete_role(self, account, role_id):
225 """Delete a user.
226
227 Arguments:
228 account - a cloud account
229 role_id - id of the user
230 """
231 raise NotImplementedError
232
233 @rwstatus(ret_on_failure=[[]])
234 def do_get_role_list(self, account):
235 """List roles.
236
237 Arguments:
238 account - a cloud account
239
240 Returns:
241 List of roles
242 """
243 raise NotImplementedError
244
245 @rwstatus(ret_on_failure=[""])
246 def do_create_image(self, account, image):
247 """Create an image
248
249 Arguments:
250 account - a cloud account
251 image - a description of the image to create
252
253 Returns:
254 The image id
255 """
256
257 try:
258 # If the use passed in a file descriptor, use that to
259 # upload the image.
260 if image.has_field("fileno"):
261 new_fileno = os.dup(image.fileno)
262 hdl = os.fdopen(new_fileno, 'rb')
263 else:
264 hdl = open(image.location, "rb")
265 except Exception as e:
266 self.log.error("Could not open file for upload. Exception received: %s", str(e))
267 raise
268
269 with hdl as fd:
270 kwargs = {}
271 kwargs['name'] = image.name
272
273 if image.disk_format:
274 kwargs['disk_format'] = image.disk_format
275 if image.container_format:
276 kwargs['container_format'] = image.container_format
277
278 with self._use_driver(account) as drv:
279 # Create Image
280 image_id = drv.glance_image_create(**kwargs)
281 # Upload the Image
282 drv.glance_image_upload(image_id, fd)
283
284 if image.checksum:
285 stored_image = drv.glance_image_get(image_id)
286 if stored_image.checksum != image.checksum:
287 drv.glance_image_delete(image_id=image_id)
288 raise ImageUploadError(
289 "image checksum did not match (actual: %s, expected: %s). Deleting." %
290 (stored_image.checksum, image.checksum)
291 )
292
293 return image_id
294
295 @rwstatus
296 def do_delete_image(self, account, image_id):
297 """Delete a vm image.
298
299 Arguments:
300 account - a cloud account
301 image_id - id of the image to delete
302 """
303 with self._use_driver(account) as drv:
304 drv.glance_image_delete(image_id=image_id)
305
306
307 @staticmethod
308 def _fill_image_info(img_info):
309 """Create a GI object from image info dictionary
310
311 Converts image information dictionary object returned by openstack
312 driver into Protobuf Gi Object
313
314 Arguments:
315 account - a cloud account
316 img_info - image information dictionary object from openstack
317
318 Returns:
319 The ImageInfoItem
320 """
321 img = RwcalYang.ImageInfoItem()
322 img.name = img_info['name']
323 img.id = img_info['id']
324 img.checksum = img_info['checksum']
325 img.disk_format = img_info['disk_format']
326 img.container_format = img_info['container_format']
327 if img_info['status'] == 'active':
328 img.state = 'active'
329 else:
330 img.state = 'inactive'
331 return img
332
333 @rwstatus(ret_on_failure=[[]])
334 def do_get_image_list(self, account):
335 """Return a list of the names of all available images.
336
337 Arguments:
338 account - a cloud account
339
340 Returns:
341 The the list of images in VimResources object
342 """
343 response = RwcalYang.VimResources()
344 with self._use_driver(account) as drv:
345 images = drv.glance_image_list()
346 for img in images:
347 response.imageinfo_list.append(RwcalOpenstackPlugin._fill_image_info(img))
348 return response
349
350 @rwstatus(ret_on_failure=[None])
351 def do_get_image(self, account, image_id):
352 """Return a image information.
353
354 Arguments:
355 account - a cloud account
356 image_id - an id of the image
357
358 Returns:
359 ImageInfoItem object containing image information.
360 """
361 with self._use_driver(account) as drv:
362 image = drv.glance_image_get(image_id)
363 return RwcalOpenstackPlugin._fill_image_info(image)
364
365 @rwstatus(ret_on_failure=[""])
366 def do_create_vm(self, account, vminfo):
367 """Create a new virtual machine.
368
369 Arguments:
370 account - a cloud account
371 vminfo - information that defines the type of VM to create
372
373 Returns:
374 The image id
375 """
376 kwargs = {}
377 kwargs['name'] = vminfo.vm_name
378 kwargs['flavor_id'] = vminfo.flavor_id
379 if vminfo.has_field('image_id'):
380 kwargs['image_id'] = vminfo.image_id
381
382 with self._use_driver(account) as drv:
383 ### If floating_ip is required and we don't have one, better fail before any further allocation
384 if vminfo.has_field('allocate_public_address') and vminfo.allocate_public_address:
385 if account.openstack.has_field('floating_ip_pool'):
386 pool_name = account.openstack.floating_ip_pool
387 else:
388 pool_name = None
389 floating_ip = self._allocate_floating_ip(drv, pool_name)
390 else:
391 floating_ip = None
392
393 if vminfo.has_field('cloud_init') and vminfo.cloud_init.has_field('userdata'):
394 kwargs['userdata'] = vminfo.cloud_init.userdata
395 else:
396 kwargs['userdata'] = ''
397
398 if account.openstack.security_groups:
399 kwargs['security_groups'] = account.openstack.security_groups
400
401 port_list = []
402 for port in vminfo.port_list:
403 port_list.append(port.port_id)
404
405 if port_list:
406 kwargs['port_list'] = port_list
407
408 network_list = []
409 for network in vminfo.network_list:
410 network_list.append(network.network_id)
411
412 if network_list:
413 kwargs['network_list'] = network_list
414
415 metadata = {}
416 for field in vminfo.user_tags.fields:
417 if vminfo.user_tags.has_field(field):
418 metadata[field] = getattr(vminfo.user_tags, field)
419 kwargs['metadata'] = metadata
420
421 if vminfo.has_field('availability_zone'):
422 kwargs['availability_zone'] = vminfo.availability_zone
423 else:
424 kwargs['availability_zone'] = None
425
426 if vminfo.has_field('server_group'):
427 kwargs['scheduler_hints'] = {'group': vminfo.server_group }
428 else:
429 kwargs['scheduler_hints'] = None
430
431 with self._use_driver(account) as drv:
432 vm_id = drv.nova_server_create(**kwargs)
433 if floating_ip:
434 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
435
436 return vm_id
437
438 @rwstatus
439 def do_start_vm(self, account, vm_id):
440 """Start an existing virtual machine.
441
442 Arguments:
443 account - a cloud account
444 vm_id - an id of the VM
445 """
446 with self._use_driver(account) as drv:
447 drv.nova_server_start(vm_id)
448
449 @rwstatus
450 def do_stop_vm(self, account, vm_id):
451 """Stop a running virtual machine.
452
453 Arguments:
454 account - a cloud account
455 vm_id - an id of the VM
456 """
457 with self._use_driver(account) as drv:
458 drv.nova_server_stop(vm_id)
459
460 @rwstatus
461 def do_delete_vm(self, account, vm_id):
462 """Delete a virtual machine.
463
464 Arguments:
465 account - a cloud account
466 vm_id - an id of the VM
467 """
468 with self._use_driver(account) as drv:
469 drv.nova_server_delete(vm_id)
470
471 @rwstatus
472 def do_reboot_vm(self, account, vm_id):
473 """Reboot a virtual machine.
474
475 Arguments:
476 account - a cloud account
477 vm_id - an id of the VM
478 """
479 with self._use_driver(account) as drv:
480 drv.nova_server_reboot(vm_id)
481
482 @staticmethod
483 def _fill_vm_info(vm_info, mgmt_network):
484 """Create a GI object from vm info dictionary
485
486 Converts VM information dictionary object returned by openstack
487 driver into Protobuf Gi Object
488
489 Arguments:
490 vm_info - VM information from openstack
491 mgmt_network - Management network
492
493 Returns:
494 Protobuf Gi object for VM
495 """
496 vm = RwcalYang.VMInfoItem()
497 vm.vm_id = vm_info['id']
498 vm.vm_name = vm_info['name']
499 vm.image_id = vm_info['image']['id']
500 vm.flavor_id = vm_info['flavor']['id']
501 vm.state = vm_info['status']
502 for network_name, network_info in vm_info['addresses'].items():
503 if network_info:
504 if network_name == mgmt_network:
505 vm.public_ip = next((item['addr']
506 for item in network_info
507 if item['OS-EXT-IPS:type'] == 'floating'),
508 network_info[0]['addr'])
509 vm.management_ip = network_info[0]['addr']
510 else:
511 for interface in network_info:
512 addr = vm.private_ip_list.add()
513 addr.ip_address = interface['addr']
514
515 for network_name, network_info in vm_info['addresses'].items():
516 if network_info and network_name == mgmt_network and not vm.public_ip:
517 for interface in network_info:
518 if 'OS-EXT-IPS:type' in interface and interface['OS-EXT-IPS:type'] == 'floating':
519 vm.public_ip = interface['addr']
520
521 # Look for any metadata
522 for key, value in vm_info['metadata'].items():
523 if key in vm.user_tags.fields:
524 setattr(vm.user_tags, key, value)
525 if 'OS-EXT-SRV-ATTR:host' in vm_info:
526 if vm_info['OS-EXT-SRV-ATTR:host'] != None:
527 vm.host_name = vm_info['OS-EXT-SRV-ATTR:host']
528 if 'OS-EXT-AZ:availability_zone' in vm_info:
529 if vm_info['OS-EXT-AZ:availability_zone'] != None:
530 vm.availability_zone = vm_info['OS-EXT-AZ:availability_zone']
531 return vm
532
533 @rwstatus(ret_on_failure=[[]])
534 def do_get_vm_list(self, account):
535 """Return a list of the VMs as vala boxed objects
536
537 Arguments:
538 account - a cloud account
539
540 Returns:
541 List containing VM information
542 """
543 response = RwcalYang.VimResources()
544 with self._use_driver(account) as drv:
545 vms = drv.nova_server_list()
546 for vm in vms:
547 response.vminfo_list.append(RwcalOpenstackPlugin._fill_vm_info(vm, account.openstack.mgmt_network))
548 return response
549
550 @rwstatus(ret_on_failure=[None])
551 def do_get_vm(self, account, id):
552 """Return vm information.
553
554 Arguments:
555 account - a cloud account
556 id - an id for the VM
557
558 Returns:
559 VM information
560 """
561 with self._use_driver(account) as drv:
562 vm = drv.nova_server_get(id)
563 return RwcalOpenstackPlugin._fill_vm_info(vm, account.openstack.mgmt_network)
564
565 @staticmethod
566 def _get_guest_epa_specs(guest_epa):
567 """
568 Returns EPA Specs dictionary for guest_epa attributes
569 """
570 epa_specs = {}
571 if guest_epa.has_field('mempage_size'):
572 mempage_size = espec_utils.guest.mano_to_extra_spec_mempage_size(guest_epa.mempage_size)
573 if mempage_size is not None:
574 epa_specs['hw:mem_page_size'] = mempage_size
575
576 if guest_epa.has_field('cpu_pinning_policy'):
577 cpu_pinning_policy = espec_utils.guest.mano_to_extra_spec_cpu_pinning_policy(guest_epa.cpu_pinning_policy)
578 if cpu_pinning_policy is not None:
579 epa_specs['hw:cpu_policy'] = cpu_pinning_policy
580
581 if guest_epa.has_field('cpu_thread_pinning_policy'):
582 cpu_thread_pinning_policy = espec_utils.guest.mano_to_extra_spec_cpu_thread_pinning_policy(guest_epa.cpu_thread_pinning_policy)
583 if cpu_thread_pinning_policy is None:
584 epa_specs['hw:cpu_threads_policy'] = cpu_thread_pinning_policy
585
586 if guest_epa.has_field('trusted_execution'):
587 trusted_execution = espec_utils.guest.mano_to_extra_spec_trusted_execution(guest_epa.trusted_execution)
588 if trusted_execution is not None:
589 epa_specs['trust:trusted_host'] = trusted_execution
590
591 if guest_epa.has_field('numa_node_policy'):
592 if guest_epa.numa_node_policy.has_field('node_cnt'):
593 numa_node_count = espec_utils.guest.mano_to_extra_spec_numa_node_count(guest_epa.numa_node_policy.node_cnt)
594 if numa_node_count is not None:
595 epa_specs['hw:numa_nodes'] = numa_node_count
596
597 if guest_epa.numa_node_policy.has_field('mem_policy'):
598 numa_memory_policy = espec_utils.guest.mano_to_extra_spec_numa_memory_policy(guest_epa.numa_node_policy.mem_policy)
599 if numa_memory_policy is not None:
600 epa_specs['hw:numa_mempolicy'] = numa_memory_policy
601
602 if guest_epa.numa_node_policy.has_field('node'):
603 for node in guest_epa.numa_node_policy.node:
604 if node.has_field('vcpu') and node.vcpu:
605 epa_specs['hw:numa_cpus.'+str(node.id)] = ','.join([str(j.id) for j in node.vcpu])
606 if node.memory_mb:
607 epa_specs['hw:numa_mem.'+str(node.id)] = str(node.memory_mb)
608
609 if guest_epa.has_field('pcie_device'):
610 pci_devices = []
611 for device in guest_epa.pcie_device:
612 pci_devices.append(device.device_id +':'+str(device.count))
613 epa_specs['pci_passthrough:alias'] = ','.join(pci_devices)
614
615 return epa_specs
616
617 @staticmethod
618 def _get_host_epa_specs(host_epa):
619 """
620 Returns EPA Specs dictionary for host_epa attributes
621 """
622
623 epa_specs = {}
624
625 if host_epa.has_field('cpu_model'):
626 cpu_model = espec_utils.host.mano_to_extra_spec_cpu_model(host_epa.cpu_model)
627 if cpu_model is not None:
628 epa_specs['capabilities:cpu_info:model'] = cpu_model
629
630 if host_epa.has_field('cpu_arch'):
631 cpu_arch = espec_utils.host.mano_to_extra_spec_cpu_arch(host_epa.cpu_arch)
632 if cpu_arch is not None:
633 epa_specs['capabilities:cpu_info:arch'] = cpu_arch
634
635 if host_epa.has_field('cpu_vendor'):
636 cpu_vendor = espec_utils.host.mano_to_extra_spec_cpu_vendor(host_epa.cpu_vendor)
637 if cpu_vendor is not None:
638 epa_specs['capabilities:cpu_info:vendor'] = cpu_vendor
639
640 if host_epa.has_field('cpu_socket_count'):
641 cpu_socket_count = espec_utils.host.mano_to_extra_spec_cpu_socket_count(host_epa.cpu_socket_count)
642 if cpu_socket_count is not None:
643 epa_specs['capabilities:cpu_info:topology:sockets'] = cpu_socket_count
644
645 if host_epa.has_field('cpu_core_count'):
646 cpu_core_count = espec_utils.host.mano_to_extra_spec_cpu_core_count(host_epa.cpu_core_count)
647 if cpu_core_count is not None:
648 epa_specs['capabilities:cpu_info:topology:cores'] = cpu_core_count
649
650 if host_epa.has_field('cpu_core_thread_count'):
651 cpu_core_thread_count = espec_utils.host.mano_to_extra_spec_cpu_core_thread_count(host_epa.cpu_core_thread_count)
652 if cpu_core_thread_count is not None:
653 epa_specs['capabilities:cpu_info:topology:threads'] = cpu_core_thread_count
654
655 if host_epa.has_field('cpu_feature'):
656 cpu_features = []
657 espec_cpu_features = []
658 for feature in host_epa.cpu_feature:
659 cpu_features.append(feature.feature)
660 espec_cpu_features = espec_utils.host.mano_to_extra_spec_cpu_features(cpu_features)
661 if espec_cpu_features is not None:
662 epa_specs['capabilities:cpu_info:features'] = espec_cpu_features
663 return epa_specs
664
665 @staticmethod
666 def _get_hypervisor_epa_specs(guest_epa):
667 """
668 Returns EPA Specs dictionary for hypervisor_epa attributes
669 """
670 hypervisor_epa = {}
671 return hypervisor_epa
672
673 @staticmethod
674 def _get_vswitch_epa_specs(guest_epa):
675 """
676 Returns EPA Specs dictionary for vswitch_epa attributes
677 """
678 vswitch_epa = {}
679 return vswitch_epa
680
681 @staticmethod
682 def _get_host_aggregate_epa_specs(host_aggregate):
683 """
684 Returns EPA Specs dictionary for host aggregates
685 """
686 epa_specs = {}
687 for aggregate in host_aggregate:
688 epa_specs['aggregate_instance_extra_specs:'+aggregate.metadata_key] = aggregate.metadata_value
689
690 return epa_specs
691
692 @staticmethod
693 def _get_epa_specs(flavor):
694 """
695 Returns epa_specs dictionary based on flavor information
696 """
697 epa_specs = {}
698 if flavor.has_field('guest_epa'):
699 guest_epa = RwcalOpenstackPlugin._get_guest_epa_specs(flavor.guest_epa)
700 epa_specs.update(guest_epa)
701 if flavor.has_field('host_epa'):
702 host_epa = RwcalOpenstackPlugin._get_host_epa_specs(flavor.host_epa)
703 epa_specs.update(host_epa)
704 if flavor.has_field('hypervisor_epa'):
705 hypervisor_epa = RwcalOpenstackPlugin._get_hypervisor_epa_specs(flavor.hypervisor_epa)
706 epa_specs.update(hypervisor_epa)
707 if flavor.has_field('vswitch_epa'):
708 vswitch_epa = RwcalOpenstackPlugin._get_vswitch_epa_specs(flavor.vswitch_epa)
709 epa_specs.update(vswitch_epa)
710 if flavor.has_field('host_aggregate'):
711 host_aggregate = RwcalOpenstackPlugin._get_host_aggregate_epa_specs(flavor.host_aggregate)
712 epa_specs.update(host_aggregate)
713 return epa_specs
714
715 @rwstatus(ret_on_failure=[""])
716 def do_create_flavor(self, account, flavor):
717 """Create new flavor.
718
719 Arguments:
720 account - a cloud account
721 flavor - flavor of the VM
722
723 Returns:
724 flavor id
725 """
726 epa_specs = RwcalOpenstackPlugin._get_epa_specs(flavor)
727 with self._use_driver(account) as drv:
728 return drv.nova_flavor_create(name = flavor.name,
729 ram = flavor.vm_flavor.memory_mb,
730 vcpus = flavor.vm_flavor.vcpu_count,
731 disk = flavor.vm_flavor.storage_gb,
732 epa_specs = epa_specs)
733
734
735 @rwstatus
736 def do_delete_flavor(self, account, flavor_id):
737 """Delete flavor.
738
739 Arguments:
740 account - a cloud account
741 flavor_id - id flavor of the VM
742 """
743 with self._use_driver(account) as drv:
744 drv.nova_flavor_delete(flavor_id)
745
746 @staticmethod
747 def _fill_epa_attributes(flavor, flavor_info):
748 """Helper function to populate the EPA attributes
749
750 Arguments:
751 flavor : Object with EPA attributes
752 flavor_info: A dictionary of flavor_info received from openstack
753 Returns:
754 None
755 """
756 getattr(flavor, 'vm_flavor').vcpu_count = flavor_info['vcpus']
757 getattr(flavor, 'vm_flavor').memory_mb = flavor_info['ram']
758 getattr(flavor, 'vm_flavor').storage_gb = flavor_info['disk']
759
760 ### If extra_specs in flavor_info
761 if not 'extra_specs' in flavor_info:
762 return
763
764 for attr in flavor_info['extra_specs']:
765 if attr == 'hw:cpu_policy':
766 cpu_pinning_policy = espec_utils.guest.extra_spec_to_mano_cpu_pinning_policy(flavor_info['extra_specs']['hw:cpu_policy'])
767 if cpu_pinning_policy is not None:
768 getattr(flavor, 'guest_epa').cpu_pinning_policy = cpu_pinning_policy
769
770 elif attr == 'hw:cpu_threads_policy':
771 cpu_thread_pinning_policy = espec_utils.guest.extra_spec_to_mano_cpu_thread_pinning_policy(flavor_info['extra_specs']['hw:cpu_threads_policy'])
772 if cpu_thread_pinning_policy is not None:
773 getattr(flavor, 'guest_epa').cpu_thread_pinning_policy = cpu_thread_pinning_policy
774
775 elif attr == 'hw:mem_page_size':
776 mempage_size = espec_utils.guest.extra_spec_to_mano_mempage_size(flavor_info['extra_specs']['hw:mem_page_size'])
777 if mempage_size is not None:
778 getattr(flavor, 'guest_epa').mempage_size = mempage_size
779
780
781 elif attr == 'hw:numa_nodes':
782 numa_node_count = espec_utils.guest.extra_specs_to_mano_numa_node_count(flavor_info['extra_specs']['hw:numa_nodes'])
783 if numa_node_count is not None:
784 getattr(flavor,'guest_epa').numa_node_policy.node_cnt = numa_node_count
785
786 elif attr.startswith('hw:numa_cpus.'):
787 node_id = attr.split('.')[1]
788 nodes = [ n for n in flavor.guest_epa.numa_node_policy.node if n.id == int(node_id) ]
789 if nodes:
790 numa_node = nodes[0]
791 else:
792 numa_node = getattr(flavor,'guest_epa').numa_node_policy.node.add()
793 numa_node.id = int(node_id)
794
795 for x in flavor_info['extra_specs'][attr].split(','):
796 numa_node_vcpu = numa_node.vcpu.add()
797 numa_node_vcpu.id = int(x)
798
799 elif attr.startswith('hw:numa_mem.'):
800 node_id = attr.split('.')[1]
801 nodes = [ n for n in flavor.guest_epa.numa_node_policy.node if n.id == int(node_id) ]
802 if nodes:
803 numa_node = nodes[0]
804 else:
805 numa_node = getattr(flavor,'guest_epa').numa_node_policy.node.add()
806 numa_node.id = int(node_id)
807
808 numa_node.memory_mb = int(flavor_info['extra_specs'][attr])
809
810 elif attr == 'hw:numa_mempolicy':
811 numa_memory_policy = espec_utils.guest.extra_to_mano_spec_numa_memory_policy(flavor_info['extra_specs']['hw:numa_mempolicy'])
812 if numa_memory_policy is not None:
813 getattr(flavor,'guest_epa').numa_node_policy.mem_policy = numa_memory_policy
814
815 elif attr == 'trust:trusted_host':
816 trusted_execution = espec_utils.guest.extra_spec_to_mano_trusted_execution(flavor_info['extra_specs']['trust:trusted_host'])
817 if trusted_execution is not None:
818 getattr(flavor,'guest_epa').trusted_execution = trusted_execution
819
820 elif attr == 'pci_passthrough:alias':
821 device_types = flavor_info['extra_specs']['pci_passthrough:alias']
822 for device in device_types.split(','):
823 dev = getattr(flavor,'guest_epa').pcie_device.add()
824 dev.device_id = device.split(':')[0]
825 dev.count = int(device.split(':')[1])
826
827 elif attr == 'capabilities:cpu_info:model':
828 cpu_model = espec_utils.host.extra_specs_to_mano_cpu_model(flavor_info['extra_specs']['capabilities:cpu_info:model'])
829 if cpu_model is not None:
830 getattr(flavor, 'host_epa').cpu_model = cpu_model
831
832 elif attr == 'capabilities:cpu_info:arch':
833 cpu_arch = espec_utils.host.extra_specs_to_mano_cpu_arch(flavor_info['extra_specs']['capabilities:cpu_info:arch'])
834 if cpu_arch is not None:
835 getattr(flavor, 'host_epa').cpu_arch = cpu_arch
836
837 elif attr == 'capabilities:cpu_info:vendor':
838 cpu_vendor = espec_utils.host.extra_spec_to_mano_cpu_vendor(flavor_info['extra_specs']['capabilities:cpu_info:vendor'])
839 if cpu_vendor is not None:
840 getattr(flavor, 'host_epa').cpu_vendor = cpu_vendor
841
842 elif attr == 'capabilities:cpu_info:topology:sockets':
843 cpu_sockets = espec_utils.host.extra_spec_to_mano_cpu_socket_count(flavor_info['extra_specs']['capabilities:cpu_info:topology:sockets'])
844 if cpu_sockets is not None:
845 getattr(flavor, 'host_epa').cpu_socket_count = cpu_sockets
846
847 elif attr == 'capabilities:cpu_info:topology:cores':
848 cpu_cores = espec_utils.host.extra_spec_to_mano_cpu_core_count(flavor_info['extra_specs']['capabilities:cpu_info:topology:cores'])
849 if cpu_cores is not None:
850 getattr(flavor, 'host_epa').cpu_core_count = cpu_cores
851
852 elif attr == 'capabilities:cpu_info:topology:threads':
853 cpu_threads = espec_utils.host.extra_spec_to_mano_cpu_core_thread_count(flavor_info['extra_specs']['capabilities:cpu_info:topology:threads'])
854 if cpu_threads is not None:
855 getattr(flavor, 'host_epa').cpu_core_thread_count = cpu_threads
856
857 elif attr == 'capabilities:cpu_info:features':
858 cpu_features = espec_utils.host.extra_spec_to_mano_cpu_features(flavor_info['extra_specs']['capabilities:cpu_info:features'])
859 if cpu_features is not None:
860 for feature in cpu_features:
861 getattr(flavor, 'host_epa').cpu_feature.append(feature)
862 elif attr.startswith('aggregate_instance_extra_specs:'):
863 aggregate = getattr(flavor, 'host_aggregate').add()
864 aggregate.metadata_key = ":".join(attr.split(':')[1::])
865 aggregate.metadata_value = flavor_info['extra_specs'][attr]
866
867 @staticmethod
868 def _fill_flavor_info(flavor_info):
869 """Create a GI object from flavor info dictionary
870
871 Converts Flavor information dictionary object returned by openstack
872 driver into Protobuf Gi Object
873
874 Arguments:
875 flavor_info: Flavor information from openstack
876
877 Returns:
878 Object of class FlavorInfoItem
879 """
880 flavor = RwcalYang.FlavorInfoItem()
881 flavor.name = flavor_info['name']
882 flavor.id = flavor_info['id']
883 RwcalOpenstackPlugin._fill_epa_attributes(flavor, flavor_info)
884 return flavor
885
886
887 @rwstatus(ret_on_failure=[[]])
888 def do_get_flavor_list(self, account):
889 """Return flavor information.
890
891 Arguments:
892 account - a cloud account
893
894 Returns:
895 List of flavors
896 """
897 response = RwcalYang.VimResources()
898 with self._use_driver(account) as drv:
899 flavors = drv.nova_flavor_list()
900 for flv in flavors:
901 response.flavorinfo_list.append(RwcalOpenstackPlugin._fill_flavor_info(flv))
902 return response
903
904 @rwstatus(ret_on_failure=[None])
905 def do_get_flavor(self, account, id):
906 """Return flavor information.
907
908 Arguments:
909 account - a cloud account
910 id - an id for the flavor
911
912 Returns:
913 Flavor info item
914 """
915 with self._use_driver(account) as drv:
916 flavor = drv.nova_flavor_get(id)
917 return RwcalOpenstackPlugin._fill_flavor_info(flavor)
918
919
920 def _fill_network_info(self, network_info, account):
921 """Create a GI object from network info dictionary
922
923 Converts Network information dictionary object returned by openstack
924 driver into Protobuf Gi Object
925
926 Arguments:
927 network_info - Network information from openstack
928 account - a cloud account
929
930 Returns:
931 Network info item
932 """
933 network = RwcalYang.NetworkInfoItem()
934 network.network_name = network_info['name']
935 network.network_id = network_info['id']
936 if ('provider:network_type' in network_info) and (network_info['provider:network_type'] != None):
937 network.provider_network.overlay_type = network_info['provider:network_type'].upper()
938 if ('provider:segmentation_id' in network_info) and (network_info['provider:segmentation_id']):
939 network.provider_network.segmentation_id = network_info['provider:segmentation_id']
940 if ('provider:physical_network' in network_info) and (network_info['provider:physical_network']):
941 network.provider_network.physical_network = network_info['provider:physical_network'].upper()
942
943 if 'subnets' in network_info and network_info['subnets']:
944 subnet_id = network_info['subnets'][0]
945 with self._use_driver(account) as drv:
946 subnet = drv.neutron_subnet_get(subnet_id)
947 network.subnet = subnet['cidr']
948 return network
949
950 @rwstatus(ret_on_failure=[[]])
951 def do_get_network_list(self, account):
952 """Return a list of networks
953
954 Arguments:
955 account - a cloud account
956
957 Returns:
958 List of networks
959 """
960 response = RwcalYang.VimResources()
961 with self._use_driver(account) as drv:
962 networks = drv.neutron_network_list()
963 for network in networks:
964 response.networkinfo_list.append(self._fill_network_info(network, account))
965 return response
966
967 @rwstatus(ret_on_failure=[None])
968 def do_get_network(self, account, id):
969 """Return a network
970
971 Arguments:
972 account - a cloud account
973 id - an id for the network
974
975 Returns:
976 Network info item
977 """
978 with self._use_driver(account) as drv:
979 network = drv.neutron_network_get(id)
980 return self._fill_network_info(network, account)
981
982 @rwstatus(ret_on_failure=[""])
983 def do_create_network(self, account, network):
984 """Create a new network
985
986 Arguments:
987 account - a cloud account
988 network - Network object
989
990 Returns:
991 Network id
992 """
993 kwargs = {}
994 kwargs['name'] = network.network_name
995 kwargs['admin_state_up'] = True
996 kwargs['external_router'] = False
997 kwargs['shared'] = False
998
999 if network.has_field('provider_network'):
1000 if network.provider_network.has_field('physical_network'):
1001 kwargs['physical_network'] = network.provider_network.physical_network
1002 if network.provider_network.has_field('overlay_type'):
1003 kwargs['network_type'] = network.provider_network.overlay_type.lower()
1004 if network.provider_network.has_field('segmentation_id'):
1005 kwargs['segmentation_id'] = network.provider_network.segmentation_id
1006
1007 with self._use_driver(account) as drv:
1008 network_id = drv.neutron_network_create(**kwargs)
1009 drv.neutron_subnet_create(network_id = network_id,
1010 cidr = network.subnet)
1011 return network_id
1012
1013 @rwstatus
1014 def do_delete_network(self, account, network_id):
1015 """Delete a network
1016
1017 Arguments:
1018 account - a cloud account
1019 network_id - an id for the network
1020 """
1021 with self._use_driver(account) as drv:
1022 drv.neutron_network_delete(network_id)
1023
1024 @staticmethod
1025 def _fill_port_info(port_info):
1026 """Create a GI object from port info dictionary
1027
1028 Converts Port information dictionary object returned by openstack
1029 driver into Protobuf Gi Object
1030
1031 Arguments:
1032 port_info - Port information from openstack
1033
1034 Returns:
1035 Port info item
1036 """
1037 port = RwcalYang.PortInfoItem()
1038
1039 port.port_name = port_info['name']
1040 port.port_id = port_info['id']
1041 port.network_id = port_info['network_id']
1042 port.port_state = port_info['status']
1043 if 'device_id' in port_info:
1044 port.vm_id = port_info['device_id']
1045 if 'fixed_ips' in port_info:
1046 port.ip_address = port_info['fixed_ips'][0]['ip_address']
1047 return port
1048
1049 @rwstatus(ret_on_failure=[None])
1050 def do_get_port(self, account, port_id):
1051 """Return a port
1052
1053 Arguments:
1054 account - a cloud account
1055 port_id - an id for the port
1056
1057 Returns:
1058 Port info item
1059 """
1060 with self._use_driver(account) as drv:
1061 port = drv.neutron_port_get(port_id)
1062
1063 return RwcalOpenstackPlugin._fill_port_info(port)
1064
1065 @rwstatus(ret_on_failure=[[]])
1066 def do_get_port_list(self, account):
1067 """Return a list of ports
1068
1069 Arguments:
1070 account - a cloud account
1071
1072 Returns:
1073 Port info list
1074 """
1075 response = RwcalYang.VimResources()
1076 with self._use_driver(account) as drv:
1077 ports = drv.neutron_port_list(*{})
1078 for port in ports:
1079 response.portinfo_list.append(RwcalOpenstackPlugin._fill_port_info(port))
1080 return response
1081
1082 @rwstatus(ret_on_failure=[""])
1083 def do_create_port(self, account, port):
1084 """Create a new port
1085
1086 Arguments:
1087 account - a cloud account
1088 port - port object
1089
1090 Returns:
1091 Port id
1092 """
1093 kwargs = {}
1094 kwargs['name'] = port.port_name
1095 kwargs['network_id'] = port.network_id
1096 kwargs['admin_state_up'] = True
1097 if port.has_field('vm_id'):
1098 kwargs['vm_id'] = port.vm_id
1099 if port.has_field('port_type'):
1100 kwargs['port_type'] = port.port_type
1101 else:
1102 kwargs['port_type'] = "normal"
1103
1104 with self._use_driver(account) as drv:
1105 return drv.neutron_port_create(**kwargs)
1106
1107 @rwstatus
1108 def do_delete_port(self, account, port_id):
1109 """Delete a port
1110
1111 Arguments:
1112 account - a cloud account
1113 port_id - an id for port
1114 """
1115 with self._use_driver(account) as drv:
1116 drv.neutron_port_delete(port_id)
1117
1118 @rwstatus(ret_on_failure=[""])
1119 def do_add_host(self, account, host):
1120 """Add a new host
1121
1122 Arguments:
1123 account - a cloud account
1124 host - a host object
1125
1126 Returns:
1127 An id for the host
1128 """
1129 raise NotImplementedError
1130
1131 @rwstatus
1132 def do_remove_host(self, account, host_id):
1133 """Remove a host
1134
1135 Arguments:
1136 account - a cloud account
1137 host_id - an id for the host
1138 """
1139 raise NotImplementedError
1140
1141 @rwstatus(ret_on_failure=[None])
1142 def do_get_host(self, account, host_id):
1143 """Return a host
1144
1145 Arguments:
1146 account - a cloud account
1147 host_id - an id for host
1148
1149 Returns:
1150 Host info item
1151 """
1152 raise NotImplementedError
1153
1154 @rwstatus(ret_on_failure=[[]])
1155 def do_get_host_list(self, account):
1156 """Return a list of hosts
1157
1158 Arguments:
1159 account - a cloud account
1160
1161 Returns:
1162 List of hosts
1163 """
1164 raise NotImplementedError
1165
1166 @staticmethod
1167 def _fill_connection_point_info(c_point, port_info):
1168 """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints()
1169
1170 Converts Port information dictionary object returned by openstack
1171 driver into Protobuf Gi Object
1172
1173 Arguments:
1174 port_info - Port information from openstack
1175 Returns:
1176 Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints
1177 """
1178 c_point.name = port_info['name']
1179 c_point.connection_point_id = port_info['id']
1180 if ('fixed_ips' in port_info) and (len(port_info['fixed_ips']) >= 1):
1181 if 'ip_address' in port_info['fixed_ips'][0]:
1182 c_point.ip_address = port_info['fixed_ips'][0]['ip_address']
1183 if 'mac_address' in port_info :
1184 c_point.mac_addr = port_info['mac_address']
1185 if port_info['status'] == 'ACTIVE':
1186 c_point.state = 'active'
1187 else:
1188 c_point.state = 'inactive'
1189 if 'network_id' in port_info:
1190 c_point.virtual_link_id = port_info['network_id']
1191 if ('device_id' in port_info) and (port_info['device_id']):
1192 c_point.vdu_id = port_info['device_id']
1193
1194 @staticmethod
1195 def _fill_virtual_link_info(network_info, port_list, subnet):
1196 """Create a GI object for VirtualLinkInfoParams
1197
1198 Converts Network and Port information dictionary object
1199 returned by openstack driver into Protobuf Gi Object
1200
1201 Arguments:
1202 network_info - Network information from openstack
1203 port_list - A list of port information from openstack
1204 subnet: Subnet information from openstack
1205 Returns:
1206 Protobuf Gi object for VirtualLinkInfoParams
1207 """
1208 link = RwcalYang.VirtualLinkInfoParams()
1209 link.name = network_info['name']
1210 if network_info['status'] == 'ACTIVE':
1211 link.state = 'active'
1212 else:
1213 link.state = 'inactive'
1214 link.virtual_link_id = network_info['id']
1215 for port in port_list:
1216 if port['device_owner'] == 'compute:None':
1217 c_point = link.connection_points.add()
1218 RwcalOpenstackPlugin._fill_connection_point_info(c_point, port)
1219
1220 if subnet != None:
1221 link.subnet = subnet['cidr']
1222
1223 if ('provider:network_type' in network_info) and (network_info['provider:network_type'] != None):
1224 link.provider_network.overlay_type = network_info['provider:network_type'].upper()
1225 if ('provider:segmentation_id' in network_info) and (network_info['provider:segmentation_id']):
1226 link.provider_network.segmentation_id = network_info['provider:segmentation_id']
1227 if ('provider:physical_network' in network_info) and (network_info['provider:physical_network']):
1228 link.provider_network.physical_network = network_info['provider:physical_network'].upper()
1229
1230 return link
1231
1232 @staticmethod
1233 def _fill_vdu_info(vm_info, flavor_info, mgmt_network, port_list, server_group, volume_list = None):
1234 """Create a GI object for VDUInfoParams
1235
1236 Converts VM information dictionary object returned by openstack
1237 driver into Protobuf Gi Object
1238
1239 Arguments:
1240 vm_info - VM information from openstack
1241 flavor_info - VM Flavor information from openstack
1242 mgmt_network - Management network
1243 port_list - A list of port information from openstack
1244 server_group - A list (with one element or empty list) of server group to which this VM belongs
1245 Returns:
1246 Protobuf Gi object for VDUInfoParams
1247 """
1248 vdu = RwcalYang.VDUInfoParams()
1249 vdu.name = vm_info['name']
1250 vdu.vdu_id = vm_info['id']
1251 for network_name, network_info in vm_info['addresses'].items():
1252 if network_info and network_name == mgmt_network:
1253 for interface in network_info:
1254 if 'OS-EXT-IPS:type' in interface:
1255 if interface['OS-EXT-IPS:type'] == 'fixed':
1256 vdu.management_ip = interface['addr']
1257 elif interface['OS-EXT-IPS:type'] == 'floating':
1258 vdu.public_ip = interface['addr']
1259
1260 # Look for any metadata
1261 for key, value in vm_info['metadata'].items():
1262 if key == 'node_id':
1263 vdu.node_id = value
1264 if ('image' in vm_info) and ('id' in vm_info['image']):
1265 vdu.image_id = vm_info['image']['id']
1266 if ('flavor' in vm_info) and ('id' in vm_info['flavor']):
1267 vdu.flavor_id = vm_info['flavor']['id']
1268
1269 if vm_info['status'] == 'ACTIVE':
1270 vdu.state = 'active'
1271 elif vm_info['status'] == 'ERROR':
1272 vdu.state = 'failed'
1273 else:
1274 vdu.state = 'inactive'
1275
1276 if 'availability_zone' in vm_info:
1277 vdu.availability_zone = vm_info['availability_zone']
1278
1279 if server_group:
1280 vdu.server_group.name = server_group[0]
1281
1282 vdu.cloud_type = 'openstack'
1283 # Fill the port information
1284 for port in port_list:
1285 c_point = vdu.connection_points.add()
1286 RwcalOpenstackPlugin._fill_connection_point_info(c_point, port)
1287
1288 if flavor_info is not None:
1289 RwcalOpenstackPlugin._fill_epa_attributes(vdu, flavor_info)
1290
1291 # Fill the volume information
1292 if volume_list is not None:
1293 for os_volume in volume_list:
1294 volr = vdu.volumes.add()
1295 try:
1296 " Device name is of format /dev/vda"
1297 vol_name = (os_volume['device']).split('/')[2]
1298 except:
1299 continue
1300 volr.name = vol_name
1301 volr.volume_id = os_volume['volumeId']
1302
1303 return vdu
1304
1305 @rwcalstatus(ret_on_failure=[""])
1306 def do_create_virtual_link(self, account, link_params):
1307 """Create a new virtual link
1308
1309 Arguments:
1310 account - a cloud account
1311 link_params - information that defines the type of VDU to create
1312
1313 Returns:
1314 The vdu_id
1315 """
1316 kwargs = {}
1317 kwargs['name'] = link_params.name
1318 kwargs['admin_state_up'] = True
1319 kwargs['external_router'] = False
1320 kwargs['shared'] = False
1321
1322 if link_params.has_field('provider_network'):
1323 if link_params.provider_network.has_field('physical_network'):
1324 kwargs['physical_network'] = link_params.provider_network.physical_network
1325 if link_params.provider_network.has_field('overlay_type'):
1326 kwargs['network_type'] = link_params.provider_network.overlay_type.lower()
1327 if link_params.provider_network.has_field('segmentation_id'):
1328 kwargs['segmentation_id'] = link_params.provider_network.segmentation_id
1329
1330
1331 with self._use_driver(account) as drv:
1332 try:
1333 network_id = drv.neutron_network_create(**kwargs)
1334 except Exception as e:
1335 self.log.error("Encountered exceptions during network creation. Exception: %s", str(e))
1336 raise
1337
1338 kwargs = {'network_id' : network_id,
1339 'dhcp_params': {'enable_dhcp': True},
1340 'gateway_ip' : None,}
1341
1342 if link_params.ip_profile_params.has_field('ip_version'):
1343 kwargs['ip_version'] = 6 if link_params.ip_profile_params.ip_version == 'ipv6' else 4
1344 else:
1345 kwargs['ip_version'] = 4
1346
1347 if link_params.ip_profile_params.has_field('subnet_address'):
1348 kwargs['cidr'] = link_params.ip_profile_params.subnet_address
1349 elif link_params.ip_profile_params.has_field('subnet_prefix_pool'):
1350 subnet_pool = drv.netruon_subnetpool_by_name(link_params.ip_profile_params.subnet_prefix_pool)
1351 if subnet_pool is None:
1352 self.log.error("Could not find subnet pool with name :%s to be used for network: %s",
1353 link_params.ip_profile_params.subnet_prefix_pool,
1354 link_params.name)
1355 raise NeutronException.NotFound("SubnetPool with name %s not found"%(link_params.ip_profile_params.subnet_prefix_pool))
1356
1357 kwargs['subnetpool_id'] = subnet_pool['id']
1358 elif link_params.has_field('subnet'):
1359 kwargs['cidr'] = link_params.subnet
1360 else:
1361 assert 0, "No IP Prefix or Pool name specified"
1362
1363 if link_params.ip_profile_params.has_field('dhcp_params'):
1364 if link_params.ip_profile_params.dhcp_params.has_field('enabled'):
1365 kwargs['dhcp_params']['enable_dhcp'] = link_params.ip_profile_params.dhcp_params.enabled
1366 if link_params.ip_profile_params.dhcp_params.has_field('start_address'):
1367 kwargs['dhcp_params']['start_address'] = link_params.ip_profile_params.dhcp_params.start_address
1368 if link_params.ip_profile_params.dhcp_params.has_field('count'):
1369 kwargs['dhcp_params']['count'] = link_params.ip_profile_params.dhcp_params.count
1370
1371 if link_params.ip_profile_params.has_field('dns_server'):
1372 kwargs['dns_server'] = []
1373 for server in link_params.ip_profile_params.dns_server:
1374 kwargs['dns_server'].append(server.address)
1375
1376 if link_params.ip_profile_params.has_field('gateway_address'):
1377 kwargs['gateway_ip'] = link_params.ip_profile_params.gateway_address
1378
1379 drv.neutron_subnet_create(**kwargs)
1380
1381 return network_id
1382
1383
1384 @rwstatus
1385 def do_delete_virtual_link(self, account, link_id):
1386 """Delete a virtual link
1387
1388 Arguments:
1389 account - a cloud account
1390 link_id - id for the virtual-link to be deleted
1391
1392 Returns:
1393 None
1394 """
1395 if not link_id:
1396 self.log.error("Empty link_id during the virtual link deletion")
1397 raise Exception("Empty link_id during the virtual link deletion")
1398
1399 with self._use_driver(account) as drv:
1400 port_list = drv.neutron_port_list(**{'network_id': link_id})
1401
1402 for port in port_list:
1403 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
1404 self.do_delete_port(account, port['id'], no_rwstatus=True)
1405 self.do_delete_network(account, link_id, no_rwstatus=True)
1406
1407 @rwstatus(ret_on_failure=[None])
1408 def do_get_virtual_link(self, account, link_id):
1409 """Get information about virtual link.
1410
1411 Arguments:
1412 account - a cloud account
1413 link_id - id for the virtual-link
1414
1415 Returns:
1416 Object of type RwcalYang.VirtualLinkInfoParams
1417 """
1418 if not link_id:
1419 self.log.error("Empty link_id during the virtual link get request")
1420 raise Exception("Empty link_id during the virtual link get request")
1421
1422 with self._use_driver(account) as drv:
1423 network = drv.neutron_network_get(link_id)
1424 if network:
1425 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1426 if 'subnets' in network:
1427 subnet = drv.neutron_subnet_get(network['subnets'][0])
1428 else:
1429 subnet = None
1430 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1431 else:
1432 virtual_link = None
1433 return virtual_link
1434
1435 @rwstatus(ret_on_failure=[None])
1436 def do_get_virtual_link_list(self, account):
1437 """Get information about all the virtual links
1438
1439 Arguments:
1440 account - a cloud account
1441
1442 Returns:
1443 A list of objects of type RwcalYang.VirtualLinkInfoParams
1444 """
1445 vnf_resources = RwcalYang.VNFResources()
1446 with self._use_driver(account) as drv:
1447 networks = drv.neutron_network_list()
1448 for network in networks:
1449 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1450 if ('subnets' in network) and (network['subnets']):
1451 subnet = drv.neutron_subnet_get(network['subnets'][0])
1452 else:
1453 subnet = None
1454 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1455 vnf_resources.virtual_link_info_list.append(virtual_link)
1456 return vnf_resources
1457
1458 def _create_connection_point(self, account, c_point):
1459 """
1460 Create a connection point
1461 Arguments:
1462 account - a cloud account
1463 c_point - connection_points
1464 """
1465 kwargs = {}
1466 kwargs['name'] = c_point.name
1467 kwargs['network_id'] = c_point.virtual_link_id
1468 kwargs['admin_state_up'] = True
1469
1470 if c_point.type_yang == 'VIRTIO' or c_point.type_yang == 'E1000':
1471 kwargs['port_type'] = 'normal'
1472 elif c_point.type_yang == 'SR_IOV':
1473 kwargs['port_type'] = 'direct'
1474 else:
1475 raise NotImplementedError("Port Type: %s not supported" %(c_point.type_yang))
1476
1477 with self._use_driver(account) as drv:
1478 if c_point.has_field('security_group'):
1479 group = drv.neutron_security_group_by_name(c_point.security_group)
1480 if group is not None:
1481 kwargs['security_groups'] = [group['id']]
1482 return drv.neutron_port_create(**kwargs)
1483
1484 def _allocate_floating_ip(self, drv, pool_name):
1485 """
1486 Allocate a floating_ip. If unused floating_ip exists then its reused.
1487 Arguments:
1488 drv: OpenstackDriver instance
1489 pool_name: Floating IP pool name
1490
1491 Returns:
1492 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1493 """
1494
1495 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1496
1497 # if pool_name is not None:
1498 # ### Filter further based on IP address
1499 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1500
1501 # if not available_ip:
1502 # floating_ip = drv.nova_floating_ip_create(pool_name)
1503 # else:
1504 # floating_ip = available_ip[0]
1505
1506 floating_ip = drv.nova_floating_ip_create(pool_name)
1507 return floating_ip
1508
1509 def _match_vm_flavor(self, required, available):
1510 self.log.info("Matching VM Flavor attributes")
1511 if available.vcpu_count != required.vcpu_count:
1512 self.log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1513 required.vcpu_count,
1514 available.vcpu_count)
1515 return False
1516 if available.memory_mb != required.memory_mb:
1517 self.log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1518 required.memory_mb,
1519 available.memory_mb)
1520 return False
1521 if available.storage_gb != required.storage_gb:
1522 self.log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1523 required.storage_gb,
1524 available.storage_gb)
1525 return False
1526 self.log.debug("VM Flavor match found")
1527 return True
1528
1529 def _match_guest_epa(self, required, available):
1530 self.log.info("Matching Guest EPA attributes")
1531 if required.has_field('pcie_device'):
1532 self.log.debug("Matching pcie_device")
1533 if available.has_field('pcie_device') == False:
1534 self.log.debug("Matching pcie_device failed. Not available in flavor")
1535 return False
1536 else:
1537 for dev in required.pcie_device:
1538 if not [ d for d in available.pcie_device
1539 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
1540 self.log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
1541 return False
1542 elif available.has_field('pcie_device'):
1543 self.log.debug("Rejecting available flavor because pcie_device not required but available")
1544 return False
1545
1546
1547 if required.has_field('mempage_size'):
1548 self.log.debug("Matching mempage_size")
1549 if available.has_field('mempage_size') == False:
1550 self.log.debug("Matching mempage_size failed. Not available in flavor")
1551 return False
1552 else:
1553 if required.mempage_size != available.mempage_size:
1554 self.log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
1555 return False
1556 elif available.has_field('mempage_size'):
1557 self.log.debug("Rejecting available flavor because mempage_size not required but available")
1558 return False
1559
1560 if required.has_field('cpu_pinning_policy'):
1561 self.log.debug("Matching cpu_pinning_policy")
1562 if required.cpu_pinning_policy != 'ANY':
1563 if available.has_field('cpu_pinning_policy') == False:
1564 self.log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1565 return False
1566 else:
1567 if required.cpu_pinning_policy != available.cpu_pinning_policy:
1568 self.log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
1569 return False
1570 elif available.has_field('cpu_pinning_policy'):
1571 self.log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1572 return False
1573
1574 if required.has_field('cpu_thread_pinning_policy'):
1575 self.log.debug("Matching cpu_thread_pinning_policy")
1576 if available.has_field('cpu_thread_pinning_policy') == False:
1577 self.log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1578 return False
1579 else:
1580 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
1581 self.log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
1582 return False
1583 elif available.has_field('cpu_thread_pinning_policy'):
1584 self.log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1585 return False
1586
1587 if required.has_field('trusted_execution'):
1588 self.log.debug("Matching trusted_execution")
1589 if required.trusted_execution == True:
1590 if available.has_field('trusted_execution') == False:
1591 self.log.debug("Matching trusted_execution failed. Not available in flavor")
1592 return False
1593 else:
1594 if required.trusted_execution != available.trusted_execution:
1595 self.log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
1596 return False
1597 elif available.has_field('trusted_execution'):
1598 self.log.debug("Rejecting available flavor because trusted_execution not required but available")
1599 return False
1600
1601 if required.has_field('numa_node_policy'):
1602 self.log.debug("Matching numa_node_policy")
1603 if available.has_field('numa_node_policy') == False:
1604 self.log.debug("Matching numa_node_policy failed. Not available in flavor")
1605 return False
1606 else:
1607 if required.numa_node_policy.has_field('node_cnt'):
1608 self.log.debug("Matching numa_node_policy node_cnt")
1609 if available.numa_node_policy.has_field('node_cnt') == False:
1610 self.log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1611 return False
1612 else:
1613 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
1614 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)
1615 return False
1616 elif available.numa_node_policy.has_field('node_cnt'):
1617 self.log.debug("Rejecting available flavor because numa node count not required but available")
1618 return False
1619
1620 if required.numa_node_policy.has_field('mem_policy'):
1621 self.log.debug("Matching numa_node_policy mem_policy")
1622 if available.numa_node_policy.has_field('mem_policy') == False:
1623 self.log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1624 return False
1625 else:
1626 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
1627 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)
1628 return False
1629 elif available.numa_node_policy.has_field('mem_policy'):
1630 self.log.debug("Rejecting available flavor because num node mem_policy not required but available")
1631 return False
1632
1633 if required.numa_node_policy.has_field('node'):
1634 self.log.debug("Matching numa_node_policy nodes configuration")
1635 if available.numa_node_policy.has_field('node') == False:
1636 self.log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1637 return False
1638 for required_node in required.numa_node_policy.node:
1639 self.log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1640 numa_match = False
1641 for available_node in available.numa_node_policy.node:
1642 if required_node.id != available_node.id:
1643 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1644 continue
1645 if required_node.vcpu != available_node.vcpu:
1646 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1647 continue
1648 if required_node.memory_mb != available_node.memory_mb:
1649 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1650 continue
1651 numa_match = True
1652 if numa_match == False:
1653 return False
1654 elif available.numa_node_policy.has_field('node'):
1655 self.log.debug("Rejecting available flavor because numa nodes not required but available")
1656 return False
1657 elif available.has_field('numa_node_policy'):
1658 self.log.debug("Rejecting available flavor because numa_node_policy not required but available")
1659 return False
1660 self.log.info("Successful match for Guest EPA attributes")
1661 return True
1662
1663 def _match_vswitch_epa(self, required, available):
1664 self.log.debug("VSwitch EPA match found")
1665 return True
1666
1667 def _match_hypervisor_epa(self, required, available):
1668 self.log.debug("Hypervisor EPA match found")
1669 return True
1670
1671 def _match_host_epa(self, required, available):
1672 self.log.info("Matching Host EPA attributes")
1673 if required.has_field('cpu_model'):
1674 self.log.debug("Matching CPU model")
1675 if available.has_field('cpu_model') == False:
1676 self.log.debug("Matching CPU model failed. Not available in flavor")
1677 return False
1678 else:
1679 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1680 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1681 self.log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1682 return False
1683 elif available.has_field('cpu_model'):
1684 self.log.debug("Rejecting available flavor because cpu_model not required but available")
1685 return False
1686
1687 if required.has_field('cpu_arch'):
1688 self.log.debug("Matching CPU architecture")
1689 if available.has_field('cpu_arch') == False:
1690 self.log.debug("Matching CPU architecture failed. Not available in flavor")
1691 return False
1692 else:
1693 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1694 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1695 self.log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1696 return False
1697 elif available.has_field('cpu_arch'):
1698 self.log.debug("Rejecting available flavor because cpu_arch not required but available")
1699 return False
1700
1701 if required.has_field('cpu_vendor'):
1702 self.log.debug("Matching CPU vendor")
1703 if available.has_field('cpu_vendor') == False:
1704 self.log.debug("Matching CPU vendor failed. Not available in flavor")
1705 return False
1706 else:
1707 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1708 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1709 self.log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1710 return False
1711 elif available.has_field('cpu_vendor'):
1712 self.log.debug("Rejecting available flavor because cpu_vendor not required but available")
1713 return False
1714
1715 if required.has_field('cpu_socket_count'):
1716 self.log.debug("Matching CPU socket count")
1717 if available.has_field('cpu_socket_count') == False:
1718 self.log.debug("Matching CPU socket count failed. Not available in flavor")
1719 return False
1720 else:
1721 if required.cpu_socket_count != available.cpu_socket_count:
1722 self.log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1723 return False
1724 elif available.has_field('cpu_socket_count'):
1725 self.log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1726 return False
1727
1728 if required.has_field('cpu_core_count'):
1729 self.log.debug("Matching CPU core count")
1730 if available.has_field('cpu_core_count') == False:
1731 self.log.debug("Matching CPU core count failed. Not available in flavor")
1732 return False
1733 else:
1734 if required.cpu_core_count != available.cpu_core_count:
1735 self.log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1736 return False
1737 elif available.has_field('cpu_core_count'):
1738 self.log.debug("Rejecting available flavor because cpu_core_count not required but available")
1739 return False
1740
1741 if required.has_field('cpu_core_thread_count'):
1742 self.log.debug("Matching CPU core thread count")
1743 if available.has_field('cpu_core_thread_count') == False:
1744 self.log.debug("Matching CPU core thread count failed. Not available in flavor")
1745 return False
1746 else:
1747 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1748 self.log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1749 return False
1750 elif available.has_field('cpu_core_thread_count'):
1751 self.log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1752 return False
1753
1754 if required.has_field('cpu_feature'):
1755 self.log.debug("Matching CPU feature list")
1756 if available.has_field('cpu_feature') == False:
1757 self.log.debug("Matching CPU feature list failed. Not available in flavor")
1758 return False
1759 else:
1760 for feature in required.cpu_feature:
1761 if feature not in available.cpu_feature:
1762 self.log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1763 return False
1764 elif available.has_field('cpu_feature'):
1765 self.log.debug("Rejecting available flavor because cpu_feature not required but available")
1766 return False
1767 self.log.info("Successful match for Host EPA attributes")
1768 return True
1769
1770
1771 def _match_placement_group_inputs(self, required, available):
1772 self.log.info("Matching Host aggregate attributes")
1773
1774 if not required and not available:
1775 # Host aggregate not required and not available => success
1776 self.log.info("Successful match for Host Aggregate attributes")
1777 return True
1778 if required and available:
1779 # Host aggregate requested and available => Do a match and decide
1780 xx = [ x.as_dict() for x in required ]
1781 yy = [ y.as_dict() for y in available ]
1782 for i in xx:
1783 if i not in yy:
1784 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1785 return False
1786 self.log.info("Successful match for Host Aggregate attributes")
1787 return True
1788 else:
1789 # Either of following conditions => Failure
1790 # - Host aggregate required but not available
1791 # - Host aggregate not required but available
1792 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1793 return False
1794
1795 def match_epa_params(self, resource_info, request_params):
1796 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1797 getattr(resource_info, 'vm_flavor'))
1798 if result == False:
1799 self.log.debug("VM Flavor mismatched")
1800 return False
1801
1802 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1803 getattr(resource_info, 'guest_epa'))
1804 if result == False:
1805 self.log.debug("Guest EPA mismatched")
1806 return False
1807
1808 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1809 getattr(resource_info, 'vswitch_epa'))
1810 if result == False:
1811 self.log.debug("Vswitch EPA mismatched")
1812 return False
1813
1814 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1815 getattr(resource_info, 'hypervisor_epa'))
1816 if result == False:
1817 self.log.debug("Hypervisor EPA mismatched")
1818 return False
1819
1820 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1821 getattr(resource_info, 'host_epa'))
1822 if result == False:
1823 self.log.debug("Host EPA mismatched")
1824 return False
1825
1826 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1827 getattr(resource_info, 'host_aggregate'))
1828
1829 if result == False:
1830 self.log.debug("Host Aggregate mismatched")
1831 return False
1832
1833 return True
1834
1835 def _select_resource_flavor(self, account, vdu_init):
1836 """
1837 Select a existing flavor if it matches the request or create new flavor
1838 """
1839 flavor = RwcalYang.FlavorInfoItem()
1840 flavor.name = str(uuid.uuid4())
1841 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1842 epa_dict = {k: v for k, v in vdu_init.as_dict().items() if k in epa_types}
1843 flavor.from_dict(epa_dict)
1844
1845 rc, response = self.do_get_flavor_list(account)
1846 if rc != RwTypes.RwStatus.SUCCESS:
1847 self.log.error("Get-flavor-info-list operation failed for cloud account: %s",
1848 account.name)
1849 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account.name))
1850
1851 flavor_id = None
1852 flavor_list = response.flavorinfo_list
1853 self.log.debug("Received %d flavor information from RW.CAL", len(flavor_list))
1854 for flv in flavor_list:
1855 self.log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1856 vdu_init.name, flv)
1857 if self.match_epa_params(flv, vdu_init):
1858 self.log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1859 vdu_init.name, flv.name, flv.id)
1860 return flv.id
1861
1862 if account.openstack.dynamic_flavor_support is False:
1863 self.log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init.name)
1864 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1865 else:
1866 rc,flavor_id = self.do_create_flavor(account,flavor)
1867 if rc != RwTypes.RwStatus.SUCCESS:
1868 self.log.error("Create-flavor operation failed for cloud account: %s",
1869 account.name)
1870 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account.name))
1871 return flavor_id
1872
1873 def _create_vm(self, account, vduinfo, pci_assignement=None, server_group=None, port_list=None, network_list=None, imageinfo_list=None):
1874 """Create a new virtual machine.
1875
1876 Arguments:
1877 account - a cloud account
1878 vminfo - information that defines the type of VM to create
1879
1880 Returns:
1881 The image id
1882 """
1883 kwargs = {}
1884 kwargs['name'] = vduinfo.name
1885 kwargs['flavor_id'] = vduinfo.flavor_id
1886 if vduinfo.has_field('image_id'):
1887 kwargs['image_id'] = vduinfo.image_id
1888 else:
1889 kwargs['image_id'] = ""
1890
1891 with self._use_driver(account) as drv:
1892 ### If floating_ip is required and we don't have one, better fail before any further allocation
1893 if vduinfo.has_field('allocate_public_address') and vduinfo.allocate_public_address:
1894 if account.openstack.has_field('floating_ip_pool'):
1895 pool_name = account.openstack.floating_ip_pool
1896 else:
1897 pool_name = None
1898 floating_ip = self._allocate_floating_ip(drv, pool_name)
1899 else:
1900 floating_ip = None
1901
1902 if vduinfo.has_field('vdu_init') and vduinfo.vdu_init.has_field('userdata'):
1903 kwargs['userdata'] = vduinfo.vdu_init.userdata
1904 else:
1905 kwargs['userdata'] = ''
1906
1907 if account.openstack.security_groups:
1908 kwargs['security_groups'] = account.openstack.security_groups
1909
1910 kwargs['port_list'] = port_list
1911 kwargs['network_list'] = network_list
1912
1913 metadata = {}
1914 # Add all metadata related fields
1915 if vduinfo.has_field('node_id'):
1916 metadata['node_id'] = vduinfo.node_id
1917 if pci_assignement is not None:
1918 metadata['pci_assignement'] = pci_assignement
1919 kwargs['metadata'] = metadata
1920
1921 if vduinfo.has_field('availability_zone') and vduinfo.availability_zone.has_field('name'):
1922 kwargs['availability_zone'] = vduinfo.availability_zone
1923 else:
1924 kwargs['availability_zone'] = None
1925
1926 if server_group is not None:
1927 kwargs['scheduler_hints'] = {'group': server_group}
1928 else:
1929 kwargs['scheduler_hints'] = None
1930
1931 kwargs['block_device_mapping_v2'] = None
1932 if vduinfo.has_field('volumes') :
1933 kwargs['block_device_mapping_v2'] = []
1934 with self._use_driver(account) as drv:
1935 # Only support image->volume
1936 for volume in vduinfo.volumes:
1937 block_map = dict()
1938 block_map['boot_index'] = volume.boot_params.boot_priority
1939 if "image" in volume:
1940 # Support image->volume
1941 # Match retrived image info with volume based image name and checksum
1942 if volume.image is not None:
1943 matching_images = [img for img in imageinfo_list if img['name'] == volume.image]
1944 if volume.image_checksum is not None:
1945 matching_images = [img for img in matching_images if img['checksum'] == volume.image_checksum]
1946 img_id = matching_images[0]['id']
1947 if img_id is None:
1948 raise OpenstackCALOperationFailure("Create-vdu operation failed. Volume image not found for name {} checksum {}".format(volume.name, volume.checksum))
1949 block_map['uuid'] = img_id
1950 block_map['source_type'] = "image"
1951 else:
1952 block_map['source_type'] = "blank"
1953
1954 block_map['device_name'] = volume.name
1955 block_map['destination_type'] = "volume"
1956 block_map['volume_size'] = volume.size
1957 block_map['delete_on_termination'] = True
1958 if volume.guest_params.has_field('device_type') and volume.guest_params.device_type == 'cdrom':
1959 block_map['device_type'] = 'cdrom'
1960 if volume.guest_params.has_field('device_bus') and volume.guest_params.device_bus == 'ide':
1961 block_map['disk_bus'] = 'ide'
1962 kwargs['block_device_mapping_v2'].append(block_map)
1963
1964
1965 with self._use_driver(account) as drv:
1966 vm_id = drv.nova_server_create(**kwargs)
1967 if floating_ip:
1968 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
1969
1970 return vm_id
1971
1972 def get_openstack_image_info(self, account, image_name, image_checksum=None):
1973 self.log.debug("Looking up image id for image name %s and checksum %s on cloud account: %s",
1974 image_name, image_checksum, account.name
1975 )
1976
1977 image_list = []
1978 with self._use_driver(account) as drv:
1979 image_list = drv.glance_image_list()
1980 matching_images = [img for img in image_list if img['name'] == image_name]
1981
1982 # If the image checksum was filled in then further filter the images by the checksum
1983 if image_checksum is not None:
1984 matching_images = [img for img in matching_images if img['checksum'] == image_checksum]
1985 else:
1986 self.log.warning("Image checksum not provided. Lookup using image name (%s) only.",
1987 image_name)
1988
1989 if len(matching_images) == 0:
1990 raise ResMgrCALOperationFailure("Could not find image name {} (using checksum: {}) for cloud account: {}".format(
1991 image_name, image_checksum, account.name
1992 ))
1993
1994 elif len(matching_images) > 1:
1995 unique_checksums = {i.checksum for i in matching_images}
1996 if len(unique_checksums) > 1:
1997 msg = ("Too many images with different checksums matched "
1998 "image name of %s for cloud account: %s" % (image_name, account.name))
1999 raise ResMgrCALOperationFailure(msg)
2000
2001 return matching_images[0]
2002
2003 @rwcalstatus(ret_on_failure=[""])
2004 def do_create_vdu(self, account, vdu_init):
2005 """Create a new virtual deployment unit
2006
2007 Arguments:
2008 account - a cloud account
2009 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
2010
2011 Returns:
2012 The vdu_id
2013 """
2014 ### First create required number of ports aka connection points
2015 # Add the mgmt_ntwk by default.
2016 mgmt_network_id = None
2017 with self._use_driver(account) as drv:
2018 mgmt_network_id = drv._mgmt_network_id
2019 ### If floating_ip is required and we don't have one, better fail before any further allocation
2020 if vdu_init.has_field('allocate_public_address') and vdu_init.allocate_public_address:
2021 if account.openstack.has_field('floating_ip_pool'):
2022 pool_name = account.openstack.floating_ip_pool
2023 else:
2024 pool_name = None
2025 floating_ip = self._allocate_floating_ip(drv, pool_name)
2026 else:
2027 floating_ip = None
2028
2029 port_list = []
2030 network_list = []
2031 imageinfo_list = []
2032 is_explicit_mgmt_defined = False
2033 for c_point in vdu_init.connection_points:
2034 # if the user has specified explicit mgmt_network connection point
2035 # then remove the mgmt_network from the VM list
2036 if c_point.virtual_link_id == mgmt_network_id:
2037 is_explicit_mgmt_defined = True
2038 if c_point.virtual_link_id in network_list:
2039 assert False, "Only one port per network supported. Refer: http://specs.openstack.org/openstack/nova-specs/specs/juno/implemented/nfv-multiple-if-1-net.html"
2040 else:
2041 network_list.append(c_point.virtual_link_id)
2042 port_id = self._create_connection_point(account, c_point)
2043 port_list.append(port_id)
2044
2045 if not vdu_init.has_field('flavor_id'):
2046 vdu_init.flavor_id = self._select_resource_flavor(account,vdu_init)
2047
2048 ### Obtain all images for volumes and perform validations
2049 if vdu_init.has_field('volumes'):
2050 for volume in vdu_init.volumes:
2051 if "image" in volume:
2052 image_checksum = volume.image_checksum if volume.has_field("image_checksum") else None
2053 image_info = self.get_openstack_image_info(account, volume.image, image_checksum)
2054 imageinfo_list.append(image_info)
2055 elif vdu_init.has_field('image_id'):
2056 with self._use_driver(account) as drv:
2057 image_info = drv.glance_image_get(vdu_init.image_id)
2058 imageinfo_list.append(image_info)
2059
2060 if not imageinfo_list:
2061 err_str = ("VDU has no image information")
2062 self.log.error(err_str)
2063 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2064
2065 ### Check VDU Virtual Interface type and make sure VM with property exists
2066 if vdu_init.connection_points:
2067 ### All virtual interfaces need to be of the same type for Openstack Accounts
2068 if not (all(cp.type_yang == 'E1000' for cp in vdu_init.connection_points) or all(cp.type_yang != 'E1000' for cp in vdu_init.connection_points)):
2069 ### We have a mix of E1000 & VIRTIO/SR_IPOV virtual interface types in the VDU, abort instantiation.
2070 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO/SR_IOV & E1000."
2071
2072 ## It is not clear if all the images need to checked for HW properties. In the absence of model info describing each im age's properties,
2073 ### we shall assume that all images need to have similar properties
2074 for img_info in imageinfo_list:
2075
2076 virt_intf_type = vdu_init.connection_points[0].type_yang
2077 if virt_intf_type == 'E1000':
2078 if 'hw_vif_model' in img_info and img_info.hw_vif_model == 'e1000':
2079 self.log.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
2080 else:
2081 err_str = ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info.name)
2082 self.log.error(err_str)
2083 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2084 elif virt_intf_type == 'VIRTIO' or virt_intf_type == 'SR_IOV':
2085 if 'hw_vif_model' in img_info:
2086 err_str = ("VDU has Virtual Interface %s, but image '%s' has hw_vif_model mismatch" % virt_intf_type,img_info.name)
2087 self.log.error(err_str)
2088 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2089 else:
2090 self.log.debug("VDU has Virtual Interface %s, found matching image" % virt_intf_type)
2091 else:
2092 err_str = ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type)
2093 self.log.error(err_str)
2094 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2095
2096 with self._use_driver(account) as drv:
2097 ### Now Create VM
2098 vm_network_list = []
2099 if not is_explicit_mgmt_defined:
2100 vm_network_list.append(drv._mgmt_network_id)
2101
2102 if vdu_init.has_field('volumes'):
2103 # Only combination supported: Image->Volume
2104 for volume in vdu_init.volumes:
2105 if "volume" in volume:
2106 err_str = ("VDU Volume source not supported yet")
2107 self.log.error(err_str)
2108 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2109 if "guest_params" not in volume:
2110 err_str = ("VDU Volume destination parameters '%s' not defined")
2111 self.log.error(err_str)
2112 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2113 if not volume.guest_params.has_field('device_type'):
2114 err_str = ("VDU Volume destination type '%s' not defined")
2115 self.log.error(err_str)
2116 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2117 if volume.guest_params.device_type not in ['disk', 'cdrom'] :
2118 err_str = ("VDU Volume destination type '%s' not supported" % volume.guest_params.device_type)
2119 self.log.error(err_str)
2120 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2121
2122
2123 server_group = None
2124 if vdu_init.has_field('server_group'):
2125 ### Get list of server group in openstack for name->id mapping
2126 openstack_group_list = drv.nova_server_group_list()
2127 group_id = [ i['id'] for i in openstack_group_list if i['name'] == vdu_init.server_group.name]
2128 if len(group_id) != 1:
2129 raise OpenstackServerGroupError("VM placement failed. Server Group %s not found in openstack. Available groups" %(vdu_init.server_group.name, [i['name'] for i in openstack_group_list]))
2130 server_group = group_id[0]
2131
2132 pci_assignement = self.prepare_vpci_metadata(drv, vdu_init)
2133 if pci_assignement != '':
2134 vm.user_tags.pci_assignement = pci_assignement
2135
2136 vm_id = self._create_vm(account, vdu_init, pci_assignement=pci_assignement, server_group=server_group, port_list=port_list, network_list=vm_network_list, imageinfo_list = imageinfo_list)
2137 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
2138 return vm_id
2139
2140 def prepare_vpci_metadata(self, drv, vdu_init):
2141 pci_assignement = ''
2142 ### TEF specific metadata creation for
2143 virtio_vpci = []
2144 sriov_vpci = []
2145 virtio_meta = ''
2146 sriov_meta = ''
2147 ### For MGMT interface
2148 if vdu_init.has_field('mgmt_vpci'):
2149 xx = 'u\''+ drv._mgmt_network_id + '\' :[[u\'' + vdu_init.mgmt_vpci + '\', ' + '\'\']]'
2150 virtio_vpci.append(xx)
2151
2152 for c_point in vdu_init.connection_points:
2153 if c_point.has_field('vpci'):
2154 if c_point.has_field('vpci') and c_point.type_yang == 'VIRTIO':
2155 xx = 'u\''+c_point.virtual_link_id + '\' :[[u\'' + c_point.vpci + '\', ' + '\'\']]'
2156 virtio_vpci.append(xx)
2157 elif c_point.has_field('vpci') and c_point.type_yang == 'SR_IOV':
2158 xx = '[u\'' + c_point.vpci + '\', ' + '\'\']'
2159 sriov_vpci.append(xx)
2160
2161 if virtio_vpci:
2162 virtio_meta += ','.join(virtio_vpci)
2163
2164 if sriov_vpci:
2165 sriov_meta = 'u\'VF\': ['
2166 sriov_meta += ','.join(sriov_vpci)
2167 sriov_meta += ']'
2168
2169 if virtio_meta != '':
2170 pci_assignement += virtio_meta
2171 pci_assignement += ','
2172
2173 if sriov_meta != '':
2174 pci_assignement += sriov_meta
2175
2176 if pci_assignement != '':
2177 pci_assignement = '{' + pci_assignement + '}'
2178
2179 return pci_assignement
2180
2181
2182
2183 def prepare_vdu_on_boot(self, account, server_id, floating_ip):
2184 cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url,
2185 username = account.openstack.key,
2186 password = account.openstack.secret,
2187 tenant_name = account.openstack.tenant,
2188 mgmt_network = account.openstack.mgmt_network,
2189 server_id = server_id)
2190
2191 if floating_ip is not None:
2192 cmd += (" --floating_ip "+ floating_ip.ip)
2193
2194 exec_path = 'python3 ' + os.path.dirname(openstack_drv.__file__)
2195 exec_cmd = exec_path+'/'+cmd
2196 self.log.info("Running command: %s" %(exec_cmd))
2197 subprocess.call(exec_cmd, shell=True)
2198
2199 @rwstatus
2200 def do_modify_vdu(self, account, vdu_modify):
2201 """Modify Properties of existing virtual deployment unit
2202
2203 Arguments:
2204 account - a cloud account
2205 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2206 """
2207 ### First create required number of ports aka connection points
2208 port_list = []
2209 network_list = []
2210 for c_point in vdu_modify.connection_points_add:
2211 if c_point.virtual_link_id in network_list:
2212 assert False, "Only one port per network supported. Refer: http://specs.openstack.org/openstack/nova-specs/specs/juno/implemented/nfv-multiple-if-1-net.html"
2213 else:
2214 network_list.append(c_point.virtual_link_id)
2215 port_id = self._create_connection_point(account, c_point)
2216 port_list.append(port_id)
2217
2218 ### Now add the ports to VM
2219 for port_id in port_list:
2220 with self._use_driver(account) as drv:
2221 drv.nova_server_add_port(vdu_modify.vdu_id, port_id)
2222
2223 ### Delete the requested connection_points
2224 for c_point in vdu_modify.connection_points_remove:
2225 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
2226
2227 if vdu_modify.has_field('image_id'):
2228 with self._use_driver(account) as drv:
2229 drv.nova_server_rebuild(vdu_modify.vdu_id, vdu_modify.image_id)
2230
2231
2232 @rwstatus
2233 def do_delete_vdu(self, account, vdu_id):
2234 """Delete a virtual deployment unit
2235
2236 Arguments:
2237 account - a cloud account
2238 vdu_id - id for the vdu to be deleted
2239
2240 Returns:
2241 None
2242 """
2243 if not vdu_id:
2244 self.log.error("empty vdu_id during the vdu deletion")
2245 return
2246
2247 with self._use_driver(account) as drv:
2248 ### Get list of floating_ips associated with this instance and delete them
2249 floating_ips = [ f for f in drv.nova_floating_ip_list() if f.instance_id == vdu_id ]
2250 for f in floating_ips:
2251 drv.nova_drv.floating_ip_delete(f)
2252
2253 ### Get list of port on VM and delete them.
2254 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2255
2256 for port in port_list:
2257 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
2258 self.do_delete_port(account, port['id'], no_rwstatus=True)
2259
2260 self.do_delete_vm(account, vdu_id, no_rwstatus=True)
2261
2262
2263 @rwstatus(ret_on_failure=[None])
2264 def do_get_vdu(self, account, vdu_id):
2265 """Get information about a virtual deployment unit.
2266
2267 Arguments:
2268 account - a cloud account
2269 vdu_id - id for the vdu
2270
2271 Returns:
2272 Object of type RwcalYang.VDUInfoParams
2273 """
2274 with self._use_driver(account) as drv:
2275 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2276
2277 vm = drv.nova_server_get(vdu_id)
2278
2279 flavor_info = None
2280 if ('flavor' in vm) and ('id' in vm['flavor']):
2281 try:
2282 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2283 except Exception as e:
2284 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2285
2286 openstack_group_list = drv.nova_server_group_list()
2287 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2288 openstack_srv_volume_list = drv.nova_volume_list(vm['id'])
2289 vdu_info = RwcalOpenstackPlugin._fill_vdu_info(vm,
2290 flavor_info,
2291 account.openstack.mgmt_network,
2292 port_list,
2293 server_group,
2294 volume_list = openstack_srv_volume_list)
2295 if vdu_info.state == 'active':
2296 try:
2297 console_info = drv.nova_server_console(vdu_info.vdu_id)
2298 except Exception as e:
2299 pass
2300 else:
2301 vdu_info.console_url = console_info['console']['url']
2302 pass
2303
2304 return vdu_info
2305
2306
2307 @rwstatus(ret_on_failure=[None])
2308 def do_get_vdu_list(self, account):
2309 """Get information about all the virtual deployment units
2310
2311 Arguments:
2312 account - a cloud account
2313
2314 Returns:
2315 A list of objects of type RwcalYang.VDUInfoParams
2316 """
2317 vnf_resources = RwcalYang.VNFResources()
2318 with self._use_driver(account) as drv:
2319 vms = drv.nova_server_list()
2320 for vm in vms:
2321 port_list = drv.neutron_port_list(**{'device_id': vm['id']})
2322
2323 flavor_info = None
2324
2325 if ('flavor' in vm) and ('id' in vm['flavor']):
2326 try:
2327 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2328 except Exception as e:
2329 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2330
2331 else:
2332 flavor_info = None
2333
2334 openstack_group_list = drv.nova_server_group_list()
2335 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2336
2337 openstack_srv_volume_list = drv.nova_volume_list(vm['id'])
2338 vdu = RwcalOpenstackPlugin._fill_vdu_info(vm,
2339 flavor_info,
2340 account.openstack.mgmt_network,
2341 port_list,
2342 server_group,
2343 volume_list = openstack_srv_volume_list)
2344 if vdu.state == 'active':
2345 try:
2346 console_info = drv.nova_server_console(vdu.vdu_id)
2347 except Exception as e:
2348 pass
2349 else:
2350 vdu.console_url = console_info['console']['url']
2351 pass
2352 vnf_resources.vdu_info_list.append(vdu)
2353 return vnf_resources
2354
2355