Merge branch 'master' into v1.0
[osm/SO.git] / rwcal / plugins / vala / rwcal_openstack / rwcal_openstack.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 import contextlib
19 import logging
20 import os
21 import subprocess
22 import uuid
23
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.id) 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.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 for x in flavor_info['extra_specs'][attr].split(','):
775 numa_node_vcpu = numa_node.vcpu.add()
776 numa_node_vcpu.id = int(x)
777
778 elif attr.startswith('hw:numa_mem.'):
779 node_id = attr.split('.')[1]
780 nodes = [ n for n in flavor.guest_epa.numa_node_policy.node if n.id == int(node_id) ]
781 if nodes:
782 numa_node = nodes[0]
783 else:
784 numa_node = getattr(flavor,'guest_epa').numa_node_policy.node.add()
785 numa_node.id = int(node_id)
786
787 numa_node.memory_mb = int(flavor_info['extra_specs'][attr])
788
789 elif attr == 'hw:numa_mempolicy':
790 numa_memory_policy = espec_utils.guest.extra_to_mano_spec_numa_memory_policy(flavor_info['extra_specs']['hw:numa_mempolicy'])
791 if numa_memory_policy is not None:
792 getattr(flavor,'guest_epa').numa_node_policy.mem_policy = numa_memory_policy
793
794 elif attr == 'trust:trusted_host':
795 trusted_execution = espec_utils.guest.extra_spec_to_mano_trusted_execution(flavor_info['extra_specs']['trust:trusted_host'])
796 if trusted_execution is not None:
797 getattr(flavor,'guest_epa').trusted_execution = trusted_execution
798
799 elif attr == 'pci_passthrough:alias':
800 device_types = flavor_info['extra_specs']['pci_passthrough:alias']
801 for device in device_types.split(','):
802 dev = getattr(flavor,'guest_epa').pcie_device.add()
803 dev.device_id = device.split(':')[0]
804 dev.count = int(device.split(':')[1])
805
806 elif attr == 'capabilities:cpu_info:model':
807 cpu_model = espec_utils.host.extra_specs_to_mano_cpu_model(flavor_info['extra_specs']['capabilities:cpu_info:model'])
808 if cpu_model is not None:
809 getattr(flavor, 'host_epa').cpu_model = cpu_model
810
811 elif attr == 'capabilities:cpu_info:arch':
812 cpu_arch = espec_utils.host.extra_specs_to_mano_cpu_arch(flavor_info['extra_specs']['capabilities:cpu_info:arch'])
813 if cpu_arch is not None:
814 getattr(flavor, 'host_epa').cpu_arch = cpu_arch
815
816 elif attr == 'capabilities:cpu_info:vendor':
817 cpu_vendor = espec_utils.host.extra_spec_to_mano_cpu_vendor(flavor_info['extra_specs']['capabilities:cpu_info:vendor'])
818 if cpu_vendor is not None:
819 getattr(flavor, 'host_epa').cpu_vendor = cpu_vendor
820
821 elif attr == 'capabilities:cpu_info:topology:sockets':
822 cpu_sockets = espec_utils.host.extra_spec_to_mano_cpu_socket_count(flavor_info['extra_specs']['capabilities:cpu_info:topology:sockets'])
823 if cpu_sockets is not None:
824 getattr(flavor, 'host_epa').cpu_socket_count = cpu_sockets
825
826 elif attr == 'capabilities:cpu_info:topology:cores':
827 cpu_cores = espec_utils.host.extra_spec_to_mano_cpu_core_count(flavor_info['extra_specs']['capabilities:cpu_info:topology:cores'])
828 if cpu_cores is not None:
829 getattr(flavor, 'host_epa').cpu_core_count = cpu_cores
830
831 elif attr == 'capabilities:cpu_info:topology:threads':
832 cpu_threads = espec_utils.host.extra_spec_to_mano_cpu_core_thread_count(flavor_info['extra_specs']['capabilities:cpu_info:topology:threads'])
833 if cpu_threads is not None:
834 getattr(flavor, 'host_epa').cpu_core_thread_count = cpu_threads
835
836 elif attr == 'capabilities:cpu_info:features':
837 cpu_features = espec_utils.host.extra_spec_to_mano_cpu_features(flavor_info['extra_specs']['capabilities:cpu_info:features'])
838 if cpu_features is not None:
839 for feature in cpu_features:
840 getattr(flavor, 'host_epa').cpu_feature.append(feature)
841 elif attr.startswith('aggregate_instance_extra_specs:'):
842 aggregate = getattr(flavor, 'host_aggregate').add()
843 aggregate.metadata_key = ":".join(attr.split(':')[1::])
844 aggregate.metadata_value = flavor_info['extra_specs'][attr]
845
846 @staticmethod
847 def _fill_flavor_info(flavor_info):
848 """Create a GI object from flavor info dictionary
849
850 Converts Flavor information dictionary object returned by openstack
851 driver into Protobuf Gi Object
852
853 Arguments:
854 flavor_info: Flavor information from openstack
855
856 Returns:
857 Object of class FlavorInfoItem
858 """
859 flavor = RwcalYang.FlavorInfoItem()
860 flavor.name = flavor_info['name']
861 flavor.id = flavor_info['id']
862 RwcalOpenstackPlugin._fill_epa_attributes(flavor, flavor_info)
863 return flavor
864
865
866 @rwstatus(ret_on_failure=[[]])
867 def do_get_flavor_list(self, account):
868 """Return flavor information.
869
870 Arguments:
871 account - a cloud account
872
873 Returns:
874 List of flavors
875 """
876 response = RwcalYang.VimResources()
877 with self._use_driver(account) as drv:
878 flavors = drv.nova_flavor_list()
879 for flv in flavors:
880 response.flavorinfo_list.append(RwcalOpenstackPlugin._fill_flavor_info(flv))
881 return response
882
883 @rwstatus(ret_on_failure=[None])
884 def do_get_flavor(self, account, id):
885 """Return flavor information.
886
887 Arguments:
888 account - a cloud account
889 id - an id for the flavor
890
891 Returns:
892 Flavor info item
893 """
894 with self._use_driver(account) as drv:
895 flavor = drv.nova_flavor_get(id)
896 return RwcalOpenstackPlugin._fill_flavor_info(flavor)
897
898
899 def _fill_network_info(self, network_info, account):
900 """Create a GI object from network info dictionary
901
902 Converts Network information dictionary object returned by openstack
903 driver into Protobuf Gi Object
904
905 Arguments:
906 network_info - Network information from openstack
907 account - a cloud account
908
909 Returns:
910 Network info item
911 """
912 network = RwcalYang.NetworkInfoItem()
913 network.network_name = network_info['name']
914 network.network_id = network_info['id']
915 if ('provider:network_type' in network_info) and (network_info['provider:network_type'] != None):
916 network.provider_network.overlay_type = network_info['provider:network_type'].upper()
917 if ('provider:segmentation_id' in network_info) and (network_info['provider:segmentation_id']):
918 network.provider_network.segmentation_id = network_info['provider:segmentation_id']
919 if ('provider:physical_network' in network_info) and (network_info['provider:physical_network']):
920 network.provider_network.physical_network = network_info['provider:physical_network'].upper()
921
922 if 'subnets' in network_info and network_info['subnets']:
923 subnet_id = network_info['subnets'][0]
924 with self._use_driver(account) as drv:
925 subnet = drv.neutron_subnet_get(subnet_id)
926 network.subnet = subnet['cidr']
927 return network
928
929 @rwstatus(ret_on_failure=[[]])
930 def do_get_network_list(self, account):
931 """Return a list of networks
932
933 Arguments:
934 account - a cloud account
935
936 Returns:
937 List of networks
938 """
939 response = RwcalYang.VimResources()
940 with self._use_driver(account) as drv:
941 networks = drv.neutron_network_list()
942 for network in networks:
943 response.networkinfo_list.append(self._fill_network_info(network, account))
944 return response
945
946 @rwstatus(ret_on_failure=[None])
947 def do_get_network(self, account, id):
948 """Return a network
949
950 Arguments:
951 account - a cloud account
952 id - an id for the network
953
954 Returns:
955 Network info item
956 """
957 with self._use_driver(account) as drv:
958 network = drv.neutron_network_get(id)
959 return self._fill_network_info(network, account)
960
961 @rwstatus(ret_on_failure=[""])
962 def do_create_network(self, account, network):
963 """Create a new network
964
965 Arguments:
966 account - a cloud account
967 network - Network object
968
969 Returns:
970 Network id
971 """
972 kwargs = {}
973 kwargs['name'] = network.network_name
974 kwargs['admin_state_up'] = True
975 kwargs['external_router'] = False
976 kwargs['shared'] = False
977
978 if network.has_field('provider_network'):
979 if network.provider_network.has_field('physical_network'):
980 kwargs['physical_network'] = network.provider_network.physical_network
981 if network.provider_network.has_field('overlay_type'):
982 kwargs['network_type'] = network.provider_network.overlay_type.lower()
983 if network.provider_network.has_field('segmentation_id'):
984 kwargs['segmentation_id'] = network.provider_network.segmentation_id
985
986 with self._use_driver(account) as drv:
987 network_id = drv.neutron_network_create(**kwargs)
988 drv.neutron_subnet_create(network_id = network_id,
989 cidr = network.subnet)
990 return network_id
991
992 @rwstatus
993 def do_delete_network(self, account, network_id):
994 """Delete a network
995
996 Arguments:
997 account - a cloud account
998 network_id - an id for the network
999 """
1000 with self._use_driver(account) as drv:
1001 drv.neutron_network_delete(network_id)
1002
1003 @staticmethod
1004 def _fill_port_info(port_info):
1005 """Create a GI object from port info dictionary
1006
1007 Converts Port information dictionary object returned by openstack
1008 driver into Protobuf Gi Object
1009
1010 Arguments:
1011 port_info - Port information from openstack
1012
1013 Returns:
1014 Port info item
1015 """
1016 port = RwcalYang.PortInfoItem()
1017
1018 port.port_name = port_info['name']
1019 port.port_id = port_info['id']
1020 port.network_id = port_info['network_id']
1021 port.port_state = port_info['status']
1022 if 'device_id' in port_info:
1023 port.vm_id = port_info['device_id']
1024 if 'fixed_ips' in port_info:
1025 port.ip_address = port_info['fixed_ips'][0]['ip_address']
1026 return port
1027
1028 @rwstatus(ret_on_failure=[None])
1029 def do_get_port(self, account, port_id):
1030 """Return a port
1031
1032 Arguments:
1033 account - a cloud account
1034 port_id - an id for the port
1035
1036 Returns:
1037 Port info item
1038 """
1039 with self._use_driver(account) as drv:
1040 port = drv.neutron_port_get(port_id)
1041
1042 return RwcalOpenstackPlugin._fill_port_info(port)
1043
1044 @rwstatus(ret_on_failure=[[]])
1045 def do_get_port_list(self, account):
1046 """Return a list of ports
1047
1048 Arguments:
1049 account - a cloud account
1050
1051 Returns:
1052 Port info list
1053 """
1054 response = RwcalYang.VimResources()
1055 with self._use_driver(account) as drv:
1056 ports = drv.neutron_port_list(*{})
1057 for port in ports:
1058 response.portinfo_list.append(RwcalOpenstackPlugin._fill_port_info(port))
1059 return response
1060
1061 @rwstatus(ret_on_failure=[""])
1062 def do_create_port(self, account, port):
1063 """Create a new port
1064
1065 Arguments:
1066 account - a cloud account
1067 port - port object
1068
1069 Returns:
1070 Port id
1071 """
1072 kwargs = {}
1073 kwargs['name'] = port.port_name
1074 kwargs['network_id'] = port.network_id
1075 kwargs['admin_state_up'] = True
1076 if port.has_field('vm_id'):
1077 kwargs['vm_id'] = port.vm_id
1078 if port.has_field('port_type'):
1079 kwargs['port_type'] = port.port_type
1080 else:
1081 kwargs['port_type'] = "normal"
1082
1083 with self._use_driver(account) as drv:
1084 return drv.neutron_port_create(**kwargs)
1085
1086 @rwstatus
1087 def do_delete_port(self, account, port_id):
1088 """Delete a port
1089
1090 Arguments:
1091 account - a cloud account
1092 port_id - an id for port
1093 """
1094 with self._use_driver(account) as drv:
1095 drv.neutron_port_delete(port_id)
1096
1097 @rwstatus(ret_on_failure=[""])
1098 def do_add_host(self, account, host):
1099 """Add a new host
1100
1101 Arguments:
1102 account - a cloud account
1103 host - a host object
1104
1105 Returns:
1106 An id for the host
1107 """
1108 raise NotImplementedError
1109
1110 @rwstatus
1111 def do_remove_host(self, account, host_id):
1112 """Remove a host
1113
1114 Arguments:
1115 account - a cloud account
1116 host_id - an id for the host
1117 """
1118 raise NotImplementedError
1119
1120 @rwstatus(ret_on_failure=[None])
1121 def do_get_host(self, account, host_id):
1122 """Return a host
1123
1124 Arguments:
1125 account - a cloud account
1126 host_id - an id for host
1127
1128 Returns:
1129 Host info item
1130 """
1131 raise NotImplementedError
1132
1133 @rwstatus(ret_on_failure=[[]])
1134 def do_get_host_list(self, account):
1135 """Return a list of hosts
1136
1137 Arguments:
1138 account - a cloud account
1139
1140 Returns:
1141 List of hosts
1142 """
1143 raise NotImplementedError
1144
1145 @staticmethod
1146 def _fill_connection_point_info(c_point, port_info):
1147 """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints()
1148
1149 Converts Port information dictionary object returned by openstack
1150 driver into Protobuf Gi Object
1151
1152 Arguments:
1153 port_info - Port information from openstack
1154 Returns:
1155 Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints
1156 """
1157 c_point.name = port_info['name']
1158 c_point.connection_point_id = port_info['id']
1159 if ('fixed_ips' in port_info) and (len(port_info['fixed_ips']) >= 1):
1160 if 'ip_address' in port_info['fixed_ips'][0]:
1161 c_point.ip_address = port_info['fixed_ips'][0]['ip_address']
1162 if port_info['status'] == 'ACTIVE':
1163 c_point.state = 'active'
1164 else:
1165 c_point.state = 'inactive'
1166 if 'network_id' in port_info:
1167 c_point.virtual_link_id = port_info['network_id']
1168 if ('device_id' in port_info) and (port_info['device_id']):
1169 c_point.vdu_id = port_info['device_id']
1170
1171 @staticmethod
1172 def _fill_virtual_link_info(network_info, port_list, subnet):
1173 """Create a GI object for VirtualLinkInfoParams
1174
1175 Converts Network and Port information dictionary object
1176 returned by openstack driver into Protobuf Gi Object
1177
1178 Arguments:
1179 network_info - Network information from openstack
1180 port_list - A list of port information from openstack
1181 subnet: Subnet information from openstack
1182 Returns:
1183 Protobuf Gi object for VirtualLinkInfoParams
1184 """
1185 link = RwcalYang.VirtualLinkInfoParams()
1186 link.name = network_info['name']
1187 if network_info['status'] == 'ACTIVE':
1188 link.state = 'active'
1189 else:
1190 link.state = 'inactive'
1191 link.virtual_link_id = network_info['id']
1192 for port in port_list:
1193 if port['device_owner'] == 'compute:None':
1194 c_point = link.connection_points.add()
1195 RwcalOpenstackPlugin._fill_connection_point_info(c_point, port)
1196
1197 if subnet != None:
1198 link.subnet = subnet['cidr']
1199
1200 if ('provider:network_type' in network_info) and (network_info['provider:network_type'] != None):
1201 link.provider_network.overlay_type = network_info['provider:network_type'].upper()
1202 if ('provider:segmentation_id' in network_info) and (network_info['provider:segmentation_id']):
1203 link.provider_network.segmentation_id = network_info['provider:segmentation_id']
1204 if ('provider:physical_network' in network_info) and (network_info['provider:physical_network']):
1205 link.provider_network.physical_network = network_info['provider:physical_network'].upper()
1206
1207 return link
1208
1209 @staticmethod
1210 def _fill_vdu_info(vm_info, flavor_info, mgmt_network, port_list, server_group):
1211 """Create a GI object for VDUInfoParams
1212
1213 Converts VM information dictionary object returned by openstack
1214 driver into Protobuf Gi Object
1215
1216 Arguments:
1217 vm_info - VM information from openstack
1218 flavor_info - VM Flavor information from openstack
1219 mgmt_network - Management network
1220 port_list - A list of port information from openstack
1221 server_group - A list (with one element or empty list) of server group to which this VM belongs
1222 Returns:
1223 Protobuf Gi object for VDUInfoParams
1224 """
1225 vdu = RwcalYang.VDUInfoParams()
1226 vdu.name = vm_info['name']
1227 vdu.vdu_id = vm_info['id']
1228 for network_name, network_info in vm_info['addresses'].items():
1229 if network_info and network_name == mgmt_network:
1230 for interface in network_info:
1231 if 'OS-EXT-IPS:type' in interface:
1232 if interface['OS-EXT-IPS:type'] == 'fixed':
1233 vdu.management_ip = interface['addr']
1234 elif interface['OS-EXT-IPS:type'] == 'floating':
1235 vdu.public_ip = interface['addr']
1236
1237 # Look for any metadata
1238 for key, value in vm_info['metadata'].items():
1239 if key == 'node_id':
1240 vdu.node_id = value
1241 if ('image' in vm_info) and ('id' in vm_info['image']):
1242 vdu.image_id = vm_info['image']['id']
1243 if ('flavor' in vm_info) and ('id' in vm_info['flavor']):
1244 vdu.flavor_id = vm_info['flavor']['id']
1245
1246 if vm_info['status'] == 'ACTIVE':
1247 vdu.state = 'active'
1248 elif vm_info['status'] == 'ERROR':
1249 vdu.state = 'failed'
1250 else:
1251 vdu.state = 'inactive'
1252
1253 if 'availability_zone' in vm_info:
1254 vdu.availability_zone = vm_info['availability_zone']
1255
1256 if server_group:
1257 vdu.server_group.name = server_group[0]
1258
1259 vdu.cloud_type = 'openstack'
1260 # Fill the port information
1261 for port in port_list:
1262 c_point = vdu.connection_points.add()
1263 RwcalOpenstackPlugin._fill_connection_point_info(c_point, port)
1264
1265 if flavor_info is not None:
1266 RwcalOpenstackPlugin._fill_epa_attributes(vdu, flavor_info)
1267 return vdu
1268
1269 @rwcalstatus(ret_on_failure=[""])
1270 def do_create_virtual_link(self, account, link_params):
1271 """Create a new virtual link
1272
1273 Arguments:
1274 account - a cloud account
1275 link_params - information that defines the type of VDU to create
1276
1277 Returns:
1278 The vdu_id
1279 """
1280 kwargs = {}
1281 kwargs['name'] = link_params.name
1282 kwargs['admin_state_up'] = True
1283 kwargs['external_router'] = False
1284 kwargs['shared'] = False
1285
1286 if link_params.has_field('provider_network'):
1287 if link_params.provider_network.has_field('physical_network'):
1288 kwargs['physical_network'] = link_params.provider_network.physical_network
1289 if link_params.provider_network.has_field('overlay_type'):
1290 kwargs['network_type'] = link_params.provider_network.overlay_type.lower()
1291 if link_params.provider_network.has_field('segmentation_id'):
1292 kwargs['segmentation_id'] = link_params.provider_network.segmentation_id
1293
1294
1295 with self._use_driver(account) as drv:
1296 try:
1297 network_id = drv.neutron_network_create(**kwargs)
1298 except Exception as e:
1299 self.log.error("Encountered exceptions during network creation. Exception: %s", str(e))
1300 raise
1301
1302 kwargs = {'network_id' : network_id,
1303 'dhcp_params': {'enable_dhcp': True},
1304 'gateway_ip' : None,}
1305
1306 if link_params.ip_profile_params.has_field('ip_version'):
1307 kwargs['ip_version'] = 6 if link_params.ip_profile_params.ip_version == 'ipv6' else 4
1308 else:
1309 kwargs['ip_version'] = 4
1310
1311 if link_params.ip_profile_params.has_field('subnet_address'):
1312 kwargs['cidr'] = link_params.ip_profile_params.subnet_address
1313 elif link_params.ip_profile_params.has_field('subnet_prefix_pool'):
1314 subnet_pool = drv.netruon_subnetpool_by_name(link_params.ip_profile_params.subnet_prefix_pool)
1315 if subnet_pool is None:
1316 self.log.error("Could not find subnet pool with name :%s to be used for network: %s",
1317 link_params.ip_profile_params.subnet_prefix_pool,
1318 link_params.name)
1319 raise NeutronException.NotFound("SubnetPool with name %s not found"%(link_params.ip_profile_params.subnet_prefix_pool))
1320
1321 kwargs['subnetpool_id'] = subnet_pool['id']
1322 elif link_params.has_field('subnet'):
1323 kwargs['cidr'] = link_params.subnet
1324 else:
1325 assert 0, "No IP Prefix or Pool name specified"
1326
1327 if link_params.ip_profile_params.has_field('dhcp_params'):
1328 if link_params.ip_profile_params.dhcp_params.has_field('enabled'):
1329 kwargs['dhcp_params']['enable_dhcp'] = link_params.ip_profile_params.dhcp_params.enabled
1330 if link_params.ip_profile_params.dhcp_params.has_field('start_address'):
1331 kwargs['dhcp_params']['start_address'] = link_params.ip_profile_params.dhcp_params.start_address
1332 if link_params.ip_profile_params.dhcp_params.has_field('count'):
1333 kwargs['dhcp_params']['count'] = link_params.ip_profile_params.dhcp_params.count
1334
1335 if link_params.ip_profile_params.has_field('dns_server'):
1336 kwargs['dns_server'] = []
1337 for server in link_params.ip_profile_params.dns_server:
1338 kwargs['dns_server'].append(server.address)
1339
1340 if link_params.ip_profile_params.has_field('gateway_address'):
1341 kwargs['gateway_ip'] = link_params.ip_profile_params.gateway_address
1342
1343 drv.neutron_subnet_create(**kwargs)
1344
1345 return network_id
1346
1347
1348 @rwstatus
1349 def do_delete_virtual_link(self, account, link_id):
1350 """Delete a virtual link
1351
1352 Arguments:
1353 account - a cloud account
1354 link_id - id for the virtual-link to be deleted
1355
1356 Returns:
1357 None
1358 """
1359 if not link_id:
1360 self.log.error("Empty link_id during the virtual link deletion")
1361 raise Exception("Empty link_id during the virtual link deletion")
1362
1363 with self._use_driver(account) as drv:
1364 port_list = drv.neutron_port_list(**{'network_id': link_id})
1365
1366 for port in port_list:
1367 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
1368 self.do_delete_port(account, port['id'], no_rwstatus=True)
1369 self.do_delete_network(account, link_id, no_rwstatus=True)
1370
1371 @rwstatus(ret_on_failure=[None])
1372 def do_get_virtual_link(self, account, link_id):
1373 """Get information about virtual link.
1374
1375 Arguments:
1376 account - a cloud account
1377 link_id - id for the virtual-link
1378
1379 Returns:
1380 Object of type RwcalYang.VirtualLinkInfoParams
1381 """
1382 if not link_id:
1383 self.log.error("Empty link_id during the virtual link get request")
1384 raise Exception("Empty link_id during the virtual link get request")
1385
1386 with self._use_driver(account) as drv:
1387 network = drv.neutron_network_get(link_id)
1388 if network:
1389 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1390 if 'subnets' in network:
1391 subnet = drv.neutron_subnet_get(network['subnets'][0])
1392 else:
1393 subnet = None
1394 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1395 else:
1396 virtual_link = None
1397 return virtual_link
1398
1399 @rwstatus(ret_on_failure=[None])
1400 def do_get_virtual_link_list(self, account):
1401 """Get information about all the virtual links
1402
1403 Arguments:
1404 account - a cloud account
1405
1406 Returns:
1407 A list of objects of type RwcalYang.VirtualLinkInfoParams
1408 """
1409 vnf_resources = RwcalYang.VNFResources()
1410 with self._use_driver(account) as drv:
1411 networks = drv.neutron_network_list()
1412 for network in networks:
1413 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1414 if ('subnets' in network) and (network['subnets']):
1415 subnet = drv.neutron_subnet_get(network['subnets'][0])
1416 else:
1417 subnet = None
1418 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1419 vnf_resources.virtual_link_info_list.append(virtual_link)
1420 return vnf_resources
1421
1422 def _create_connection_point(self, account, c_point):
1423 """
1424 Create a connection point
1425 Arguments:
1426 account - a cloud account
1427 c_point - connection_points
1428 """
1429 kwargs = {}
1430 kwargs['name'] = c_point.name
1431 kwargs['network_id'] = c_point.virtual_link_id
1432 kwargs['admin_state_up'] = True
1433
1434 if c_point.type_yang == 'VIRTIO' or c_point.type_yang == 'E1000':
1435 kwargs['port_type'] = 'normal'
1436 elif c_point.type_yang == 'SR_IOV':
1437 kwargs['port_type'] = 'direct'
1438 else:
1439 raise NotImplementedError("Port Type: %s not supported" %(c_point.type_yang))
1440
1441 with self._use_driver(account) as drv:
1442 if c_point.has_field('security_group'):
1443 group = drv.neutron_security_group_by_name(c_point.security_group)
1444 if group is not None:
1445 kwargs['security_groups'] = [group['id']]
1446 return drv.neutron_port_create(**kwargs)
1447
1448 def _allocate_floating_ip(self, drv, pool_name):
1449 """
1450 Allocate a floating_ip. If unused floating_ip exists then its reused.
1451 Arguments:
1452 drv: OpenstackDriver instance
1453 pool_name: Floating IP pool name
1454
1455 Returns:
1456 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1457 """
1458
1459 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1460
1461 # if pool_name is not None:
1462 # ### Filter further based on IP address
1463 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1464
1465 # if not available_ip:
1466 # floating_ip = drv.nova_floating_ip_create(pool_name)
1467 # else:
1468 # floating_ip = available_ip[0]
1469
1470 floating_ip = drv.nova_floating_ip_create(pool_name)
1471 return floating_ip
1472
1473 def _match_vm_flavor(self, required, available):
1474 self.log.info("Matching VM Flavor attributes")
1475 if available.vcpu_count != required.vcpu_count:
1476 self.log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1477 required.vcpu_count,
1478 available.vcpu_count)
1479 return False
1480 if available.memory_mb != required.memory_mb:
1481 self.log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1482 required.memory_mb,
1483 available.memory_mb)
1484 return False
1485 if available.storage_gb != required.storage_gb:
1486 self.log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1487 required.storage_gb,
1488 available.storage_gb)
1489 return False
1490 self.log.debug("VM Flavor match found")
1491 return True
1492
1493 def _match_guest_epa(self, required, available):
1494 self.log.info("Matching Guest EPA attributes")
1495 if required.has_field('pcie_device'):
1496 self.log.debug("Matching pcie_device")
1497 if available.has_field('pcie_device') == False:
1498 self.log.debug("Matching pcie_device failed. Not available in flavor")
1499 return False
1500 else:
1501 for dev in required.pcie_device:
1502 if not [ d for d in available.pcie_device
1503 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
1504 self.log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
1505 return False
1506 elif available.has_field('pcie_device'):
1507 self.log.debug("Rejecting available flavor because pcie_device not required but available")
1508 return False
1509
1510
1511 if required.has_field('mempage_size'):
1512 self.log.debug("Matching mempage_size")
1513 if available.has_field('mempage_size') == False:
1514 self.log.debug("Matching mempage_size failed. Not available in flavor")
1515 return False
1516 else:
1517 if required.mempage_size != available.mempage_size:
1518 self.log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
1519 return False
1520 elif available.has_field('mempage_size'):
1521 self.log.debug("Rejecting available flavor because mempage_size not required but available")
1522 return False
1523
1524 if required.has_field('cpu_pinning_policy'):
1525 self.log.debug("Matching cpu_pinning_policy")
1526 if required.cpu_pinning_policy != 'ANY':
1527 if available.has_field('cpu_pinning_policy') == False:
1528 self.log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1529 return False
1530 else:
1531 if required.cpu_pinning_policy != available.cpu_pinning_policy:
1532 self.log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
1533 return False
1534 elif available.has_field('cpu_pinning_policy'):
1535 self.log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1536 return False
1537
1538 if required.has_field('cpu_thread_pinning_policy'):
1539 self.log.debug("Matching cpu_thread_pinning_policy")
1540 if available.has_field('cpu_thread_pinning_policy') == False:
1541 self.log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1542 return False
1543 else:
1544 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
1545 self.log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
1546 return False
1547 elif available.has_field('cpu_thread_pinning_policy'):
1548 self.log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1549 return False
1550
1551 if required.has_field('trusted_execution'):
1552 self.log.debug("Matching trusted_execution")
1553 if required.trusted_execution == True:
1554 if available.has_field('trusted_execution') == False:
1555 self.log.debug("Matching trusted_execution failed. Not available in flavor")
1556 return False
1557 else:
1558 if required.trusted_execution != available.trusted_execution:
1559 self.log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
1560 return False
1561 elif available.has_field('trusted_execution'):
1562 self.log.debug("Rejecting available flavor because trusted_execution not required but available")
1563 return False
1564
1565 if required.has_field('numa_node_policy'):
1566 self.log.debug("Matching numa_node_policy")
1567 if available.has_field('numa_node_policy') == False:
1568 self.log.debug("Matching numa_node_policy failed. Not available in flavor")
1569 return False
1570 else:
1571 if required.numa_node_policy.has_field('node_cnt'):
1572 self.log.debug("Matching numa_node_policy node_cnt")
1573 if available.numa_node_policy.has_field('node_cnt') == False:
1574 self.log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1575 return False
1576 else:
1577 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
1578 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)
1579 return False
1580 elif available.numa_node_policy.has_field('node_cnt'):
1581 self.log.debug("Rejecting available flavor because numa node count not required but available")
1582 return False
1583
1584 if required.numa_node_policy.has_field('mem_policy'):
1585 self.log.debug("Matching numa_node_policy mem_policy")
1586 if available.numa_node_policy.has_field('mem_policy') == False:
1587 self.log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1588 return False
1589 else:
1590 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
1591 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)
1592 return False
1593 elif available.numa_node_policy.has_field('mem_policy'):
1594 self.log.debug("Rejecting available flavor because num node mem_policy not required but available")
1595 return False
1596
1597 if required.numa_node_policy.has_field('node'):
1598 self.log.debug("Matching numa_node_policy nodes configuration")
1599 if available.numa_node_policy.has_field('node') == False:
1600 self.log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1601 return False
1602 for required_node in required.numa_node_policy.node:
1603 self.log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1604 numa_match = False
1605 for available_node in available.numa_node_policy.node:
1606 if required_node.id != available_node.id:
1607 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1608 continue
1609 if required_node.vcpu != available_node.vcpu:
1610 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1611 continue
1612 if required_node.memory_mb != available_node.memory_mb:
1613 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1614 continue
1615 numa_match = True
1616 if numa_match == False:
1617 return False
1618 elif available.numa_node_policy.has_field('node'):
1619 self.log.debug("Rejecting available flavor because numa nodes not required but available")
1620 return False
1621 elif available.has_field('numa_node_policy'):
1622 self.log.debug("Rejecting available flavor because numa_node_policy not required but available")
1623 return False
1624 self.log.info("Successful match for Guest EPA attributes")
1625 return True
1626
1627 def _match_vswitch_epa(self, required, available):
1628 self.log.debug("VSwitch EPA match found")
1629 return True
1630
1631 def _match_hypervisor_epa(self, required, available):
1632 self.log.debug("Hypervisor EPA match found")
1633 return True
1634
1635 def _match_host_epa(self, required, available):
1636 self.log.info("Matching Host EPA attributes")
1637 if required.has_field('cpu_model'):
1638 self.log.debug("Matching CPU model")
1639 if available.has_field('cpu_model') == False:
1640 self.log.debug("Matching CPU model failed. Not available in flavor")
1641 return False
1642 else:
1643 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1644 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1645 self.log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1646 return False
1647 elif available.has_field('cpu_model'):
1648 self.log.debug("Rejecting available flavor because cpu_model not required but available")
1649 return False
1650
1651 if required.has_field('cpu_arch'):
1652 self.log.debug("Matching CPU architecture")
1653 if available.has_field('cpu_arch') == False:
1654 self.log.debug("Matching CPU architecture failed. Not available in flavor")
1655 return False
1656 else:
1657 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1658 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1659 self.log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1660 return False
1661 elif available.has_field('cpu_arch'):
1662 self.log.debug("Rejecting available flavor because cpu_arch not required but available")
1663 return False
1664
1665 if required.has_field('cpu_vendor'):
1666 self.log.debug("Matching CPU vendor")
1667 if available.has_field('cpu_vendor') == False:
1668 self.log.debug("Matching CPU vendor failed. Not available in flavor")
1669 return False
1670 else:
1671 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1672 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1673 self.log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1674 return False
1675 elif available.has_field('cpu_vendor'):
1676 self.log.debug("Rejecting available flavor because cpu_vendor not required but available")
1677 return False
1678
1679 if required.has_field('cpu_socket_count'):
1680 self.log.debug("Matching CPU socket count")
1681 if available.has_field('cpu_socket_count') == False:
1682 self.log.debug("Matching CPU socket count failed. Not available in flavor")
1683 return False
1684 else:
1685 if required.cpu_socket_count != available.cpu_socket_count:
1686 self.log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1687 return False
1688 elif available.has_field('cpu_socket_count'):
1689 self.log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1690 return False
1691
1692 if required.has_field('cpu_core_count'):
1693 self.log.debug("Matching CPU core count")
1694 if available.has_field('cpu_core_count') == False:
1695 self.log.debug("Matching CPU core count failed. Not available in flavor")
1696 return False
1697 else:
1698 if required.cpu_core_count != available.cpu_core_count:
1699 self.log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1700 return False
1701 elif available.has_field('cpu_core_count'):
1702 self.log.debug("Rejecting available flavor because cpu_core_count not required but available")
1703 return False
1704
1705 if required.has_field('cpu_core_thread_count'):
1706 self.log.debug("Matching CPU core thread count")
1707 if available.has_field('cpu_core_thread_count') == False:
1708 self.log.debug("Matching CPU core thread count failed. Not available in flavor")
1709 return False
1710 else:
1711 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1712 self.log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1713 return False
1714 elif available.has_field('cpu_core_thread_count'):
1715 self.log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1716 return False
1717
1718 if required.has_field('cpu_feature'):
1719 self.log.debug("Matching CPU feature list")
1720 if available.has_field('cpu_feature') == False:
1721 self.log.debug("Matching CPU feature list failed. Not available in flavor")
1722 return False
1723 else:
1724 for feature in required.cpu_feature:
1725 if feature not in available.cpu_feature:
1726 self.log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1727 return False
1728 elif available.has_field('cpu_feature'):
1729 self.log.debug("Rejecting available flavor because cpu_feature not required but available")
1730 return False
1731 self.log.info("Successful match for Host EPA attributes")
1732 return True
1733
1734
1735 def _match_placement_group_inputs(self, required, available):
1736 self.log.info("Matching Host aggregate attributes")
1737
1738 if not required and not available:
1739 # Host aggregate not required and not available => success
1740 self.log.info("Successful match for Host Aggregate attributes")
1741 return True
1742 if required and available:
1743 # Host aggregate requested and available => Do a match and decide
1744 xx = [ x.as_dict() for x in required ]
1745 yy = [ y.as_dict() for y in available ]
1746 for i in xx:
1747 if i not in yy:
1748 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1749 return False
1750 self.log.info("Successful match for Host Aggregate attributes")
1751 return True
1752 else:
1753 # Either of following conditions => Failure
1754 # - Host aggregate required but not available
1755 # - Host aggregate not required but available
1756 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1757 return False
1758
1759 def match_epa_params(self, resource_info, request_params):
1760 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1761 getattr(resource_info, 'vm_flavor'))
1762 if result == False:
1763 self.log.debug("VM Flavor mismatched")
1764 return False
1765
1766 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1767 getattr(resource_info, 'guest_epa'))
1768 if result == False:
1769 self.log.debug("Guest EPA mismatched")
1770 return False
1771
1772 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1773 getattr(resource_info, 'vswitch_epa'))
1774 if result == False:
1775 self.log.debug("Vswitch EPA mismatched")
1776 return False
1777
1778 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1779 getattr(resource_info, 'hypervisor_epa'))
1780 if result == False:
1781 self.log.debug("Hypervisor EPA mismatched")
1782 return False
1783
1784 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1785 getattr(resource_info, 'host_epa'))
1786 if result == False:
1787 self.log.debug("Host EPA mismatched")
1788 return False
1789
1790 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1791 getattr(resource_info, 'host_aggregate'))
1792
1793 if result == False:
1794 self.log.debug("Host Aggregate mismatched")
1795 return False
1796
1797 return True
1798
1799 def _select_resource_flavor(self, account, vdu_init):
1800 """
1801 Select a existing flavor if it matches the request or create new flavor
1802 """
1803 flavor = RwcalYang.FlavorInfoItem()
1804 flavor.name = str(uuid.uuid4())
1805 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1806 epa_dict = {k: v for k, v in vdu_init.as_dict().items() if k in epa_types}
1807 flavor.from_dict(epa_dict)
1808
1809 rc, response = self.do_get_flavor_list(account)
1810 if rc != RwTypes.RwStatus.SUCCESS:
1811 self.log.error("Get-flavor-info-list operation failed for cloud account: %s",
1812 account.name)
1813 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account.name))
1814
1815 flavor_id = None
1816 flavor_list = response.flavorinfo_list
1817 self.log.debug("Received %d flavor information from RW.CAL", len(flavor_list))
1818 for flv in flavor_list:
1819 self.log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1820 vdu_init.name, flv)
1821 if self.match_epa_params(flv, vdu_init):
1822 self.log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1823 vdu_init.name, flv.name, flv.id)
1824 return flv.id
1825
1826 if account.openstack.dynamic_flavor_support is False:
1827 self.log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init.name)
1828 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1829 else:
1830 rc,flavor_id = self.do_create_flavor(account,flavor)
1831 if rc != RwTypes.RwStatus.SUCCESS:
1832 self.log.error("Create-flavor operation failed for cloud account: %s",
1833 account.name)
1834 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account.name))
1835 return flavor_id
1836
1837 @rwcalstatus(ret_on_failure=[""])
1838 def do_create_vdu(self, account, vdu_init):
1839 """Create a new virtual deployment unit
1840
1841 Arguments:
1842 account - a cloud account
1843 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
1844
1845 Returns:
1846 The vdu_id
1847 """
1848 ### First create required number of ports aka connection points
1849 with self._use_driver(account) as drv:
1850 ### If floating_ip is required and we don't have one, better fail before any further allocation
1851 if vdu_init.has_field('allocate_public_address') and vdu_init.allocate_public_address:
1852 if account.openstack.has_field('floating_ip_pool'):
1853 pool_name = account.openstack.floating_ip_pool
1854 else:
1855 pool_name = None
1856 floating_ip = self._allocate_floating_ip(drv, pool_name)
1857 else:
1858 floating_ip = None
1859
1860 port_list = []
1861 network_list = []
1862 for c_point in vdu_init.connection_points:
1863 if c_point.virtual_link_id in network_list:
1864 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"
1865 else:
1866 network_list.append(c_point.virtual_link_id)
1867 port_id = self._create_connection_point(account, c_point)
1868 port_list.append(port_id)
1869
1870 if not vdu_init.has_field('flavor_id'):
1871 vdu_init.flavor_id = self._select_resource_flavor(account,vdu_init)
1872
1873 ### Check VDU Virtual Interface type and make sure VM with property exists
1874 if vdu_init.connection_points is not None:
1875 ### All virtual interfaces need to be of the same type for Openstack Accounts
1876 if not all(cp.type_yang == vdu_init.connection_points[0].type_yang for cp in vdu_init.connection_points):
1877 ### We have a mix of E1000 & VIRTIO virtual interface types in the VDU, abort instantiation.
1878 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO & E1000."
1879
1880 with self._use_driver(account) as drv:
1881 img_info = drv.glance_image_get(vdu_init.image_id)
1882
1883 virt_intf_type = vdu_init.connection_points[0].type_yang
1884 if virt_intf_type == 'E1000':
1885 if 'hw_vif_model' in img_info and img_info.hw_vif_model == 'e1000':
1886 self.log.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
1887 else:
1888 err_str = ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info.name)
1889 self.log.error(err_str)
1890 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1891 elif virt_intf_type == 'VIRTIO':
1892 if 'hw_vif_model' in img_info:
1893 err_str = ("VDU has Virtual Interface VIRTIO, but image '%s' has hw_vif_model mismatch" % img_info.name)
1894 self.log.error(err_str)
1895 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1896 else:
1897 self.log.debug("VDU has Virtual Interface VIRTIO, found matching image")
1898 else:
1899 err_str = ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type)
1900 self.log.error(err_str)
1901 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1902
1903 with self._use_driver(account) as drv:
1904 ### Now Create VM
1905 vm = RwcalYang.VMInfoItem()
1906 vm.vm_name = vdu_init.name
1907 vm.flavor_id = vdu_init.flavor_id
1908 vm.image_id = vdu_init.image_id
1909 vm_network = vm.network_list.add()
1910 vm_network.network_id = drv._mgmt_network_id
1911 if vdu_init.has_field('vdu_init') and vdu_init.vdu_init.has_field('userdata'):
1912 vm.cloud_init.userdata = vdu_init.vdu_init.userdata
1913
1914 if vdu_init.has_field('node_id'):
1915 vm.user_tags.node_id = vdu_init.node_id;
1916
1917 if vdu_init.has_field('availability_zone') and vdu_init.availability_zone.has_field('name'):
1918 vm.availability_zone = vdu_init.availability_zone.name
1919
1920 if vdu_init.has_field('server_group'):
1921 ### Get list of server group in openstack for name->id mapping
1922 openstack_group_list = drv.nova_server_group_list()
1923 group_id = [ i['id'] for i in openstack_group_list if i['name'] == vdu_init.server_group.name]
1924 if len(group_id) != 1:
1925 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]))
1926 vm.server_group = group_id[0]
1927
1928 for port_id in port_list:
1929 port = vm.port_list.add()
1930 port.port_id = port_id
1931
1932 pci_assignement = self.prepare_vpci_metadata(drv, vdu_init)
1933 if pci_assignement != '':
1934 vm.user_tags.pci_assignement = pci_assignement
1935
1936 vm_id = self.do_create_vm(account, vm, no_rwstatus=True)
1937 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
1938 return vm_id
1939
1940 def prepare_vpci_metadata(self, drv, vdu_init):
1941 pci_assignement = ''
1942 ### TEF specific metadata creation for
1943 virtio_vpci = []
1944 sriov_vpci = []
1945 virtio_meta = ''
1946 sriov_meta = ''
1947 ### For MGMT interface
1948 if vdu_init.has_field('mgmt_vpci'):
1949 xx = 'u\''+ drv._mgmt_network_id + '\' :[[u\'' + vdu_init.mgmt_vpci + '\', ' + '\'\']]'
1950 virtio_vpci.append(xx)
1951
1952 for c_point in vdu_init.connection_points:
1953 if c_point.has_field('vpci'):
1954 if c_point.has_field('vpci') and c_point.type_yang == 'VIRTIO':
1955 xx = 'u\''+c_point.virtual_link_id + '\' :[[u\'' + c_point.vpci + '\', ' + '\'\']]'
1956 virtio_vpci.append(xx)
1957 elif c_point.has_field('vpci') and c_point.type_yang == 'SR_IOV':
1958 xx = '[u\'' + c_point.vpci + '\', ' + '\'\']'
1959 sriov_vpci.append(xx)
1960
1961 if virtio_vpci:
1962 virtio_meta += ','.join(virtio_vpci)
1963
1964 if sriov_vpci:
1965 sriov_meta = 'u\'VF\': ['
1966 sriov_meta += ','.join(sriov_vpci)
1967 sriov_meta += ']'
1968
1969 if virtio_meta != '':
1970 pci_assignement += virtio_meta
1971 pci_assignement += ','
1972
1973 if sriov_meta != '':
1974 pci_assignement += sriov_meta
1975
1976 if pci_assignement != '':
1977 pci_assignement = '{' + pci_assignement + '}'
1978
1979 return pci_assignement
1980
1981
1982
1983 def prepare_vdu_on_boot(self, account, server_id, floating_ip):
1984 cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url,
1985 username = account.openstack.key,
1986 password = account.openstack.secret,
1987 tenant_name = account.openstack.tenant,
1988 mgmt_network = account.openstack.mgmt_network,
1989 server_id = server_id)
1990
1991 if floating_ip is not None:
1992 cmd += (" --floating_ip "+ floating_ip.ip)
1993
1994 exec_path = 'python3 ' + os.path.dirname(openstack_drv.__file__)
1995 exec_cmd = exec_path+'/'+cmd
1996 self.log.info("Running command: %s" %(exec_cmd))
1997 subprocess.call(exec_cmd, shell=True)
1998
1999 @rwstatus
2000 def do_modify_vdu(self, account, vdu_modify):
2001 """Modify Properties of existing virtual deployment unit
2002
2003 Arguments:
2004 account - a cloud account
2005 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2006 """
2007 ### First create required number of ports aka connection points
2008 port_list = []
2009 network_list = []
2010 for c_point in vdu_modify.connection_points_add:
2011 if c_point.virtual_link_id in network_list:
2012 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"
2013 else:
2014 network_list.append(c_point.virtual_link_id)
2015 port_id = self._create_connection_point(account, c_point)
2016 port_list.append(port_id)
2017
2018 ### Now add the ports to VM
2019 for port_id in port_list:
2020 with self._use_driver(account) as drv:
2021 drv.nova_server_add_port(vdu_modify.vdu_id, port_id)
2022
2023 ### Delete the requested connection_points
2024 for c_point in vdu_modify.connection_points_remove:
2025 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
2026
2027 if vdu_modify.has_field('image_id'):
2028 with self._use_driver(account) as drv:
2029 drv.nova_server_rebuild(vdu_modify.vdu_id, vdu_modify.image_id)
2030
2031
2032 @rwstatus
2033 def do_delete_vdu(self, account, vdu_id):
2034 """Delete a virtual deployment unit
2035
2036 Arguments:
2037 account - a cloud account
2038 vdu_id - id for the vdu to be deleted
2039
2040 Returns:
2041 None
2042 """
2043 if not vdu_id:
2044 self.log.error("empty vdu_id during the vdu deletion")
2045 return
2046
2047 with self._use_driver(account) as drv:
2048 ### Get list of floating_ips associated with this instance and delete them
2049 floating_ips = [ f for f in drv.nova_floating_ip_list() if f.instance_id == vdu_id ]
2050 for f in floating_ips:
2051 drv.nova_drv.floating_ip_delete(f)
2052
2053 ### Get list of port on VM and delete them.
2054 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2055
2056 for port in port_list:
2057 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
2058 self.do_delete_port(account, port['id'], no_rwstatus=True)
2059
2060 self.do_delete_vm(account, vdu_id, no_rwstatus=True)
2061
2062
2063 @rwstatus(ret_on_failure=[None])
2064 def do_get_vdu(self, account, vdu_id):
2065 """Get information about a virtual deployment unit.
2066
2067 Arguments:
2068 account - a cloud account
2069 vdu_id - id for the vdu
2070
2071 Returns:
2072 Object of type RwcalYang.VDUInfoParams
2073 """
2074 with self._use_driver(account) as drv:
2075
2076 ### Get list of ports excluding the one for management network
2077 port_list = [p for p in drv.neutron_port_list(**{'device_id': vdu_id}) if p['network_id'] != drv.get_mgmt_network_id()]
2078
2079 vm = drv.nova_server_get(vdu_id)
2080
2081 flavor_info = None
2082 if ('flavor' in vm) and ('id' in vm['flavor']):
2083 try:
2084 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2085 except Exception as e:
2086 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2087
2088 openstack_group_list = drv.nova_server_group_list()
2089 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2090 vdu_info = RwcalOpenstackPlugin._fill_vdu_info(vm,
2091 flavor_info,
2092 account.openstack.mgmt_network,
2093 port_list,
2094 server_group)
2095 if vdu_info.state == 'active':
2096 try:
2097 console_info = drv.nova_server_console(vdu_info.vdu_id)
2098 except Exception as e:
2099 pass
2100 else:
2101 vdu_info.console_url = console_info['console']['url']
2102 pass
2103
2104 return vdu_info
2105
2106
2107 @rwstatus(ret_on_failure=[None])
2108 def do_get_vdu_list(self, account):
2109 """Get information about all the virtual deployment units
2110
2111 Arguments:
2112 account - a cloud account
2113
2114 Returns:
2115 A list of objects of type RwcalYang.VDUInfoParams
2116 """
2117 vnf_resources = RwcalYang.VNFResources()
2118 with self._use_driver(account) as drv:
2119 vms = drv.nova_server_list()
2120 for vm in vms:
2121 ### Get list of ports excluding one for management network
2122 port_list = [p for p in drv.neutron_port_list(**{'device_id': vm['id']}) if p['network_id'] != drv.get_mgmt_network_id()]
2123
2124 flavor_info = None
2125
2126 if ('flavor' in vm) and ('id' in vm['flavor']):
2127 try:
2128 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2129 except Exception as e:
2130 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2131
2132 else:
2133 flavor_info = None
2134
2135 openstack_group_list = drv.nova_server_group_list()
2136 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2137
2138 vdu = RwcalOpenstackPlugin._fill_vdu_info(vm,
2139 flavor_info,
2140 account.openstack.mgmt_network,
2141 port_list,
2142 server_group)
2143 if vdu.state == 'active':
2144 try:
2145 console_info = drv.nova_server_console(vdu.vdu_id)
2146 except Exception as e:
2147 pass
2148 else:
2149 vdu.console_url = console_info['console']['url']
2150 pass
2151 vnf_resources.vdu_info_list.append(vdu)
2152 return vnf_resources
2153
2154