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