ddf665acd3f6cd1561fdcad3894d24b6a9322f01
[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 'mac_address' in port_info :
1163 c_point.mac_addr = port_info['mac_address']
1164 if port_info['status'] == 'ACTIVE':
1165 c_point.state = 'active'
1166 else:
1167 c_point.state = 'inactive'
1168 if 'network_id' in port_info:
1169 c_point.virtual_link_id = port_info['network_id']
1170 if ('device_id' in port_info) and (port_info['device_id']):
1171 c_point.vdu_id = port_info['device_id']
1172
1173 @staticmethod
1174 def _fill_virtual_link_info(network_info, port_list, subnet):
1175 """Create a GI object for VirtualLinkInfoParams
1176
1177 Converts Network and Port information dictionary object
1178 returned by openstack driver into Protobuf Gi Object
1179
1180 Arguments:
1181 network_info - Network information from openstack
1182 port_list - A list of port information from openstack
1183 subnet: Subnet information from openstack
1184 Returns:
1185 Protobuf Gi object for VirtualLinkInfoParams
1186 """
1187 link = RwcalYang.VirtualLinkInfoParams()
1188 link.name = network_info['name']
1189 if network_info['status'] == 'ACTIVE':
1190 link.state = 'active'
1191 else:
1192 link.state = 'inactive'
1193 link.virtual_link_id = network_info['id']
1194 for port in port_list:
1195 if port['device_owner'] == 'compute:None':
1196 c_point = link.connection_points.add()
1197 RwcalOpenstackPlugin._fill_connection_point_info(c_point, port)
1198
1199 if subnet != None:
1200 link.subnet = subnet['cidr']
1201
1202 if ('provider:network_type' in network_info) and (network_info['provider:network_type'] != None):
1203 link.provider_network.overlay_type = network_info['provider:network_type'].upper()
1204 if ('provider:segmentation_id' in network_info) and (network_info['provider:segmentation_id']):
1205 link.provider_network.segmentation_id = network_info['provider:segmentation_id']
1206 if ('provider:physical_network' in network_info) and (network_info['provider:physical_network']):
1207 link.provider_network.physical_network = network_info['provider:physical_network'].upper()
1208
1209 return link
1210
1211 @staticmethod
1212 def _fill_vdu_info(vm_info, flavor_info, mgmt_network, port_list, server_group):
1213 """Create a GI object for VDUInfoParams
1214
1215 Converts VM information dictionary object returned by openstack
1216 driver into Protobuf Gi Object
1217
1218 Arguments:
1219 vm_info - VM information from openstack
1220 flavor_info - VM Flavor information from openstack
1221 mgmt_network - Management network
1222 port_list - A list of port information from openstack
1223 server_group - A list (with one element or empty list) of server group to which this VM belongs
1224 Returns:
1225 Protobuf Gi object for VDUInfoParams
1226 """
1227 vdu = RwcalYang.VDUInfoParams()
1228 vdu.name = vm_info['name']
1229 vdu.vdu_id = vm_info['id']
1230 for network_name, network_info in vm_info['addresses'].items():
1231 if network_info and network_name == mgmt_network:
1232 for interface in network_info:
1233 if 'OS-EXT-IPS:type' in interface:
1234 if interface['OS-EXT-IPS:type'] == 'fixed':
1235 vdu.management_ip = interface['addr']
1236 elif interface['OS-EXT-IPS:type'] == 'floating':
1237 vdu.public_ip = interface['addr']
1238
1239 # Look for any metadata
1240 for key, value in vm_info['metadata'].items():
1241 if key == 'node_id':
1242 vdu.node_id = value
1243 if ('image' in vm_info) and ('id' in vm_info['image']):
1244 vdu.image_id = vm_info['image']['id']
1245 if ('flavor' in vm_info) and ('id' in vm_info['flavor']):
1246 vdu.flavor_id = vm_info['flavor']['id']
1247
1248 if vm_info['status'] == 'ACTIVE':
1249 vdu.state = 'active'
1250 elif vm_info['status'] == 'ERROR':
1251 vdu.state = 'failed'
1252 else:
1253 vdu.state = 'inactive'
1254
1255 if 'availability_zone' in vm_info:
1256 vdu.availability_zone = vm_info['availability_zone']
1257
1258 if server_group:
1259 vdu.server_group.name = server_group[0]
1260
1261 vdu.cloud_type = 'openstack'
1262 # Fill the port information
1263 for port in port_list:
1264 c_point = vdu.connection_points.add()
1265 RwcalOpenstackPlugin._fill_connection_point_info(c_point, port)
1266
1267 if flavor_info is not None:
1268 RwcalOpenstackPlugin._fill_epa_attributes(vdu, flavor_info)
1269 return vdu
1270
1271 @rwcalstatus(ret_on_failure=[""])
1272 def do_create_virtual_link(self, account, link_params):
1273 """Create a new virtual link
1274
1275 Arguments:
1276 account - a cloud account
1277 link_params - information that defines the type of VDU to create
1278
1279 Returns:
1280 The vdu_id
1281 """
1282 kwargs = {}
1283 kwargs['name'] = link_params.name
1284 kwargs['admin_state_up'] = True
1285 kwargs['external_router'] = False
1286 kwargs['shared'] = False
1287
1288 if link_params.has_field('provider_network'):
1289 if link_params.provider_network.has_field('physical_network'):
1290 kwargs['physical_network'] = link_params.provider_network.physical_network
1291 if link_params.provider_network.has_field('overlay_type'):
1292 kwargs['network_type'] = link_params.provider_network.overlay_type.lower()
1293 if link_params.provider_network.has_field('segmentation_id'):
1294 kwargs['segmentation_id'] = link_params.provider_network.segmentation_id
1295
1296
1297 with self._use_driver(account) as drv:
1298 try:
1299 network_id = drv.neutron_network_create(**kwargs)
1300 except Exception as e:
1301 self.log.error("Encountered exceptions during network creation. Exception: %s", str(e))
1302 raise
1303
1304 kwargs = {'network_id' : network_id,
1305 'dhcp_params': {'enable_dhcp': True},
1306 'gateway_ip' : None,}
1307
1308 if link_params.ip_profile_params.has_field('ip_version'):
1309 kwargs['ip_version'] = 6 if link_params.ip_profile_params.ip_version == 'ipv6' else 4
1310 else:
1311 kwargs['ip_version'] = 4
1312
1313 if link_params.ip_profile_params.has_field('subnet_address'):
1314 kwargs['cidr'] = link_params.ip_profile_params.subnet_address
1315 elif link_params.ip_profile_params.has_field('subnet_prefix_pool'):
1316 subnet_pool = drv.netruon_subnetpool_by_name(link_params.ip_profile_params.subnet_prefix_pool)
1317 if subnet_pool is None:
1318 self.log.error("Could not find subnet pool with name :%s to be used for network: %s",
1319 link_params.ip_profile_params.subnet_prefix_pool,
1320 link_params.name)
1321 raise NeutronException.NotFound("SubnetPool with name %s not found"%(link_params.ip_profile_params.subnet_prefix_pool))
1322
1323 kwargs['subnetpool_id'] = subnet_pool['id']
1324 elif link_params.has_field('subnet'):
1325 kwargs['cidr'] = link_params.subnet
1326 else:
1327 assert 0, "No IP Prefix or Pool name specified"
1328
1329 if link_params.ip_profile_params.has_field('dhcp_params'):
1330 if link_params.ip_profile_params.dhcp_params.has_field('enabled'):
1331 kwargs['dhcp_params']['enable_dhcp'] = link_params.ip_profile_params.dhcp_params.enabled
1332 if link_params.ip_profile_params.dhcp_params.has_field('start_address'):
1333 kwargs['dhcp_params']['start_address'] = link_params.ip_profile_params.dhcp_params.start_address
1334 if link_params.ip_profile_params.dhcp_params.has_field('count'):
1335 kwargs['dhcp_params']['count'] = link_params.ip_profile_params.dhcp_params.count
1336
1337 if link_params.ip_profile_params.has_field('dns_server'):
1338 kwargs['dns_server'] = []
1339 for server in link_params.ip_profile_params.dns_server:
1340 kwargs['dns_server'].append(server.address)
1341
1342 if link_params.ip_profile_params.has_field('gateway_address'):
1343 kwargs['gateway_ip'] = link_params.ip_profile_params.gateway_address
1344
1345 drv.neutron_subnet_create(**kwargs)
1346
1347 return network_id
1348
1349
1350 @rwstatus
1351 def do_delete_virtual_link(self, account, link_id):
1352 """Delete a virtual link
1353
1354 Arguments:
1355 account - a cloud account
1356 link_id - id for the virtual-link to be deleted
1357
1358 Returns:
1359 None
1360 """
1361 if not link_id:
1362 self.log.error("Empty link_id during the virtual link deletion")
1363 raise Exception("Empty link_id during the virtual link deletion")
1364
1365 with self._use_driver(account) as drv:
1366 port_list = drv.neutron_port_list(**{'network_id': link_id})
1367
1368 for port in port_list:
1369 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
1370 self.do_delete_port(account, port['id'], no_rwstatus=True)
1371 self.do_delete_network(account, link_id, no_rwstatus=True)
1372
1373 @rwstatus(ret_on_failure=[None])
1374 def do_get_virtual_link(self, account, link_id):
1375 """Get information about virtual link.
1376
1377 Arguments:
1378 account - a cloud account
1379 link_id - id for the virtual-link
1380
1381 Returns:
1382 Object of type RwcalYang.VirtualLinkInfoParams
1383 """
1384 if not link_id:
1385 self.log.error("Empty link_id during the virtual link get request")
1386 raise Exception("Empty link_id during the virtual link get request")
1387
1388 with self._use_driver(account) as drv:
1389 network = drv.neutron_network_get(link_id)
1390 if network:
1391 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1392 if 'subnets' in network:
1393 subnet = drv.neutron_subnet_get(network['subnets'][0])
1394 else:
1395 subnet = None
1396 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1397 else:
1398 virtual_link = None
1399 return virtual_link
1400
1401 @rwstatus(ret_on_failure=[None])
1402 def do_get_virtual_link_list(self, account):
1403 """Get information about all the virtual links
1404
1405 Arguments:
1406 account - a cloud account
1407
1408 Returns:
1409 A list of objects of type RwcalYang.VirtualLinkInfoParams
1410 """
1411 vnf_resources = RwcalYang.VNFResources()
1412 with self._use_driver(account) as drv:
1413 networks = drv.neutron_network_list()
1414 for network in networks:
1415 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1416 if ('subnets' in network) and (network['subnets']):
1417 subnet = drv.neutron_subnet_get(network['subnets'][0])
1418 else:
1419 subnet = None
1420 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1421 vnf_resources.virtual_link_info_list.append(virtual_link)
1422 return vnf_resources
1423
1424 def _create_connection_point(self, account, c_point):
1425 """
1426 Create a connection point
1427 Arguments:
1428 account - a cloud account
1429 c_point - connection_points
1430 """
1431 kwargs = {}
1432 kwargs['name'] = c_point.name
1433 kwargs['network_id'] = c_point.virtual_link_id
1434 kwargs['admin_state_up'] = True
1435
1436 if c_point.type_yang == 'VIRTIO' or c_point.type_yang == 'E1000':
1437 kwargs['port_type'] = 'normal'
1438 elif c_point.type_yang == 'SR_IOV':
1439 kwargs['port_type'] = 'direct'
1440 else:
1441 raise NotImplementedError("Port Type: %s not supported" %(c_point.type_yang))
1442
1443 with self._use_driver(account) as drv:
1444 if c_point.has_field('security_group'):
1445 group = drv.neutron_security_group_by_name(c_point.security_group)
1446 if group is not None:
1447 kwargs['security_groups'] = [group['id']]
1448 return drv.neutron_port_create(**kwargs)
1449
1450 def _allocate_floating_ip(self, drv, pool_name):
1451 """
1452 Allocate a floating_ip. If unused floating_ip exists then its reused.
1453 Arguments:
1454 drv: OpenstackDriver instance
1455 pool_name: Floating IP pool name
1456
1457 Returns:
1458 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1459 """
1460
1461 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1462
1463 # if pool_name is not None:
1464 # ### Filter further based on IP address
1465 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1466
1467 # if not available_ip:
1468 # floating_ip = drv.nova_floating_ip_create(pool_name)
1469 # else:
1470 # floating_ip = available_ip[0]
1471
1472 floating_ip = drv.nova_floating_ip_create(pool_name)
1473 return floating_ip
1474
1475 def _match_vm_flavor(self, required, available):
1476 self.log.info("Matching VM Flavor attributes")
1477 if available.vcpu_count != required.vcpu_count:
1478 self.log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1479 required.vcpu_count,
1480 available.vcpu_count)
1481 return False
1482 if available.memory_mb != required.memory_mb:
1483 self.log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1484 required.memory_mb,
1485 available.memory_mb)
1486 return False
1487 if available.storage_gb != required.storage_gb:
1488 self.log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1489 required.storage_gb,
1490 available.storage_gb)
1491 return False
1492 self.log.debug("VM Flavor match found")
1493 return True
1494
1495 def _match_guest_epa(self, required, available):
1496 self.log.info("Matching Guest EPA attributes")
1497 if required.has_field('pcie_device'):
1498 self.log.debug("Matching pcie_device")
1499 if available.has_field('pcie_device') == False:
1500 self.log.debug("Matching pcie_device failed. Not available in flavor")
1501 return False
1502 else:
1503 for dev in required.pcie_device:
1504 if not [ d for d in available.pcie_device
1505 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
1506 self.log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
1507 return False
1508 elif available.has_field('pcie_device'):
1509 self.log.debug("Rejecting available flavor because pcie_device not required but available")
1510 return False
1511
1512
1513 if required.has_field('mempage_size'):
1514 self.log.debug("Matching mempage_size")
1515 if available.has_field('mempage_size') == False:
1516 self.log.debug("Matching mempage_size failed. Not available in flavor")
1517 return False
1518 else:
1519 if required.mempage_size != available.mempage_size:
1520 self.log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
1521 return False
1522 elif available.has_field('mempage_size'):
1523 self.log.debug("Rejecting available flavor because mempage_size not required but available")
1524 return False
1525
1526 if required.has_field('cpu_pinning_policy'):
1527 self.log.debug("Matching cpu_pinning_policy")
1528 if required.cpu_pinning_policy != 'ANY':
1529 if available.has_field('cpu_pinning_policy') == False:
1530 self.log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1531 return False
1532 else:
1533 if required.cpu_pinning_policy != available.cpu_pinning_policy:
1534 self.log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
1535 return False
1536 elif available.has_field('cpu_pinning_policy'):
1537 self.log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1538 return False
1539
1540 if required.has_field('cpu_thread_pinning_policy'):
1541 self.log.debug("Matching cpu_thread_pinning_policy")
1542 if available.has_field('cpu_thread_pinning_policy') == False:
1543 self.log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1544 return False
1545 else:
1546 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
1547 self.log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
1548 return False
1549 elif available.has_field('cpu_thread_pinning_policy'):
1550 self.log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1551 return False
1552
1553 if required.has_field('trusted_execution'):
1554 self.log.debug("Matching trusted_execution")
1555 if required.trusted_execution == True:
1556 if available.has_field('trusted_execution') == False:
1557 self.log.debug("Matching trusted_execution failed. Not available in flavor")
1558 return False
1559 else:
1560 if required.trusted_execution != available.trusted_execution:
1561 self.log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
1562 return False
1563 elif available.has_field('trusted_execution'):
1564 self.log.debug("Rejecting available flavor because trusted_execution not required but available")
1565 return False
1566
1567 if required.has_field('numa_node_policy'):
1568 self.log.debug("Matching numa_node_policy")
1569 if available.has_field('numa_node_policy') == False:
1570 self.log.debug("Matching numa_node_policy failed. Not available in flavor")
1571 return False
1572 else:
1573 if required.numa_node_policy.has_field('node_cnt'):
1574 self.log.debug("Matching numa_node_policy node_cnt")
1575 if available.numa_node_policy.has_field('node_cnt') == False:
1576 self.log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1577 return False
1578 else:
1579 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
1580 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)
1581 return False
1582 elif available.numa_node_policy.has_field('node_cnt'):
1583 self.log.debug("Rejecting available flavor because numa node count not required but available")
1584 return False
1585
1586 if required.numa_node_policy.has_field('mem_policy'):
1587 self.log.debug("Matching numa_node_policy mem_policy")
1588 if available.numa_node_policy.has_field('mem_policy') == False:
1589 self.log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1590 return False
1591 else:
1592 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
1593 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)
1594 return False
1595 elif available.numa_node_policy.has_field('mem_policy'):
1596 self.log.debug("Rejecting available flavor because num node mem_policy not required but available")
1597 return False
1598
1599 if required.numa_node_policy.has_field('node'):
1600 self.log.debug("Matching numa_node_policy nodes configuration")
1601 if available.numa_node_policy.has_field('node') == False:
1602 self.log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1603 return False
1604 for required_node in required.numa_node_policy.node:
1605 self.log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1606 numa_match = False
1607 for available_node in available.numa_node_policy.node:
1608 if required_node.id != available_node.id:
1609 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1610 continue
1611 if required_node.vcpu != available_node.vcpu:
1612 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1613 continue
1614 if required_node.memory_mb != available_node.memory_mb:
1615 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1616 continue
1617 numa_match = True
1618 if numa_match == False:
1619 return False
1620 elif available.numa_node_policy.has_field('node'):
1621 self.log.debug("Rejecting available flavor because numa nodes not required but available")
1622 return False
1623 elif available.has_field('numa_node_policy'):
1624 self.log.debug("Rejecting available flavor because numa_node_policy not required but available")
1625 return False
1626 self.log.info("Successful match for Guest EPA attributes")
1627 return True
1628
1629 def _match_vswitch_epa(self, required, available):
1630 self.log.debug("VSwitch EPA match found")
1631 return True
1632
1633 def _match_hypervisor_epa(self, required, available):
1634 self.log.debug("Hypervisor EPA match found")
1635 return True
1636
1637 def _match_host_epa(self, required, available):
1638 self.log.info("Matching Host EPA attributes")
1639 if required.has_field('cpu_model'):
1640 self.log.debug("Matching CPU model")
1641 if available.has_field('cpu_model') == False:
1642 self.log.debug("Matching CPU model failed. Not available in flavor")
1643 return False
1644 else:
1645 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1646 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1647 self.log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1648 return False
1649 elif available.has_field('cpu_model'):
1650 self.log.debug("Rejecting available flavor because cpu_model not required but available")
1651 return False
1652
1653 if required.has_field('cpu_arch'):
1654 self.log.debug("Matching CPU architecture")
1655 if available.has_field('cpu_arch') == False:
1656 self.log.debug("Matching CPU architecture failed. Not available in flavor")
1657 return False
1658 else:
1659 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1660 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1661 self.log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1662 return False
1663 elif available.has_field('cpu_arch'):
1664 self.log.debug("Rejecting available flavor because cpu_arch not required but available")
1665 return False
1666
1667 if required.has_field('cpu_vendor'):
1668 self.log.debug("Matching CPU vendor")
1669 if available.has_field('cpu_vendor') == False:
1670 self.log.debug("Matching CPU vendor failed. Not available in flavor")
1671 return False
1672 else:
1673 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1674 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1675 self.log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1676 return False
1677 elif available.has_field('cpu_vendor'):
1678 self.log.debug("Rejecting available flavor because cpu_vendor not required but available")
1679 return False
1680
1681 if required.has_field('cpu_socket_count'):
1682 self.log.debug("Matching CPU socket count")
1683 if available.has_field('cpu_socket_count') == False:
1684 self.log.debug("Matching CPU socket count failed. Not available in flavor")
1685 return False
1686 else:
1687 if required.cpu_socket_count != available.cpu_socket_count:
1688 self.log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1689 return False
1690 elif available.has_field('cpu_socket_count'):
1691 self.log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1692 return False
1693
1694 if required.has_field('cpu_core_count'):
1695 self.log.debug("Matching CPU core count")
1696 if available.has_field('cpu_core_count') == False:
1697 self.log.debug("Matching CPU core count failed. Not available in flavor")
1698 return False
1699 else:
1700 if required.cpu_core_count != available.cpu_core_count:
1701 self.log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1702 return False
1703 elif available.has_field('cpu_core_count'):
1704 self.log.debug("Rejecting available flavor because cpu_core_count not required but available")
1705 return False
1706
1707 if required.has_field('cpu_core_thread_count'):
1708 self.log.debug("Matching CPU core thread count")
1709 if available.has_field('cpu_core_thread_count') == False:
1710 self.log.debug("Matching CPU core thread count failed. Not available in flavor")
1711 return False
1712 else:
1713 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1714 self.log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1715 return False
1716 elif available.has_field('cpu_core_thread_count'):
1717 self.log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1718 return False
1719
1720 if required.has_field('cpu_feature'):
1721 self.log.debug("Matching CPU feature list")
1722 if available.has_field('cpu_feature') == False:
1723 self.log.debug("Matching CPU feature list failed. Not available in flavor")
1724 return False
1725 else:
1726 for feature in required.cpu_feature:
1727 if feature not in available.cpu_feature:
1728 self.log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1729 return False
1730 elif available.has_field('cpu_feature'):
1731 self.log.debug("Rejecting available flavor because cpu_feature not required but available")
1732 return False
1733 self.log.info("Successful match for Host EPA attributes")
1734 return True
1735
1736
1737 def _match_placement_group_inputs(self, required, available):
1738 self.log.info("Matching Host aggregate attributes")
1739
1740 if not required and not available:
1741 # Host aggregate not required and not available => success
1742 self.log.info("Successful match for Host Aggregate attributes")
1743 return True
1744 if required and available:
1745 # Host aggregate requested and available => Do a match and decide
1746 xx = [ x.as_dict() for x in required ]
1747 yy = [ y.as_dict() for y in available ]
1748 for i in xx:
1749 if i not in yy:
1750 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1751 return False
1752 self.log.info("Successful match for Host Aggregate attributes")
1753 return True
1754 else:
1755 # Either of following conditions => Failure
1756 # - Host aggregate required but not available
1757 # - Host aggregate not required but available
1758 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1759 return False
1760
1761 def match_epa_params(self, resource_info, request_params):
1762 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1763 getattr(resource_info, 'vm_flavor'))
1764 if result == False:
1765 self.log.debug("VM Flavor mismatched")
1766 return False
1767
1768 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1769 getattr(resource_info, 'guest_epa'))
1770 if result == False:
1771 self.log.debug("Guest EPA mismatched")
1772 return False
1773
1774 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1775 getattr(resource_info, 'vswitch_epa'))
1776 if result == False:
1777 self.log.debug("Vswitch EPA mismatched")
1778 return False
1779
1780 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1781 getattr(resource_info, 'hypervisor_epa'))
1782 if result == False:
1783 self.log.debug("Hypervisor EPA mismatched")
1784 return False
1785
1786 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1787 getattr(resource_info, 'host_epa'))
1788 if result == False:
1789 self.log.debug("Host EPA mismatched")
1790 return False
1791
1792 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1793 getattr(resource_info, 'host_aggregate'))
1794
1795 if result == False:
1796 self.log.debug("Host Aggregate mismatched")
1797 return False
1798
1799 return True
1800
1801 def _select_resource_flavor(self, account, vdu_init):
1802 """
1803 Select a existing flavor if it matches the request or create new flavor
1804 """
1805 flavor = RwcalYang.FlavorInfoItem()
1806 flavor.name = str(uuid.uuid4())
1807 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1808 epa_dict = {k: v for k, v in vdu_init.as_dict().items() if k in epa_types}
1809 flavor.from_dict(epa_dict)
1810
1811 rc, response = self.do_get_flavor_list(account)
1812 if rc != RwTypes.RwStatus.SUCCESS:
1813 self.log.error("Get-flavor-info-list operation failed for cloud account: %s",
1814 account.name)
1815 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account.name))
1816
1817 flavor_id = None
1818 flavor_list = response.flavorinfo_list
1819 self.log.debug("Received %d flavor information from RW.CAL", len(flavor_list))
1820 for flv in flavor_list:
1821 self.log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1822 vdu_init.name, flv)
1823 if self.match_epa_params(flv, vdu_init):
1824 self.log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1825 vdu_init.name, flv.name, flv.id)
1826 return flv.id
1827
1828 if account.openstack.dynamic_flavor_support is False:
1829 self.log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init.name)
1830 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1831 else:
1832 rc,flavor_id = self.do_create_flavor(account,flavor)
1833 if rc != RwTypes.RwStatus.SUCCESS:
1834 self.log.error("Create-flavor operation failed for cloud account: %s",
1835 account.name)
1836 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account.name))
1837 return flavor_id
1838
1839 @rwcalstatus(ret_on_failure=[""])
1840 def do_create_vdu(self, account, vdu_init):
1841 """Create a new virtual deployment unit
1842
1843 Arguments:
1844 account - a cloud account
1845 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
1846
1847 Returns:
1848 The vdu_id
1849 """
1850 ### First create required number of ports aka connection points
1851 with self._use_driver(account) as drv:
1852 ### If floating_ip is required and we don't have one, better fail before any further allocation
1853 if vdu_init.has_field('allocate_public_address') and vdu_init.allocate_public_address:
1854 if account.openstack.has_field('floating_ip_pool'):
1855 pool_name = account.openstack.floating_ip_pool
1856 else:
1857 pool_name = None
1858 floating_ip = self._allocate_floating_ip(drv, pool_name)
1859 else:
1860 floating_ip = None
1861
1862 port_list = []
1863 network_list = []
1864 for c_point in vdu_init.connection_points:
1865 if c_point.virtual_link_id in network_list:
1866 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"
1867 else:
1868 network_list.append(c_point.virtual_link_id)
1869 port_id = self._create_connection_point(account, c_point)
1870 port_list.append(port_id)
1871
1872 if not vdu_init.has_field('flavor_id'):
1873 vdu_init.flavor_id = self._select_resource_flavor(account,vdu_init)
1874
1875 ### Check VDU Virtual Interface type and make sure VM with property exists
1876 if vdu_init.connection_points is not None:
1877 ### All virtual interfaces need to be of the same type for Openstack Accounts
1878 if not all(cp.type_yang == vdu_init.connection_points[0].type_yang for cp in vdu_init.connection_points):
1879 ### We have a mix of E1000 & VIRTIO virtual interface types in the VDU, abort instantiation.
1880 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO & E1000."
1881
1882 with self._use_driver(account) as drv:
1883 img_info = drv.glance_image_get(vdu_init.image_id)
1884
1885 virt_intf_type = vdu_init.connection_points[0].type_yang
1886 if virt_intf_type == 'E1000':
1887 if 'hw_vif_model' in img_info and img_info.hw_vif_model == 'e1000':
1888 self.log.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
1889 else:
1890 err_str = ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info.name)
1891 self.log.error(err_str)
1892 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1893 elif virt_intf_type == 'VIRTIO':
1894 if 'hw_vif_model' in img_info:
1895 err_str = ("VDU has Virtual Interface VIRTIO, but image '%s' has hw_vif_model mismatch" % img_info.name)
1896 self.log.error(err_str)
1897 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1898 else:
1899 self.log.debug("VDU has Virtual Interface VIRTIO, found matching image")
1900 else:
1901 err_str = ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type)
1902 self.log.error(err_str)
1903 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1904
1905 with self._use_driver(account) as drv:
1906 ### Now Create VM
1907 vm = RwcalYang.VMInfoItem()
1908 vm.vm_name = vdu_init.name
1909 vm.flavor_id = vdu_init.flavor_id
1910 vm.image_id = vdu_init.image_id
1911 vm_network = vm.network_list.add()
1912 vm_network.network_id = drv._mgmt_network_id
1913 if vdu_init.has_field('vdu_init') and vdu_init.vdu_init.has_field('userdata'):
1914 vm.cloud_init.userdata = vdu_init.vdu_init.userdata
1915
1916 if vdu_init.has_field('node_id'):
1917 vm.user_tags.node_id = vdu_init.node_id;
1918
1919 if vdu_init.has_field('availability_zone') and vdu_init.availability_zone.has_field('name'):
1920 vm.availability_zone = vdu_init.availability_zone.name
1921
1922 if vdu_init.has_field('server_group'):
1923 ### Get list of server group in openstack for name->id mapping
1924 openstack_group_list = drv.nova_server_group_list()
1925 group_id = [ i['id'] for i in openstack_group_list if i['name'] == vdu_init.server_group.name]
1926 if len(group_id) != 1:
1927 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]))
1928 vm.server_group = group_id[0]
1929
1930 for port_id in port_list:
1931 port = vm.port_list.add()
1932 port.port_id = port_id
1933
1934 pci_assignement = self.prepare_vpci_metadata(drv, vdu_init)
1935 if pci_assignement != '':
1936 vm.user_tags.pci_assignement = pci_assignement
1937
1938 vm_id = self.do_create_vm(account, vm, no_rwstatus=True)
1939 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
1940 return vm_id
1941
1942 def prepare_vpci_metadata(self, drv, vdu_init):
1943 pci_assignement = ''
1944 ### TEF specific metadata creation for
1945 virtio_vpci = []
1946 sriov_vpci = []
1947 virtio_meta = ''
1948 sriov_meta = ''
1949 ### For MGMT interface
1950 if vdu_init.has_field('mgmt_vpci'):
1951 xx = 'u\''+ drv._mgmt_network_id + '\' :[[u\'' + vdu_init.mgmt_vpci + '\', ' + '\'\']]'
1952 virtio_vpci.append(xx)
1953
1954 for c_point in vdu_init.connection_points:
1955 if c_point.has_field('vpci'):
1956 if c_point.has_field('vpci') and c_point.type_yang == 'VIRTIO':
1957 xx = 'u\''+c_point.virtual_link_id + '\' :[[u\'' + c_point.vpci + '\', ' + '\'\']]'
1958 virtio_vpci.append(xx)
1959 elif c_point.has_field('vpci') and c_point.type_yang == 'SR_IOV':
1960 xx = '[u\'' + c_point.vpci + '\', ' + '\'\']'
1961 sriov_vpci.append(xx)
1962
1963 if virtio_vpci:
1964 virtio_meta += ','.join(virtio_vpci)
1965
1966 if sriov_vpci:
1967 sriov_meta = 'u\'VF\': ['
1968 sriov_meta += ','.join(sriov_vpci)
1969 sriov_meta += ']'
1970
1971 if virtio_meta != '':
1972 pci_assignement += virtio_meta
1973 pci_assignement += ','
1974
1975 if sriov_meta != '':
1976 pci_assignement += sriov_meta
1977
1978 if pci_assignement != '':
1979 pci_assignement = '{' + pci_assignement + '}'
1980
1981 return pci_assignement
1982
1983
1984
1985 def prepare_vdu_on_boot(self, account, server_id, floating_ip):
1986 cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url,
1987 username = account.openstack.key,
1988 password = account.openstack.secret,
1989 tenant_name = account.openstack.tenant,
1990 mgmt_network = account.openstack.mgmt_network,
1991 server_id = server_id)
1992
1993 if floating_ip is not None:
1994 cmd += (" --floating_ip "+ floating_ip.ip)
1995
1996 exec_path = 'python3 ' + os.path.dirname(openstack_drv.__file__)
1997 exec_cmd = exec_path+'/'+cmd
1998 self.log.info("Running command: %s" %(exec_cmd))
1999 subprocess.call(exec_cmd, shell=True)
2000
2001 @rwstatus
2002 def do_modify_vdu(self, account, vdu_modify):
2003 """Modify Properties of existing virtual deployment unit
2004
2005 Arguments:
2006 account - a cloud account
2007 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2008 """
2009 ### First create required number of ports aka connection points
2010 port_list = []
2011 network_list = []
2012 for c_point in vdu_modify.connection_points_add:
2013 if c_point.virtual_link_id in network_list:
2014 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"
2015 else:
2016 network_list.append(c_point.virtual_link_id)
2017 port_id = self._create_connection_point(account, c_point)
2018 port_list.append(port_id)
2019
2020 ### Now add the ports to VM
2021 for port_id in port_list:
2022 with self._use_driver(account) as drv:
2023 drv.nova_server_add_port(vdu_modify.vdu_id, port_id)
2024
2025 ### Delete the requested connection_points
2026 for c_point in vdu_modify.connection_points_remove:
2027 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
2028
2029 if vdu_modify.has_field('image_id'):
2030 with self._use_driver(account) as drv:
2031 drv.nova_server_rebuild(vdu_modify.vdu_id, vdu_modify.image_id)
2032
2033
2034 @rwstatus
2035 def do_delete_vdu(self, account, vdu_id):
2036 """Delete a virtual deployment unit
2037
2038 Arguments:
2039 account - a cloud account
2040 vdu_id - id for the vdu to be deleted
2041
2042 Returns:
2043 None
2044 """
2045 if not vdu_id:
2046 self.log.error("empty vdu_id during the vdu deletion")
2047 return
2048
2049 with self._use_driver(account) as drv:
2050 ### Get list of floating_ips associated with this instance and delete them
2051 floating_ips = [ f for f in drv.nova_floating_ip_list() if f.instance_id == vdu_id ]
2052 for f in floating_ips:
2053 drv.nova_drv.floating_ip_delete(f)
2054
2055 ### Get list of port on VM and delete them.
2056 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2057
2058 for port in port_list:
2059 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
2060 self.do_delete_port(account, port['id'], no_rwstatus=True)
2061
2062 self.do_delete_vm(account, vdu_id, no_rwstatus=True)
2063
2064
2065 @rwstatus(ret_on_failure=[None])
2066 def do_get_vdu(self, account, vdu_id):
2067 """Get information about a virtual deployment unit.
2068
2069 Arguments:
2070 account - a cloud account
2071 vdu_id - id for the vdu
2072
2073 Returns:
2074 Object of type RwcalYang.VDUInfoParams
2075 """
2076 with self._use_driver(account) as drv:
2077
2078 ### Get list of ports excluding the one for management network
2079 port_list = [p for p in drv.neutron_port_list(**{'device_id': vdu_id}) if p['network_id'] != drv.get_mgmt_network_id()]
2080
2081 vm = drv.nova_server_get(vdu_id)
2082
2083 flavor_info = None
2084 if ('flavor' in vm) and ('id' in vm['flavor']):
2085 try:
2086 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2087 except Exception as e:
2088 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2089
2090 openstack_group_list = drv.nova_server_group_list()
2091 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2092 vdu_info = RwcalOpenstackPlugin._fill_vdu_info(vm,
2093 flavor_info,
2094 account.openstack.mgmt_network,
2095 port_list,
2096 server_group)
2097 if vdu_info.state == 'active':
2098 try:
2099 console_info = drv.nova_server_console(vdu_info.vdu_id)
2100 except Exception as e:
2101 pass
2102 else:
2103 vdu_info.console_url = console_info['console']['url']
2104 pass
2105
2106 return vdu_info
2107
2108
2109 @rwstatus(ret_on_failure=[None])
2110 def do_get_vdu_list(self, account):
2111 """Get information about all the virtual deployment units
2112
2113 Arguments:
2114 account - a cloud account
2115
2116 Returns:
2117 A list of objects of type RwcalYang.VDUInfoParams
2118 """
2119 vnf_resources = RwcalYang.VNFResources()
2120 with self._use_driver(account) as drv:
2121 vms = drv.nova_server_list()
2122 for vm in vms:
2123 ### Get list of ports excluding one for management network
2124 port_list = [p for p in drv.neutron_port_list(**{'device_id': vm['id']}) if p['network_id'] != drv.get_mgmt_network_id()]
2125
2126 flavor_info = None
2127
2128 if ('flavor' in vm) and ('id' in vm['flavor']):
2129 try:
2130 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2131 except Exception as e:
2132 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2133
2134 else:
2135 flavor_info = None
2136
2137 openstack_group_list = drv.nova_server_group_list()
2138 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2139
2140 vdu = RwcalOpenstackPlugin._fill_vdu_info(vm,
2141 flavor_info,
2142 account.openstack.mgmt_network,
2143 port_list,
2144 server_group)
2145 if vdu.state == 'active':
2146 try:
2147 console_info = drv.nova_server_console(vdu.vdu_id)
2148 except Exception as e:
2149 pass
2150 else:
2151 vdu.console_url = console_info['console']['url']
2152 pass
2153 vnf_resources.vdu_info_list.append(vdu)
2154 return vnf_resources
2155
2156