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