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