merge from v1.0
[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.custom_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.custom_boot_data.custom_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 with self._use_driver(account) as drv:
1504 if c_point.has_field('security_group'):
1505 group = drv.neutron_security_group_by_name(c_point.security_group)
1506 if group is not None:
1507 kwargs['security_groups'] = [group['id']]
1508 return drv.neutron_port_create(**kwargs)
1509
1510 def _allocate_floating_ip(self, drv, pool_name):
1511 """
1512 Allocate a floating_ip. If unused floating_ip exists then its reused.
1513 Arguments:
1514 drv: OpenstackDriver instance
1515 pool_name: Floating IP pool name
1516
1517 Returns:
1518 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1519 """
1520
1521 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1522
1523 # if pool_name is not None:
1524 # ### Filter further based on IP address
1525 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1526
1527 # if not available_ip:
1528 # floating_ip = drv.nova_floating_ip_create(pool_name)
1529 # else:
1530 # floating_ip = available_ip[0]
1531
1532 floating_ip = drv.nova_floating_ip_create(pool_name)
1533 return floating_ip
1534
1535 def _match_vm_flavor(self, required, available):
1536 self.log.info("Matching VM Flavor attributes")
1537 if available.vcpu_count != required.vcpu_count:
1538 self.log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1539 required.vcpu_count,
1540 available.vcpu_count)
1541 return False
1542 if available.memory_mb != required.memory_mb:
1543 self.log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1544 required.memory_mb,
1545 available.memory_mb)
1546 return False
1547 if available.storage_gb != required.storage_gb:
1548 self.log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1549 required.storage_gb,
1550 available.storage_gb)
1551 return False
1552 self.log.debug("VM Flavor match found")
1553 return True
1554
1555 def _match_guest_epa(self, required, available):
1556 self.log.info("Matching Guest EPA attributes")
1557 if required.has_field('pcie_device'):
1558 self.log.debug("Matching pcie_device")
1559 if available.has_field('pcie_device') == False:
1560 self.log.debug("Matching pcie_device failed. Not available in flavor")
1561 return False
1562 else:
1563 for dev in required.pcie_device:
1564 if not [ d for d in available.pcie_device
1565 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
1566 self.log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
1567 return False
1568 elif available.has_field('pcie_device'):
1569 self.log.debug("Rejecting available flavor because pcie_device not required but available")
1570 return False
1571
1572
1573 if required.has_field('mempage_size'):
1574 self.log.debug("Matching mempage_size")
1575 if available.has_field('mempage_size') == False:
1576 self.log.debug("Matching mempage_size failed. Not available in flavor")
1577 return False
1578 else:
1579 if required.mempage_size != available.mempage_size:
1580 self.log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
1581 return False
1582 elif available.has_field('mempage_size'):
1583 self.log.debug("Rejecting available flavor because mempage_size not required but available")
1584 return False
1585
1586 if required.has_field('cpu_pinning_policy'):
1587 self.log.debug("Matching cpu_pinning_policy")
1588 if required.cpu_pinning_policy != 'ANY':
1589 if available.has_field('cpu_pinning_policy') == False:
1590 self.log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1591 return False
1592 else:
1593 if required.cpu_pinning_policy != available.cpu_pinning_policy:
1594 self.log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
1595 return False
1596 elif available.has_field('cpu_pinning_policy'):
1597 self.log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1598 return False
1599
1600 if required.has_field('cpu_thread_pinning_policy'):
1601 self.log.debug("Matching cpu_thread_pinning_policy")
1602 if available.has_field('cpu_thread_pinning_policy') == False:
1603 self.log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1604 return False
1605 else:
1606 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
1607 self.log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
1608 return False
1609 elif available.has_field('cpu_thread_pinning_policy'):
1610 self.log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1611 return False
1612
1613 if required.has_field('trusted_execution'):
1614 self.log.debug("Matching trusted_execution")
1615 if required.trusted_execution == True:
1616 if available.has_field('trusted_execution') == False:
1617 self.log.debug("Matching trusted_execution failed. Not available in flavor")
1618 return False
1619 else:
1620 if required.trusted_execution != available.trusted_execution:
1621 self.log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
1622 return False
1623 elif available.has_field('trusted_execution'):
1624 self.log.debug("Rejecting available flavor because trusted_execution not required but available")
1625 return False
1626
1627 if required.has_field('numa_node_policy'):
1628 self.log.debug("Matching numa_node_policy")
1629 if available.has_field('numa_node_policy') == False:
1630 self.log.debug("Matching numa_node_policy failed. Not available in flavor")
1631 return False
1632 else:
1633 if required.numa_node_policy.has_field('node_cnt'):
1634 self.log.debug("Matching numa_node_policy node_cnt")
1635 if available.numa_node_policy.has_field('node_cnt') == False:
1636 self.log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1637 return False
1638 else:
1639 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
1640 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)
1641 return False
1642 elif available.numa_node_policy.has_field('node_cnt'):
1643 self.log.debug("Rejecting available flavor because numa node count not required but available")
1644 return False
1645
1646 if required.numa_node_policy.has_field('mem_policy'):
1647 self.log.debug("Matching numa_node_policy mem_policy")
1648 if available.numa_node_policy.has_field('mem_policy') == False:
1649 self.log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1650 return False
1651 else:
1652 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
1653 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)
1654 return False
1655 elif available.numa_node_policy.has_field('mem_policy'):
1656 self.log.debug("Rejecting available flavor because num node mem_policy not required but available")
1657 return False
1658
1659 if required.numa_node_policy.has_field('node'):
1660 self.log.debug("Matching numa_node_policy nodes configuration")
1661 if available.numa_node_policy.has_field('node') == False:
1662 self.log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1663 return False
1664 for required_node in required.numa_node_policy.node:
1665 self.log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1666 numa_match = False
1667 for available_node in available.numa_node_policy.node:
1668 if required_node.id != available_node.id:
1669 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1670 continue
1671 if required_node.vcpu != available_node.vcpu:
1672 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1673 continue
1674 if required_node.memory_mb != available_node.memory_mb:
1675 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1676 continue
1677 numa_match = True
1678 if numa_match == False:
1679 return False
1680 elif available.numa_node_policy.has_field('node'):
1681 self.log.debug("Rejecting available flavor because numa nodes not required but available")
1682 return False
1683 elif available.has_field('numa_node_policy'):
1684 self.log.debug("Rejecting available flavor because numa_node_policy not required but available")
1685 return False
1686 self.log.info("Successful match for Guest EPA attributes")
1687 return True
1688
1689 def _match_vswitch_epa(self, required, available):
1690 self.log.debug("VSwitch EPA match found")
1691 return True
1692
1693 def _match_hypervisor_epa(self, required, available):
1694 self.log.debug("Hypervisor EPA match found")
1695 return True
1696
1697 def _match_host_epa(self, required, available):
1698 self.log.info("Matching Host EPA attributes")
1699 if required.has_field('cpu_model'):
1700 self.log.debug("Matching CPU model")
1701 if available.has_field('cpu_model') == False:
1702 self.log.debug("Matching CPU model failed. Not available in flavor")
1703 return False
1704 else:
1705 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1706 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1707 self.log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1708 return False
1709 elif available.has_field('cpu_model'):
1710 self.log.debug("Rejecting available flavor because cpu_model not required but available")
1711 return False
1712
1713 if required.has_field('cpu_arch'):
1714 self.log.debug("Matching CPU architecture")
1715 if available.has_field('cpu_arch') == False:
1716 self.log.debug("Matching CPU architecture failed. Not available in flavor")
1717 return False
1718 else:
1719 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1720 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1721 self.log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1722 return False
1723 elif available.has_field('cpu_arch'):
1724 self.log.debug("Rejecting available flavor because cpu_arch not required but available")
1725 return False
1726
1727 if required.has_field('cpu_vendor'):
1728 self.log.debug("Matching CPU vendor")
1729 if available.has_field('cpu_vendor') == False:
1730 self.log.debug("Matching CPU vendor failed. Not available in flavor")
1731 return False
1732 else:
1733 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1734 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1735 self.log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1736 return False
1737 elif available.has_field('cpu_vendor'):
1738 self.log.debug("Rejecting available flavor because cpu_vendor not required but available")
1739 return False
1740
1741 if required.has_field('cpu_socket_count'):
1742 self.log.debug("Matching CPU socket count")
1743 if available.has_field('cpu_socket_count') == False:
1744 self.log.debug("Matching CPU socket count failed. Not available in flavor")
1745 return False
1746 else:
1747 if required.cpu_socket_count != available.cpu_socket_count:
1748 self.log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1749 return False
1750 elif available.has_field('cpu_socket_count'):
1751 self.log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1752 return False
1753
1754 if required.has_field('cpu_core_count'):
1755 self.log.debug("Matching CPU core count")
1756 if available.has_field('cpu_core_count') == False:
1757 self.log.debug("Matching CPU core count failed. Not available in flavor")
1758 return False
1759 else:
1760 if required.cpu_core_count != available.cpu_core_count:
1761 self.log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1762 return False
1763 elif available.has_field('cpu_core_count'):
1764 self.log.debug("Rejecting available flavor because cpu_core_count not required but available")
1765 return False
1766
1767 if required.has_field('cpu_core_thread_count'):
1768 self.log.debug("Matching CPU core thread count")
1769 if available.has_field('cpu_core_thread_count') == False:
1770 self.log.debug("Matching CPU core thread count failed. Not available in flavor")
1771 return False
1772 else:
1773 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1774 self.log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1775 return False
1776 elif available.has_field('cpu_core_thread_count'):
1777 self.log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1778 return False
1779
1780 if required.has_field('cpu_feature'):
1781 self.log.debug("Matching CPU feature list")
1782 if available.has_field('cpu_feature') == False:
1783 self.log.debug("Matching CPU feature list failed. Not available in flavor")
1784 return False
1785 else:
1786 for feature in required.cpu_feature:
1787 if feature not in available.cpu_feature:
1788 self.log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1789 return False
1790 elif available.has_field('cpu_feature'):
1791 self.log.debug("Rejecting available flavor because cpu_feature not required but available")
1792 return False
1793 self.log.info("Successful match for Host EPA attributes")
1794 return True
1795
1796
1797 def _match_placement_group_inputs(self, required, available):
1798 self.log.info("Matching Host aggregate attributes")
1799
1800 if not required and not available:
1801 # Host aggregate not required and not available => success
1802 self.log.info("Successful match for Host Aggregate attributes")
1803 return True
1804 if required and available:
1805 # Host aggregate requested and available => Do a match and decide
1806 xx = [ x.as_dict() for x in required ]
1807 yy = [ y.as_dict() for y in available ]
1808 for i in xx:
1809 if i not in yy:
1810 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1811 return False
1812 self.log.info("Successful match for Host Aggregate attributes")
1813 return True
1814 else:
1815 # Either of following conditions => Failure
1816 # - Host aggregate required but not available
1817 # - Host aggregate not required but available
1818 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1819 return False
1820
1821 def match_epa_params(self, resource_info, request_params):
1822 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1823 getattr(resource_info, 'vm_flavor'))
1824 if result == False:
1825 self.log.debug("VM Flavor mismatched")
1826 return False
1827
1828 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1829 getattr(resource_info, 'guest_epa'))
1830 if result == False:
1831 self.log.debug("Guest EPA mismatched")
1832 return False
1833
1834 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1835 getattr(resource_info, 'vswitch_epa'))
1836 if result == False:
1837 self.log.debug("Vswitch EPA mismatched")
1838 return False
1839
1840 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1841 getattr(resource_info, 'hypervisor_epa'))
1842 if result == False:
1843 self.log.debug("Hypervisor EPA mismatched")
1844 return False
1845
1846 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1847 getattr(resource_info, 'host_epa'))
1848 if result == False:
1849 self.log.debug("Host EPA mismatched")
1850 return False
1851
1852 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1853 getattr(resource_info, 'host_aggregate'))
1854
1855 if result == False:
1856 self.log.debug("Host Aggregate mismatched")
1857 return False
1858
1859 return True
1860
1861 def _select_resource_flavor(self, account, vdu_init):
1862 """
1863 Select a existing flavor if it matches the request or create new flavor
1864 """
1865 flavor = RwcalYang.FlavorInfoItem()
1866 flavor.name = str(uuid.uuid4())
1867 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1868 epa_dict = {k: v for k, v in vdu_init.as_dict().items() if k in epa_types}
1869 flavor.from_dict(epa_dict)
1870
1871 rc, response = self.do_get_flavor_list(account)
1872 if rc != RwTypes.RwStatus.SUCCESS:
1873 self.log.error("Get-flavor-info-list operation failed for cloud account: %s",
1874 account.name)
1875 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account.name))
1876
1877 flavor_id = None
1878 flavor_list = response.flavorinfo_list
1879 self.log.debug("Received %d flavor information from RW.CAL", len(flavor_list))
1880 for flv in flavor_list:
1881 self.log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1882 vdu_init.name, flv)
1883 if self.match_epa_params(flv, vdu_init):
1884 self.log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1885 vdu_init.name, flv.name, flv.id)
1886 return flv.id
1887
1888 if account.openstack.dynamic_flavor_support is False:
1889 self.log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init.name)
1890 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1891 else:
1892 rc,flavor_id = self.do_create_flavor(account,flavor)
1893 if rc != RwTypes.RwStatus.SUCCESS:
1894 self.log.error("Create-flavor operation failed for cloud account: %s",
1895 account.name)
1896 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account.name))
1897 return flavor_id
1898
1899 def _create_vm(self, account, vduinfo, pci_assignement=None, server_group=None, port_list=None, network_list=None, imageinfo_list=None):
1900 """Create a new virtual machine.
1901
1902 Arguments:
1903 account - a cloud account
1904 vminfo - information that defines the type of VM to create
1905
1906 Returns:
1907 The image id
1908 """
1909 kwargs = {}
1910 kwargs['name'] = vduinfo.name
1911 kwargs['flavor_id'] = vduinfo.flavor_id
1912 if vduinfo.has_field('image_id'):
1913 kwargs['image_id'] = vduinfo.image_id
1914 else:
1915 kwargs['image_id'] = ""
1916
1917 with self._use_driver(account) as drv:
1918 ### If floating_ip is required and we don't have one, better fail before any further allocation
1919 if vduinfo.has_field('allocate_public_address') and vduinfo.allocate_public_address:
1920 if account.openstack.has_field('floating_ip_pool'):
1921 pool_name = account.openstack.floating_ip_pool
1922 else:
1923 pool_name = None
1924 floating_ip = self._allocate_floating_ip(drv, pool_name)
1925 else:
1926 floating_ip = None
1927
1928 if vduinfo.has_field('vdu_init') and vduinfo.vdu_init.has_field('userdata'):
1929 kwargs['userdata'] = vduinfo.vdu_init.userdata
1930 else:
1931 kwargs['userdata'] = ''
1932
1933 if account.openstack.security_groups:
1934 kwargs['security_groups'] = account.openstack.security_groups
1935
1936 kwargs['port_list'] = port_list
1937 kwargs['network_list'] = network_list
1938
1939 metadata = {}
1940 files = {}
1941 config_drive = False
1942 # Add all metadata related fields
1943 if vduinfo.has_field('node_id'):
1944 metadata['node_id'] = vduinfo.node_id
1945 if pci_assignement is not None:
1946 metadata['pci_assignement'] = pci_assignement
1947 if vduinfo.has_field('custom_boot_data'):
1948 if vduinfo.custom_boot_data.has_field('custom_meta_data'):
1949 for custom_meta_item in vduinfo.custom_boot_data.custom_meta_data:
1950 if custom_meta_item.data_type == "STRING":
1951 metadata[custom_meta_item.name] = custom_meta_item.value
1952 elif custom_meta_item.data_type == "JSON":
1953 metadata[custom_meta_item.name] = tornado.escape.json_decode(custom_meta_item.value)
1954 else:
1955 raise OpenstackCALOperationFailure("Create-vdu operation failed. Unsupported data-type {} for custom-meta-data name {} ".format(custom_meta_item.data_type, custom_meta_item.name))
1956 if vduinfo.custom_boot_data.has_field('custom_config_files'):
1957 for custom_config_file in vduinfo.custom_boot_data.custom_config_files:
1958 files[custom_config_file.dest] = custom_config_file.source
1959
1960 if vduinfo.custom_boot_data.has_field('custom_drive'):
1961 if vduinfo.custom_boot_data.custom_drive is True:
1962 config_drive = True
1963
1964 kwargs['metadata'] = metadata
1965 kwargs['files'] = files
1966 kwargs['config_drive'] = config_drive
1967
1968 if vduinfo.has_field('availability_zone') and vduinfo.availability_zone.has_field('name'):
1969 kwargs['availability_zone'] = vduinfo.availability_zone
1970 else:
1971 kwargs['availability_zone'] = None
1972
1973 if server_group is not None:
1974 kwargs['scheduler_hints'] = {'group': server_group}
1975 else:
1976 kwargs['scheduler_hints'] = None
1977
1978 kwargs['block_device_mapping_v2'] = None
1979 vol_metadata = False
1980 if vduinfo.has_field('volumes') :
1981 kwargs['block_device_mapping_v2'] = []
1982 with self._use_driver(account) as drv:
1983 # Only support image->volume
1984 for volume in vduinfo.volumes:
1985 block_map = dict()
1986 block_map['boot_index'] = volume.boot_params.boot_priority
1987 if "image" in volume:
1988 # Support image->volume
1989 # Match retrived image info with volume based image name and checksum
1990 if volume.image is not None:
1991 matching_images = [img for img in imageinfo_list if img['name'] == volume.image]
1992 if volume.image_checksum is not None:
1993 matching_images = [img for img in matching_images if img['checksum'] == volume.image_checksum]
1994 img_id = matching_images[0]['id']
1995 if img_id is None:
1996 raise OpenstackCALOperationFailure("Create-vdu operation failed. Volume image not found for name {} checksum {}".format(volume.name, volume.checksum))
1997 block_map['uuid'] = img_id
1998 block_map['source_type'] = "image"
1999 else:
2000 block_map['source_type'] = "blank"
2001
2002 block_map['device_name'] = volume.name
2003 block_map['destination_type'] = "volume"
2004 block_map['volume_size'] = volume.size
2005 block_map['delete_on_termination'] = True
2006 if volume.guest_params.has_field('device_type') and volume.guest_params.device_type == 'cdrom':
2007 block_map['device_type'] = 'cdrom'
2008 if volume.guest_params.has_field('device_bus') and volume.guest_params.device_bus == 'ide':
2009 block_map['disk_bus'] = 'ide'
2010 kwargs['block_device_mapping_v2'].append(block_map)
2011
2012
2013 with self._use_driver(account) as drv:
2014 vm_id = drv.nova_server_create(**kwargs)
2015 if floating_ip:
2016 self.prepare_vdu_on_boot(account, vm_id, floating_ip, vduinfo.volumes)
2017
2018 return vm_id
2019
2020 def get_openstack_image_info(self, account, image_name, image_checksum=None):
2021 self.log.debug("Looking up image id for image name %s and checksum %s on cloud account: %s",
2022 image_name, image_checksum, account.name
2023 )
2024
2025 image_list = []
2026 with self._use_driver(account) as drv:
2027 image_list = drv.glance_image_list()
2028 matching_images = [img for img in image_list if img['name'] == image_name]
2029
2030 # If the image checksum was filled in then further filter the images by the checksum
2031 if image_checksum is not None:
2032 matching_images = [img for img in matching_images if img['checksum'] == image_checksum]
2033 else:
2034 self.log.warning("Image checksum not provided. Lookup using image name (%s) only.",
2035 image_name)
2036
2037 if len(matching_images) == 0:
2038 raise ResMgrCALOperationFailure("Could not find image name {} (using checksum: {}) for cloud account: {}".format(
2039 image_name, image_checksum, account.name
2040 ))
2041
2042 elif len(matching_images) > 1:
2043 unique_checksums = {i.checksum for i in matching_images}
2044 if len(unique_checksums) > 1:
2045 msg = ("Too many images with different checksums matched "
2046 "image name of %s for cloud account: %s" % (image_name, account.name))
2047 raise ResMgrCALOperationFailure(msg)
2048
2049 return matching_images[0]
2050
2051 @rwcalstatus(ret_on_failure=[""])
2052 def do_create_vdu(self, account, vdu_init):
2053 """Create a new virtual deployment unit
2054
2055 Arguments:
2056 account - a cloud account
2057 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
2058
2059 Returns:
2060 The vdu_id
2061 """
2062 ### First create required number of ports aka connection points
2063 # Add the mgmt_ntwk by default.
2064 mgmt_network_id = None
2065 with self._use_driver(account) as drv:
2066 mgmt_network_id = drv._mgmt_network_id
2067 ### If floating_ip is required and we don't have one, better fail before any further allocation
2068 if vdu_init.has_field('allocate_public_address') and vdu_init.allocate_public_address:
2069 if account.openstack.has_field('floating_ip_pool'):
2070 pool_name = account.openstack.floating_ip_pool
2071 else:
2072 pool_name = None
2073 floating_ip = self._allocate_floating_ip(drv, pool_name)
2074 else:
2075 floating_ip = None
2076
2077 port_list = []
2078 network_list = []
2079 imageinfo_list = []
2080 is_explicit_mgmt_defined = False
2081 for c_point in vdu_init.connection_points:
2082 # if the user has specified explicit mgmt_network connection point
2083 # then remove the mgmt_network from the VM list
2084 if c_point.virtual_link_id == mgmt_network_id:
2085 is_explicit_mgmt_defined = True
2086 if c_point.virtual_link_id in network_list:
2087 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"
2088 else:
2089 network_list.append(c_point.virtual_link_id)
2090 port_id = self._create_connection_point(account, c_point)
2091 port_list.append(port_id)
2092
2093 if not vdu_init.has_field('flavor_id'):
2094 vdu_init.flavor_id = self._select_resource_flavor(account,vdu_init)
2095
2096 ### Obtain all images for volumes and perform validations
2097 if vdu_init.has_field('volumes'):
2098 for volume in vdu_init.volumes:
2099 if "image" in volume:
2100 image_checksum = volume.image_checksum if volume.has_field("image_checksum") else None
2101 image_info = self.get_openstack_image_info(account, volume.image, image_checksum)
2102 imageinfo_list.append(image_info)
2103 elif vdu_init.has_field('image_id'):
2104 with self._use_driver(account) as drv:
2105 image_info = drv.glance_image_get(vdu_init.image_id)
2106 imageinfo_list.append(image_info)
2107
2108 if not imageinfo_list:
2109 err_str = ("VDU has no image information")
2110 self.log.error(err_str)
2111 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2112
2113 ### Check VDU Virtual Interface type and make sure VM with property exists
2114 if vdu_init.connection_points:
2115 ### All virtual interfaces need to be of the same type for Openstack Accounts
2116 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)):
2117 ### We have a mix of E1000 & VIRTIO/SR_IPOV virtual interface types in the VDU, abort instantiation.
2118 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO/SR_IOV & E1000."
2119
2120 ## 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,
2121 ### we shall assume that all images need to have similar properties
2122 for img_info in imageinfo_list:
2123
2124 virt_intf_type = vdu_init.connection_points[0].type_yang
2125 if virt_intf_type == 'E1000':
2126 if 'hw_vif_model' in img_info and img_info.hw_vif_model == 'e1000':
2127 self.log.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
2128 else:
2129 err_str = ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info.name)
2130 self.log.error(err_str)
2131 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2132 elif virt_intf_type == 'VIRTIO' or virt_intf_type == 'SR_IOV':
2133 if 'hw_vif_model' in img_info:
2134 err_str = ("VDU has Virtual Interface %s, but image '%s' has hw_vif_model mismatch" % virt_intf_type,img_info.name)
2135 self.log.error(err_str)
2136 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2137 else:
2138 self.log.debug("VDU has Virtual Interface %s, found matching image" % virt_intf_type)
2139 else:
2140 err_str = ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type)
2141 self.log.error(err_str)
2142 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2143
2144 with self._use_driver(account) as drv:
2145 ### Now Create VM
2146 vm_network_list = []
2147 if not is_explicit_mgmt_defined:
2148 vm_network_list.append(drv._mgmt_network_id)
2149
2150 if vdu_init.has_field('volumes'):
2151 # Only combination supported: Image->Volume
2152 for volume in vdu_init.volumes:
2153 if "volume" in volume:
2154 err_str = ("VDU Volume source not supported yet")
2155 self.log.error(err_str)
2156 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2157 if "guest_params" not in volume:
2158 err_str = ("VDU Volume destination parameters '%s' not defined")
2159 self.log.error(err_str)
2160 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2161 if not volume.guest_params.has_field('device_type'):
2162 err_str = ("VDU Volume destination type '%s' not defined")
2163 self.log.error(err_str)
2164 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2165 if volume.guest_params.device_type not in ['disk', 'cdrom'] :
2166 err_str = ("VDU Volume destination type '%s' not supported" % volume.guest_params.device_type)
2167 self.log.error(err_str)
2168 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2169
2170
2171 server_group = None
2172 if vdu_init.has_field('server_group'):
2173 ### Get list of server group in openstack for name->id mapping
2174 openstack_group_list = drv.nova_server_group_list()
2175 group_id = [ i['id'] for i in openstack_group_list if i['name'] == vdu_init.server_group.name]
2176 if len(group_id) != 1:
2177 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]))
2178 server_group = group_id[0]
2179
2180 pci_assignement = self.prepare_vpci_metadata(drv, vdu_init)
2181 if pci_assignement != '':
2182 vm.user_tags.pci_assignement = pci_assignement
2183
2184 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)
2185 return vm_id
2186
2187 def prepare_vpci_metadata(self, drv, vdu_init):
2188 pci_assignement = ''
2189 ### TEF specific metadata creation for
2190 virtio_vpci = []
2191 sriov_vpci = []
2192 virtio_meta = ''
2193 sriov_meta = ''
2194 ### For MGMT interface
2195 if vdu_init.has_field('mgmt_vpci'):
2196 xx = 'u\''+ drv._mgmt_network_id + '\' :[[u\'' + vdu_init.mgmt_vpci + '\', ' + '\'\']]'
2197 virtio_vpci.append(xx)
2198
2199 for c_point in vdu_init.connection_points:
2200 if c_point.has_field('vpci'):
2201 if c_point.has_field('vpci') and c_point.type_yang == 'VIRTIO':
2202 xx = 'u\''+c_point.virtual_link_id + '\' :[[u\'' + c_point.vpci + '\', ' + '\'\']]'
2203 virtio_vpci.append(xx)
2204 elif c_point.has_field('vpci') and c_point.type_yang == 'SR_IOV':
2205 xx = '[u\'' + c_point.vpci + '\', ' + '\'\']'
2206 sriov_vpci.append(xx)
2207
2208 if virtio_vpci:
2209 virtio_meta += ','.join(virtio_vpci)
2210
2211 if sriov_vpci:
2212 sriov_meta = 'u\'VF\': ['
2213 sriov_meta += ','.join(sriov_vpci)
2214 sriov_meta += ']'
2215
2216 if virtio_meta != '':
2217 pci_assignement += virtio_meta
2218 pci_assignement += ','
2219
2220 if sriov_meta != '':
2221 pci_assignement += sriov_meta
2222
2223 if pci_assignement != '':
2224 pci_assignement = '{' + pci_assignement + '}'
2225
2226 return pci_assignement
2227
2228
2229
2230 def prepare_vdu_on_boot(self, account, server_id, floating_ip, volumes=None):
2231 cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url,
2232 username = account.openstack.key,
2233 password = account.openstack.secret,
2234 tenant_name = account.openstack.tenant,
2235 mgmt_network = account.openstack.mgmt_network,
2236 server_id = server_id)
2237
2238 if floating_ip is not None:
2239 cmd += (" --floating_ip "+ floating_ip.ip)
2240
2241 vol_metadata = False
2242 if volumes is not None:
2243 for volume in volumes:
2244 if volume.guest_params.has_field('custom_meta_data'):
2245 vol_metadata = True
2246 break
2247
2248 if vol_metadata is True:
2249 tmp_file = None
2250 with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_file:
2251 vol_list = list()
2252 for volume in volumes:
2253 vol_dict = volume.as_dict()
2254 vol_list.append(vol_dict)
2255
2256 yaml.dump(vol_list, tmp_file)
2257 cmd += (" --vol_metadata {}").format(tmp_file.name)
2258
2259 exec_path = 'python3 ' + os.path.dirname(openstack_drv.__file__)
2260 exec_cmd = exec_path+'/'+cmd
2261 self.log.info("Running command: %s" %(exec_cmd))
2262 subprocess.call(exec_cmd, shell=True)
2263
2264 @rwstatus
2265 def do_modify_vdu(self, account, vdu_modify):
2266 """Modify Properties of existing virtual deployment unit
2267
2268 Arguments:
2269 account - a cloud account
2270 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2271 """
2272 ### First create required number of ports aka connection points
2273 port_list = []
2274 network_list = []
2275 for c_point in vdu_modify.connection_points_add:
2276 if c_point.virtual_link_id in network_list:
2277 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"
2278 else:
2279 network_list.append(c_point.virtual_link_id)
2280 port_id = self._create_connection_point(account, c_point)
2281 port_list.append(port_id)
2282
2283 ### Now add the ports to VM
2284 for port_id in port_list:
2285 with self._use_driver(account) as drv:
2286 drv.nova_server_add_port(vdu_modify.vdu_id, port_id)
2287
2288 ### Delete the requested connection_points
2289 for c_point in vdu_modify.connection_points_remove:
2290 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
2291
2292 if vdu_modify.has_field('image_id'):
2293 with self._use_driver(account) as drv:
2294 drv.nova_server_rebuild(vdu_modify.vdu_id, vdu_modify.image_id)
2295
2296
2297 @rwstatus
2298 def do_delete_vdu(self, account, vdu_id):
2299 """Delete a virtual deployment unit
2300
2301 Arguments:
2302 account - a cloud account
2303 vdu_id - id for the vdu to be deleted
2304
2305 Returns:
2306 None
2307 """
2308 if not vdu_id:
2309 self.log.error("empty vdu_id during the vdu deletion")
2310 return
2311
2312 with self._use_driver(account) as drv:
2313 ### Get list of floating_ips associated with this instance and delete them
2314 floating_ips = [ f for f in drv.nova_floating_ip_list() if f.instance_id == vdu_id ]
2315 for f in floating_ips:
2316 drv.nova_drv.floating_ip_delete(f)
2317
2318 ### Get list of port on VM and delete them.
2319 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2320
2321 for port in port_list:
2322 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
2323 self.do_delete_port(account, port['id'], no_rwstatus=True)
2324
2325 self.do_delete_vm(account, vdu_id, no_rwstatus=True)
2326
2327
2328 @rwstatus(ret_on_failure=[None])
2329 def do_get_vdu(self, account, vdu_id):
2330 """Get information about a virtual deployment unit.
2331
2332 Arguments:
2333 account - a cloud account
2334 vdu_id - id for the vdu
2335
2336 Returns:
2337 Object of type RwcalYang.VDUInfoParams
2338 """
2339 with self._use_driver(account) as drv:
2340 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2341
2342 vm = drv.nova_server_get(vdu_id)
2343
2344 flavor_info = None
2345 if ('flavor' in vm) and ('id' in vm['flavor']):
2346 try:
2347 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2348 except Exception as e:
2349 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2350
2351 openstack_group_list = drv.nova_server_group_list()
2352 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2353 openstack_srv_volume_list = drv.nova_volume_list(vm['id'])
2354 vdu_info = RwcalOpenstackPlugin._fill_vdu_info(drv, vm,
2355 flavor_info,
2356 account.openstack.mgmt_network,
2357 port_list,
2358 server_group,
2359 volume_list = openstack_srv_volume_list)
2360 if vdu_info.state == 'active':
2361 try:
2362 console_info = drv.nova_server_console(vdu_info.vdu_id)
2363 except Exception as e:
2364 pass
2365 else:
2366 vdu_info.console_url = console_info['console']['url']
2367 pass
2368
2369 return vdu_info
2370
2371
2372 @rwstatus(ret_on_failure=[None])
2373 def do_get_vdu_list(self, account):
2374 """Get information about all the virtual deployment units
2375
2376 Arguments:
2377 account - a cloud account
2378
2379 Returns:
2380 A list of objects of type RwcalYang.VDUInfoParams
2381 """
2382 vnf_resources = RwcalYang.VNFResources()
2383 with self._use_driver(account) as drv:
2384 vms = drv.nova_server_list()
2385 for vm in vms:
2386 port_list = drv.neutron_port_list(**{'device_id': vm['id']})
2387
2388 flavor_info = None
2389
2390 if ('flavor' in vm) and ('id' in vm['flavor']):
2391 try:
2392 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2393 except Exception as e:
2394 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2395
2396 else:
2397 flavor_info = None
2398
2399 openstack_group_list = drv.nova_server_group_list()
2400 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2401
2402 openstack_srv_volume_list = drv.nova_volume_list(vm['id'])
2403 vdu = RwcalOpenstackPlugin._fill_vdu_info(drv, vm,
2404 flavor_info,
2405 account.openstack.mgmt_network,
2406 port_list,
2407 server_group,
2408 volume_list = openstack_srv_volume_list)
2409 if vdu.state == 'active':
2410 try:
2411 console_info = drv.nova_server_console(vdu.vdu_id)
2412 except Exception as e:
2413 pass
2414 else:
2415 vdu.console_url = console_info['console']['url']
2416 pass
2417 vnf_resources.vdu_info_list.append(vdu)
2418 return vnf_resources
2419
2420