3794ce5ae63cc6ba2cc696546d94d89d505d4e18
[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 'port_security_enabled' in c_point:
1505 kwargs['port_security_enabled'] = c_point.port_security_enabled
1506
1507 with self._use_driver(account) as drv:
1508 if c_point.has_field('security_group'):
1509 group = drv.neutron_security_group_by_name(c_point.security_group)
1510 if group is not None:
1511 kwargs['security_groups'] = [group['id']]
1512 return drv.neutron_port_create(**kwargs)
1513
1514 def _allocate_floating_ip(self, drv, pool_name):
1515 """
1516 Allocate a floating_ip. If unused floating_ip exists then its reused.
1517 Arguments:
1518 drv: OpenstackDriver instance
1519 pool_name: Floating IP pool name
1520
1521 Returns:
1522 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1523 """
1524
1525 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1526
1527 # if pool_name is not None:
1528 # ### Filter further based on IP address
1529 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1530
1531 # if not available_ip:
1532 # floating_ip = drv.nova_floating_ip_create(pool_name)
1533 # else:
1534 # floating_ip = available_ip[0]
1535
1536 floating_ip = drv.nova_floating_ip_create(pool_name)
1537 return floating_ip
1538
1539 def _match_vm_flavor(self, required, available):
1540 self.log.info("Matching VM Flavor attributes")
1541 if available.vcpu_count != required.vcpu_count:
1542 self.log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1543 required.vcpu_count,
1544 available.vcpu_count)
1545 return False
1546 if available.memory_mb != required.memory_mb:
1547 self.log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1548 required.memory_mb,
1549 available.memory_mb)
1550 return False
1551 if available.storage_gb != required.storage_gb:
1552 self.log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1553 required.storage_gb,
1554 available.storage_gb)
1555 return False
1556 self.log.debug("VM Flavor match found")
1557 return True
1558
1559 def _match_guest_epa(self, required, available):
1560 self.log.info("Matching Guest EPA attributes")
1561 if required.has_field('pcie_device'):
1562 self.log.debug("Matching pcie_device")
1563 if available.has_field('pcie_device') == False:
1564 self.log.debug("Matching pcie_device failed. Not available in flavor")
1565 return False
1566 else:
1567 for dev in required.pcie_device:
1568 if not [ d for d in available.pcie_device
1569 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
1570 self.log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
1571 return False
1572 elif available.has_field('pcie_device'):
1573 self.log.debug("Rejecting available flavor because pcie_device not required but available")
1574 return False
1575
1576
1577 if required.has_field('mempage_size'):
1578 self.log.debug("Matching mempage_size")
1579 if available.has_field('mempage_size') == False:
1580 self.log.debug("Matching mempage_size failed. Not available in flavor")
1581 return False
1582 else:
1583 if required.mempage_size != available.mempage_size:
1584 self.log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
1585 return False
1586 elif available.has_field('mempage_size'):
1587 self.log.debug("Rejecting available flavor because mempage_size not required but available")
1588 return False
1589
1590 if required.has_field('cpu_pinning_policy'):
1591 self.log.debug("Matching cpu_pinning_policy")
1592 if required.cpu_pinning_policy != 'ANY':
1593 if available.has_field('cpu_pinning_policy') == False:
1594 self.log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1595 return False
1596 else:
1597 if required.cpu_pinning_policy != available.cpu_pinning_policy:
1598 self.log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
1599 return False
1600 elif available.has_field('cpu_pinning_policy'):
1601 self.log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1602 return False
1603
1604 if required.has_field('cpu_thread_pinning_policy'):
1605 self.log.debug("Matching cpu_thread_pinning_policy")
1606 if available.has_field('cpu_thread_pinning_policy') == False:
1607 self.log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1608 return False
1609 else:
1610 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
1611 self.log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
1612 return False
1613 elif available.has_field('cpu_thread_pinning_policy'):
1614 self.log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1615 return False
1616
1617 if required.has_field('trusted_execution'):
1618 self.log.debug("Matching trusted_execution")
1619 if required.trusted_execution == True:
1620 if available.has_field('trusted_execution') == False:
1621 self.log.debug("Matching trusted_execution failed. Not available in flavor")
1622 return False
1623 else:
1624 if required.trusted_execution != available.trusted_execution:
1625 self.log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
1626 return False
1627 elif available.has_field('trusted_execution'):
1628 self.log.debug("Rejecting available flavor because trusted_execution not required but available")
1629 return False
1630
1631 if required.has_field('numa_node_policy'):
1632 self.log.debug("Matching numa_node_policy")
1633 if available.has_field('numa_node_policy') == False:
1634 self.log.debug("Matching numa_node_policy failed. Not available in flavor")
1635 return False
1636 else:
1637 if required.numa_node_policy.has_field('node_cnt'):
1638 self.log.debug("Matching numa_node_policy node_cnt")
1639 if available.numa_node_policy.has_field('node_cnt') == False:
1640 self.log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1641 return False
1642 else:
1643 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
1644 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)
1645 return False
1646 elif available.numa_node_policy.has_field('node_cnt'):
1647 self.log.debug("Rejecting available flavor because numa node count not required but available")
1648 return False
1649
1650 if required.numa_node_policy.has_field('mem_policy'):
1651 self.log.debug("Matching numa_node_policy mem_policy")
1652 if available.numa_node_policy.has_field('mem_policy') == False:
1653 self.log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1654 return False
1655 else:
1656 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
1657 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)
1658 return False
1659 elif available.numa_node_policy.has_field('mem_policy'):
1660 self.log.debug("Rejecting available flavor because num node mem_policy not required but available")
1661 return False
1662
1663 if required.numa_node_policy.has_field('node'):
1664 self.log.debug("Matching numa_node_policy nodes configuration")
1665 if available.numa_node_policy.has_field('node') == False:
1666 self.log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1667 return False
1668 for required_node in required.numa_node_policy.node:
1669 self.log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1670 numa_match = False
1671 for available_node in available.numa_node_policy.node:
1672 if required_node.id != available_node.id:
1673 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1674 continue
1675 if required_node.vcpu != available_node.vcpu:
1676 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1677 continue
1678 if required_node.memory_mb != available_node.memory_mb:
1679 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1680 continue
1681 numa_match = True
1682 if numa_match == False:
1683 return False
1684 elif available.numa_node_policy.has_field('node'):
1685 self.log.debug("Rejecting available flavor because numa nodes not required but available")
1686 return False
1687 elif available.has_field('numa_node_policy'):
1688 self.log.debug("Rejecting available flavor because numa_node_policy not required but available")
1689 return False
1690 self.log.info("Successful match for Guest EPA attributes")
1691 return True
1692
1693 def _match_vswitch_epa(self, required, available):
1694 self.log.debug("VSwitch EPA match found")
1695 return True
1696
1697 def _match_hypervisor_epa(self, required, available):
1698 self.log.debug("Hypervisor EPA match found")
1699 return True
1700
1701 def _match_host_epa(self, required, available):
1702 self.log.info("Matching Host EPA attributes")
1703 if required.has_field('cpu_model'):
1704 self.log.debug("Matching CPU model")
1705 if available.has_field('cpu_model') == False:
1706 self.log.debug("Matching CPU model failed. Not available in flavor")
1707 return False
1708 else:
1709 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1710 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1711 self.log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1712 return False
1713 elif available.has_field('cpu_model'):
1714 self.log.debug("Rejecting available flavor because cpu_model not required but available")
1715 return False
1716
1717 if required.has_field('cpu_arch'):
1718 self.log.debug("Matching CPU architecture")
1719 if available.has_field('cpu_arch') == False:
1720 self.log.debug("Matching CPU architecture failed. Not available in flavor")
1721 return False
1722 else:
1723 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1724 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1725 self.log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1726 return False
1727 elif available.has_field('cpu_arch'):
1728 self.log.debug("Rejecting available flavor because cpu_arch not required but available")
1729 return False
1730
1731 if required.has_field('cpu_vendor'):
1732 self.log.debug("Matching CPU vendor")
1733 if available.has_field('cpu_vendor') == False:
1734 self.log.debug("Matching CPU vendor failed. Not available in flavor")
1735 return False
1736 else:
1737 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1738 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1739 self.log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1740 return False
1741 elif available.has_field('cpu_vendor'):
1742 self.log.debug("Rejecting available flavor because cpu_vendor not required but available")
1743 return False
1744
1745 if required.has_field('cpu_socket_count'):
1746 self.log.debug("Matching CPU socket count")
1747 if available.has_field('cpu_socket_count') == False:
1748 self.log.debug("Matching CPU socket count failed. Not available in flavor")
1749 return False
1750 else:
1751 if required.cpu_socket_count != available.cpu_socket_count:
1752 self.log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1753 return False
1754 elif available.has_field('cpu_socket_count'):
1755 self.log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1756 return False
1757
1758 if required.has_field('cpu_core_count'):
1759 self.log.debug("Matching CPU core count")
1760 if available.has_field('cpu_core_count') == False:
1761 self.log.debug("Matching CPU core count failed. Not available in flavor")
1762 return False
1763 else:
1764 if required.cpu_core_count != available.cpu_core_count:
1765 self.log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1766 return False
1767 elif available.has_field('cpu_core_count'):
1768 self.log.debug("Rejecting available flavor because cpu_core_count not required but available")
1769 return False
1770
1771 if required.has_field('cpu_core_thread_count'):
1772 self.log.debug("Matching CPU core thread count")
1773 if available.has_field('cpu_core_thread_count') == False:
1774 self.log.debug("Matching CPU core thread count failed. Not available in flavor")
1775 return False
1776 else:
1777 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1778 self.log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1779 return False
1780 elif available.has_field('cpu_core_thread_count'):
1781 self.log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1782 return False
1783
1784 if required.has_field('cpu_feature'):
1785 self.log.debug("Matching CPU feature list")
1786 if available.has_field('cpu_feature') == False:
1787 self.log.debug("Matching CPU feature list failed. Not available in flavor")
1788 return False
1789 else:
1790 for feature in required.cpu_feature:
1791 if feature not in available.cpu_feature:
1792 self.log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1793 return False
1794 elif available.has_field('cpu_feature'):
1795 self.log.debug("Rejecting available flavor because cpu_feature not required but available")
1796 return False
1797 self.log.info("Successful match for Host EPA attributes")
1798 return True
1799
1800
1801 def _match_placement_group_inputs(self, required, available):
1802 self.log.info("Matching Host aggregate attributes")
1803
1804 if not required and not available:
1805 # Host aggregate not required and not available => success
1806 self.log.info("Successful match for Host Aggregate attributes")
1807 return True
1808 if required and available:
1809 # Host aggregate requested and available => Do a match and decide
1810 xx = [ x.as_dict() for x in required ]
1811 yy = [ y.as_dict() for y in available ]
1812 for i in xx:
1813 if i not in yy:
1814 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1815 return False
1816 self.log.info("Successful match for Host Aggregate attributes")
1817 return True
1818 else:
1819 # Either of following conditions => Failure
1820 # - Host aggregate required but not available
1821 # - Host aggregate not required but available
1822 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1823 return False
1824
1825 def match_epa_params(self, resource_info, request_params):
1826 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1827 getattr(resource_info, 'vm_flavor'))
1828 if result == False:
1829 self.log.debug("VM Flavor mismatched")
1830 return False
1831
1832 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1833 getattr(resource_info, 'guest_epa'))
1834 if result == False:
1835 self.log.debug("Guest EPA mismatched")
1836 return False
1837
1838 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1839 getattr(resource_info, 'vswitch_epa'))
1840 if result == False:
1841 self.log.debug("Vswitch EPA mismatched")
1842 return False
1843
1844 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1845 getattr(resource_info, 'hypervisor_epa'))
1846 if result == False:
1847 self.log.debug("Hypervisor EPA mismatched")
1848 return False
1849
1850 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1851 getattr(resource_info, 'host_epa'))
1852 if result == False:
1853 self.log.debug("Host EPA mismatched")
1854 return False
1855
1856 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1857 getattr(resource_info, 'host_aggregate'))
1858
1859 if result == False:
1860 self.log.debug("Host Aggregate mismatched")
1861 return False
1862
1863 return True
1864
1865 def _select_resource_flavor(self, account, vdu_init):
1866 """
1867 Select a existing flavor if it matches the request or create new flavor
1868 """
1869 flavor = RwcalYang.FlavorInfoItem()
1870 flavor.name = str(uuid.uuid4())
1871 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1872 epa_dict = {k: v for k, v in vdu_init.as_dict().items() if k in epa_types}
1873 flavor.from_dict(epa_dict)
1874
1875 rc, response = self.do_get_flavor_list(account)
1876 if rc != RwTypes.RwStatus.SUCCESS:
1877 self.log.error("Get-flavor-info-list operation failed for cloud account: %s",
1878 account.name)
1879 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account.name))
1880
1881 flavor_id = None
1882 flavor_list = response.flavorinfo_list
1883 self.log.debug("Received %d flavor information from RW.CAL", len(flavor_list))
1884 for flv in flavor_list:
1885 self.log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1886 vdu_init.name, flv)
1887 if self.match_epa_params(flv, vdu_init):
1888 self.log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1889 vdu_init.name, flv.name, flv.id)
1890 return flv.id
1891
1892 if account.openstack.dynamic_flavor_support is False:
1893 self.log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init.name)
1894 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1895 else:
1896 rc,flavor_id = self.do_create_flavor(account,flavor)
1897 if rc != RwTypes.RwStatus.SUCCESS:
1898 self.log.error("Create-flavor operation failed for cloud account: %s",
1899 account.name)
1900 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account.name))
1901 return flavor_id
1902
1903 def _create_vm(self, account, vduinfo, pci_assignement=None, server_group=None, port_list=None, network_list=None, imageinfo_list=None):
1904 """Create a new virtual machine.
1905
1906 Arguments:
1907 account - a cloud account
1908 vminfo - information that defines the type of VM to create
1909
1910 Returns:
1911 The image id
1912 """
1913 kwargs = {}
1914 kwargs['name'] = vduinfo.name
1915 kwargs['flavor_id'] = vduinfo.flavor_id
1916 if vduinfo.has_field('image_id'):
1917 kwargs['image_id'] = vduinfo.image_id
1918 else:
1919 kwargs['image_id'] = ""
1920
1921 with self._use_driver(account) as drv:
1922 ### If floating_ip is required and we don't have one, better fail before any further allocation
1923 if vduinfo.has_field('allocate_public_address') and vduinfo.allocate_public_address:
1924 if account.openstack.has_field('floating_ip_pool'):
1925 pool_name = account.openstack.floating_ip_pool
1926 else:
1927 pool_name = None
1928 floating_ip = self._allocate_floating_ip(drv, pool_name)
1929 else:
1930 floating_ip = None
1931
1932 if vduinfo.has_field('vdu_init') and vduinfo.vdu_init.has_field('userdata'):
1933 kwargs['userdata'] = vduinfo.vdu_init.userdata
1934 else:
1935 kwargs['userdata'] = ''
1936
1937 if account.openstack.security_groups:
1938 kwargs['security_groups'] = account.openstack.security_groups
1939
1940 kwargs['port_list'] = port_list
1941 kwargs['network_list'] = network_list
1942
1943 metadata = {}
1944 files = {}
1945 config_drive = False
1946 # Add all metadata related fields
1947 if vduinfo.has_field('node_id'):
1948 metadata['node_id'] = vduinfo.node_id
1949 if pci_assignement is not None:
1950 metadata['pci_assignement'] = pci_assignement
1951 if vduinfo.has_field('supplemental_boot_data'):
1952 if vduinfo.supplemental_boot_data.has_field('custom_meta_data'):
1953 for custom_meta_item in vduinfo.supplemental_boot_data.custom_meta_data:
1954 if custom_meta_item.data_type == "STRING":
1955 metadata[custom_meta_item.name] = custom_meta_item.value
1956 elif custom_meta_item.data_type == "JSON":
1957 metadata[custom_meta_item.name] = tornado.escape.json_decode(custom_meta_item.value)
1958 else:
1959 raise OpenstackCALOperationFailure("Create-vdu operation failed. Unsupported data-type {} for custom-meta-data name {} ".format(custom_meta_item.data_type, custom_meta_item.name))
1960 if vduinfo.supplemental_boot_data.has_field('config_file'):
1961 for custom_config_file in vduinfo.supplemental_boot_data.config_file:
1962 files[custom_config_file.dest] = custom_config_file.source
1963
1964 if vduinfo.supplemental_boot_data.has_field('boot_data_drive'):
1965 if vduinfo.supplemental_boot_data.boot_data_drive is True:
1966 config_drive = True
1967
1968 kwargs['metadata'] = metadata
1969 kwargs['files'] = files
1970 kwargs['config_drive'] = config_drive
1971
1972 if vduinfo.has_field('availability_zone') and vduinfo.availability_zone.has_field('name'):
1973 kwargs['availability_zone'] = vduinfo.availability_zone
1974 else:
1975 kwargs['availability_zone'] = None
1976
1977 if server_group is not None:
1978 kwargs['scheduler_hints'] = {'group': server_group}
1979 else:
1980 kwargs['scheduler_hints'] = None
1981
1982 kwargs['block_device_mapping_v2'] = None
1983 vol_metadata = False
1984 if vduinfo.has_field('volumes') :
1985 kwargs['block_device_mapping_v2'] = []
1986 with self._use_driver(account) as drv:
1987 # Only support image->volume
1988 for volume in vduinfo.volumes:
1989 block_map = dict()
1990 block_map['boot_index'] = volume.boot_priority
1991 if "image" in volume:
1992 # Support image->volume
1993 # Match retrived image info with volume based image name and checksum
1994 if volume.image is not None:
1995 matching_images = [img for img in imageinfo_list if img['name'] == volume.image]
1996 if volume.image_checksum is not None:
1997 matching_images = [img for img in matching_images if img['checksum'] == volume.image_checksum]
1998 img_id = matching_images[0]['id']
1999 if img_id is None:
2000 raise OpenstackCALOperationFailure("Create-vdu operation failed. Volume image not found for name {} checksum {}".format(volume.name, volume.checksum))
2001 block_map['uuid'] = img_id
2002 block_map['source_type'] = "image"
2003 else:
2004 block_map['source_type'] = "blank"
2005
2006 block_map['device_name'] = volume.name
2007 block_map['destination_type'] = "volume"
2008 block_map['volume_size'] = volume.size
2009 block_map['delete_on_termination'] = True
2010 if volume.has_field('device_type') and volume.device_type == 'cdrom':
2011 block_map['device_type'] = 'cdrom'
2012 if volume.has_field('device_bus') and volume.device_bus == 'ide':
2013 block_map['disk_bus'] = 'ide'
2014 kwargs['block_device_mapping_v2'].append(block_map)
2015
2016
2017 with self._use_driver(account) as drv:
2018 vm_id = drv.nova_server_create(**kwargs)
2019 if floating_ip:
2020 self.prepare_vdu_on_boot(account, vm_id, floating_ip, vduinfo.volumes)
2021
2022 return vm_id
2023
2024 def get_openstack_image_info(self, account, image_name, image_checksum=None):
2025 self.log.debug("Looking up image id for image name %s and checksum %s on cloud account: %s",
2026 image_name, image_checksum, account.name
2027 )
2028
2029 image_list = []
2030 with self._use_driver(account) as drv:
2031 image_list = drv.glance_image_list()
2032 matching_images = [img for img in image_list if img['name'] == image_name]
2033
2034 # If the image checksum was filled in then further filter the images by the checksum
2035 if image_checksum is not None:
2036 matching_images = [img for img in matching_images if img['checksum'] == image_checksum]
2037 else:
2038 self.log.warning("Image checksum not provided. Lookup using image name (%s) only.",
2039 image_name)
2040
2041 if len(matching_images) == 0:
2042 raise ResMgrCALOperationFailure("Could not find image name {} (using checksum: {}) for cloud account: {}".format(
2043 image_name, image_checksum, account.name
2044 ))
2045
2046 elif len(matching_images) > 1:
2047 unique_checksums = {i.checksum for i in matching_images}
2048 if len(unique_checksums) > 1:
2049 msg = ("Too many images with different checksums matched "
2050 "image name of %s for cloud account: %s" % (image_name, account.name))
2051 raise ResMgrCALOperationFailure(msg)
2052
2053 return matching_images[0]
2054
2055 @rwcalstatus(ret_on_failure=[""])
2056 def do_create_vdu(self, account, vdu_init):
2057 """Create a new virtual deployment unit
2058
2059 Arguments:
2060 account - a cloud account
2061 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
2062
2063 Returns:
2064 The vdu_id
2065 """
2066 ### First create required number of ports aka connection points
2067 # Add the mgmt_ntwk by default.
2068 mgmt_network_id = None
2069 with self._use_driver(account) as drv:
2070 mgmt_network_id = drv._mgmt_network_id
2071 ### If floating_ip is required and we don't have one, better fail before any further allocation
2072 if vdu_init.has_field('allocate_public_address') and vdu_init.allocate_public_address:
2073 if account.openstack.has_field('floating_ip_pool'):
2074 pool_name = account.openstack.floating_ip_pool
2075 else:
2076 pool_name = None
2077 floating_ip = self._allocate_floating_ip(drv, pool_name)
2078 else:
2079 floating_ip = None
2080
2081 port_list = []
2082 network_list = []
2083 imageinfo_list = []
2084 is_explicit_mgmt_defined = False
2085 for c_point in vdu_init.connection_points:
2086 # if the user has specified explicit mgmt_network connection point
2087 # then remove the mgmt_network from the VM list
2088 if c_point.virtual_link_id == mgmt_network_id:
2089 is_explicit_mgmt_defined = True
2090 if c_point.virtual_link_id in network_list:
2091 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"
2092 else:
2093 network_list.append(c_point.virtual_link_id)
2094 port_id = self._create_connection_point(account, c_point)
2095 port_list.append(port_id)
2096
2097 if not vdu_init.has_field('flavor_id'):
2098 vdu_init.flavor_id = self._select_resource_flavor(account,vdu_init)
2099
2100 ### Obtain all images for volumes and perform validations
2101 if vdu_init.has_field('volumes'):
2102 for volume in vdu_init.volumes:
2103 if "image" in volume:
2104 image_checksum = volume.image_checksum if volume.has_field("image_checksum") else None
2105 image_info = self.get_openstack_image_info(account, volume.image, image_checksum)
2106 imageinfo_list.append(image_info)
2107 elif vdu_init.has_field('image_id'):
2108 with self._use_driver(account) as drv:
2109 image_info = drv.glance_image_get(vdu_init.image_id)
2110 imageinfo_list.append(image_info)
2111
2112 if not imageinfo_list:
2113 err_str = ("VDU has no image information")
2114 self.log.error(err_str)
2115 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2116
2117 ### Check VDU Virtual Interface type and make sure VM with property exists
2118 if vdu_init.connection_points:
2119 ### All virtual interfaces need to be of the same type for Openstack Accounts
2120 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)):
2121 ### We have a mix of E1000 & VIRTIO/SR_IPOV virtual interface types in the VDU, abort instantiation.
2122 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO/SR_IOV & E1000."
2123
2124 ## 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,
2125 ### we shall assume that all images need to have similar properties
2126 for img_info in imageinfo_list:
2127
2128 virt_intf_type = vdu_init.connection_points[0].type_yang
2129 if virt_intf_type == 'E1000':
2130 if 'hw_vif_model' in img_info and img_info.hw_vif_model == 'e1000':
2131 self.log.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
2132 else:
2133 err_str = ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info.name)
2134 self.log.error(err_str)
2135 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2136 elif virt_intf_type == 'VIRTIO' or virt_intf_type == 'SR_IOV':
2137 if 'hw_vif_model' in img_info:
2138 err_str = ("VDU has Virtual Interface %s, but image '%s' has hw_vif_model mismatch" % virt_intf_type,img_info.name)
2139 self.log.error(err_str)
2140 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2141 else:
2142 self.log.debug("VDU has Virtual Interface %s, found matching image" % virt_intf_type)
2143 else:
2144 err_str = ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type)
2145 self.log.error(err_str)
2146 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2147
2148 with self._use_driver(account) as drv:
2149 ### Now Create VM
2150 vm_network_list = []
2151 if not is_explicit_mgmt_defined:
2152 vm_network_list.append(drv._mgmt_network_id)
2153
2154 if vdu_init.has_field('volumes'):
2155 # Only combination supported: Image->Volume
2156 for volume in vdu_init.volumes:
2157 if "volume" in volume:
2158 err_str = ("VDU Volume source not supported yet")
2159 self.log.error(err_str)
2160 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2161 if not volume.has_field('device_type'):
2162 err_str = ("VDU Volume destination type not defined")
2163 self.log.error(err_str)
2164 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2165 if volume.device_type not in ['disk', 'cdrom'] :
2166 err_str = ("VDU Volume destination type '%s' not supported" % volume.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.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