2be896a5b9d9f3c3f2ae2ad460d6d941210f9938
[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} --region {region} --user_domain {user_domain} --project_domain {project_domain} --mgmt_network {mgmt_network} --server_id {server_id} --port_metadata "
44
45 rwstatus_exception_map = { IndexError: RwTypes.RwStatus.NOTFOUND,
46 KeyError: RwTypes.RwStatus.NOTFOUND,
47 NotImplementedError: RwTypes.RwStatus.NOT_IMPLEMENTED,}
48
49 rwstatus = rw_status.rwstatus_from_exc_map(rwstatus_exception_map)
50 rwcalstatus = rwcal_status.rwcalstatus_from_exc_map(rwstatus_exception_map)
51
52
53 espec_utils = openstack_drv.OpenstackExtraSpecUtils()
54
55 class OpenstackCALOperationFailure(Exception):
56 pass
57
58 class UninitializedPluginError(Exception):
59 pass
60
61
62 class OpenstackServerGroupError(Exception):
63 pass
64
65
66 class ImageUploadError(Exception):
67 pass
68
69
70 class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud):
71 """This class implements the CAL VALA methods for openstack."""
72
73 instance_num = 1
74
75 def __init__(self):
76 GObject.Object.__init__(self)
77 self._driver_class = openstack_drv.OpenstackDriver
78 self.log = logging.getLogger('rwcal.openstack.%s' % RwcalOpenstackPlugin.instance_num)
79 self.log.setLevel(logging.DEBUG)
80
81 self._rwlog_handler = None
82 RwcalOpenstackPlugin.instance_num += 1
83
84 @contextlib.contextmanager
85 def _use_driver(self, account):
86 if self._rwlog_handler is None:
87 raise UninitializedPluginError("Must call init() in CAL plugin before use.")
88
89 with rwlogger.rwlog_root_handler(self._rwlog_handler):
90 try:
91 drv = self._driver_class(username = account.openstack.key,
92 password = account.openstack.secret,
93 auth_url = account.openstack.auth_url,
94 tenant_name = account.openstack.tenant,
95 mgmt_network = account.openstack.mgmt_network,
96 cert_validate = account.openstack.cert_validate,
97 user_domain_name = account.openstack.user_domain,
98 project_domain_name = account.openstack.project_domain,
99 region = account.openstack.region)
100 except (KeystoneExceptions.Unauthorized, KeystoneExceptions.AuthorizationFailure,
101 NeutronException.NotFound) as e:
102 raise
103 except Exception as e:
104 self.log.error("RwcalOpenstackPlugin: OpenstackDriver init failed. Exception: %s" %(str(e)))
105 raise
106
107 yield drv
108
109
110 @rwstatus
111 def do_init(self, rwlog_ctx):
112 self._rwlog_handler = rwlogger.RwLogger(
113 category="rw-cal-log",
114 subcategory="openstack",
115 log_hdl=rwlog_ctx,
116 )
117 self.log.addHandler(self._rwlog_handler)
118 self.log.propagate = False
119
120 @rwstatus(ret_on_failure=[None])
121 def do_validate_cloud_creds(self, account):
122 """
123 Validates the cloud account credentials for the specified account.
124 Performs an access to the resources using Keystone API. If creds
125 are not valid, returns an error code & reason string
126 Arguments:
127 account - a cloud account to validate
128
129 Returns:
130 Validation Code and Details String
131 """
132 status = RwcalYang.CloudConnectionStatus()
133 try:
134 with self._use_driver(account) as drv:
135 drv.validate_account_creds()
136
137 except KeystoneExceptions.Unauthorized as e:
138 self.log.error("Invalid credentials given for VIM account %s" %account.name)
139 status.status = "failure"
140 status.details = "Invalid Credentials: %s" % str(e)
141
142 except KeystoneExceptions.AuthorizationFailure as e:
143 self.log.error("Bad authentication URL given for VIM account %s. Given auth url: %s" % (
144 account.name, account.openstack.auth_url))
145 status.status = "failure"
146 status.details = "Invalid auth url: %s" % str(e)
147
148 except NeutronException.NotFound as e:
149 self.log.error("Given management network %s could not be found for VIM account %s" % (
150 account.openstack.mgmt_network, account.name))
151 status.status = "failure"
152 status.details = "mgmt network does not exist: %s" % str(e)
153
154 except openstack_drv.ValidationError as e:
155 self.log.error("RwcalOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e))
156 status.status = "failure"
157 status.details = "Invalid Credentials: %s" % str(e)
158
159 except Exception as e:
160 msg = "RwcalOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e))
161 self.log.error(msg)
162 status.status = "failure"
163 status.details = msg
164
165 else:
166 status.status = "success"
167 status.details = "Connection was successful"
168
169 return status
170
171 @rwstatus(ret_on_failure=[""])
172 def do_get_management_network(self, account):
173 """
174 Returns the management network associated with the specified account.
175 Arguments:
176 account - a cloud account
177
178 Returns:
179 The management network
180 """
181 return account.openstack.mgmt_network
182
183 @rwstatus(ret_on_failure=[""])
184 def do_create_tenant(self, account, name):
185 """Create a new tenant.
186
187 Arguments:
188 account - a cloud account
189 name - name of the tenant
190
191 Returns:
192 The tenant id
193 """
194 raise NotImplementedError
195
196 @rwstatus
197 def do_delete_tenant(self, account, tenant_id):
198 """delete a tenant.
199
200 Arguments:
201 account - a cloud account
202 tenant_id - id of the tenant
203 """
204 raise NotImplementedError
205
206 @rwstatus(ret_on_failure=[[]])
207 def do_get_tenant_list(self, account):
208 """List tenants.
209
210 Arguments:
211 account - a cloud account
212
213 Returns:
214 List of tenants
215 """
216 raise NotImplementedError
217
218 @rwstatus(ret_on_failure=[""])
219 def do_create_role(self, account, name):
220 """Create a new user.
221
222 Arguments:
223 account - a cloud account
224 name - name of the user
225
226 Returns:
227 The user id
228 """
229 raise NotImplementedError
230
231 @rwstatus
232 def do_delete_role(self, account, role_id):
233 """Delete a user.
234
235 Arguments:
236 account - a cloud account
237 role_id - id of the user
238 """
239 raise NotImplementedError
240
241 @rwstatus(ret_on_failure=[[]])
242 def do_get_role_list(self, account):
243 """List roles.
244
245 Arguments:
246 account - a cloud account
247
248 Returns:
249 List of roles
250 """
251 raise NotImplementedError
252
253 @rwstatus(ret_on_failure=[""])
254 def do_create_image(self, account, image):
255 """Create an image
256
257 Arguments:
258 account - a cloud account
259 image - a description of the image to create
260
261 Returns:
262 The image id
263 """
264
265 try:
266 # If the use passed in a file descriptor, use that to
267 # upload the image.
268 if image.has_field("fileno"):
269 new_fileno = os.dup(image.fileno)
270 hdl = os.fdopen(new_fileno, 'rb')
271 else:
272 hdl = open(image.location, "rb")
273 except Exception as e:
274 self.log.error("Could not open file for upload. Exception received: %s", str(e))
275 raise
276
277 with hdl as fd:
278 kwargs = {}
279 kwargs['name'] = image.name
280
281 if image.disk_format:
282 kwargs['disk_format'] = image.disk_format
283 if image.container_format:
284 kwargs['container_format'] = image.container_format
285
286 with self._use_driver(account) as drv:
287 # Create Image
288 image_id = drv.glance_image_create(**kwargs)
289 # Upload the Image
290 drv.glance_image_upload(image_id, fd)
291
292 if image.checksum:
293 stored_image = drv.glance_image_get(image_id)
294 if stored_image.checksum != image.checksum:
295 drv.glance_image_delete(image_id=image_id)
296 raise ImageUploadError(
297 "image checksum did not match (actual: %s, expected: %s). Deleting." %
298 (stored_image.checksum, image.checksum)
299 )
300
301 return image_id
302
303 @rwstatus
304 def do_delete_image(self, account, image_id):
305 """Delete a vm image.
306
307 Arguments:
308 account - a cloud account
309 image_id - id of the image to delete
310 """
311 with self._use_driver(account) as drv:
312 drv.glance_image_delete(image_id=image_id)
313
314
315 @staticmethod
316 def _fill_image_info(img_info):
317 """Create a GI object from image info dictionary
318
319 Converts image information dictionary object returned by openstack
320 driver into Protobuf Gi Object
321
322 Arguments:
323 account - a cloud account
324 img_info - image information dictionary object from openstack
325
326 Returns:
327 The ImageInfoItem
328 """
329 img = RwcalYang.ImageInfoItem()
330 img.name = img_info['name']
331 img.id = img_info['id']
332 img.checksum = img_info['checksum']
333 img.disk_format = img_info['disk_format']
334 img.container_format = img_info['container_format']
335 if img_info['status'] == 'active':
336 img.state = 'active'
337 else:
338 img.state = 'inactive'
339 return img
340
341 @rwstatus(ret_on_failure=[[]])
342 def do_get_image_list(self, account):
343 """Return a list of the names of all available images.
344
345 Arguments:
346 account - a cloud account
347
348 Returns:
349 The the list of images in VimResources object
350 """
351 response = RwcalYang.VimResources()
352 with self._use_driver(account) as drv:
353 images = drv.glance_image_list()
354 for img in images:
355 response.imageinfo_list.append(RwcalOpenstackPlugin._fill_image_info(img))
356 return response
357
358 @rwstatus(ret_on_failure=[None])
359 def do_get_image(self, account, image_id):
360 """Return a image information.
361
362 Arguments:
363 account - a cloud account
364 image_id - an id of the image
365
366 Returns:
367 ImageInfoItem object containing image information.
368 """
369 with self._use_driver(account) as drv:
370 image = drv.glance_image_get(image_id)
371 return RwcalOpenstackPlugin._fill_image_info(image)
372
373 # This is being deprecated. Please do not use for new SW development
374 @rwstatus(ret_on_failure=[""])
375 def do_create_vm(self, account, vminfo):
376 """Create a new virtual machine.
377
378 Arguments:
379 account - a cloud account
380 vminfo - information that defines the type of VM to create
381
382 Returns:
383 The image id
384 """
385 kwargs = {}
386 kwargs['name'] = vminfo.vm_name
387 kwargs['flavor_id'] = vminfo.flavor_id
388 if vminfo.has_field('image_id'):
389 kwargs['image_id'] = vminfo.image_id
390
391 with self._use_driver(account) as drv:
392 ### If floating_ip is required and we don't have one, better fail before any further allocation
393 pool_name = None
394 floating_ip = False
395 if vminfo.has_field('allocate_public_address') and vminfo.allocate_public_address:
396 if account.openstack.has_field('floating_ip_pool'):
397 pool_name = account.openstack.floating_ip_pool
398 floating_ip = True
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.supplemental_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.supplemental_boot_data.boot_data_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 # By default port gets created with post_security enaled as True
1503 if 'port_security_enabled' in c_point:
1504 kwargs['port_security_enabled'] = c_point.port_security_enabled
1505
1506 with self._use_driver(account) as drv:
1507 if c_point.has_field('security_group'):
1508 group = drv.neutron_security_group_by_name(c_point.security_group)
1509 if group is not None:
1510 kwargs['security_groups'] = [group['id']]
1511 return drv.neutron_port_create(**kwargs)
1512
1513 def _allocate_floating_ip(self, drv, pool_name):
1514 """
1515 Allocate a floating_ip. If unused floating_ip exists then its reused.
1516 Arguments:
1517 drv: OpenstackDriver instance
1518 pool_name: Floating IP pool name
1519
1520 Returns:
1521 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1522 """
1523
1524 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1525
1526 # if pool_name is not None:
1527 # ### Filter further based on IP address
1528 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1529
1530 # if not available_ip:
1531 # floating_ip = drv.nova_floating_ip_create(pool_name)
1532 # else:
1533 # floating_ip = available_ip[0]
1534
1535 floating_ip = drv.nova_floating_ip_create(pool_name)
1536 return floating_ip
1537
1538 def _match_vm_flavor(self, required, available):
1539 self.log.info("Matching VM Flavor attributes")
1540 if available.vcpu_count != required.vcpu_count:
1541 self.log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1542 required.vcpu_count,
1543 available.vcpu_count)
1544 return False
1545 if available.memory_mb != required.memory_mb:
1546 self.log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1547 required.memory_mb,
1548 available.memory_mb)
1549 return False
1550 if available.storage_gb != required.storage_gb:
1551 self.log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1552 required.storage_gb,
1553 available.storage_gb)
1554 return False
1555 self.log.debug("VM Flavor match found")
1556 return True
1557
1558 def _match_guest_epa(self, required, available):
1559 self.log.info("Matching Guest EPA attributes")
1560 if required.has_field('pcie_device'):
1561 self.log.debug("Matching pcie_device")
1562 if available.has_field('pcie_device') == False:
1563 self.log.debug("Matching pcie_device failed. Not available in flavor")
1564 return False
1565 else:
1566 for dev in required.pcie_device:
1567 if not [ d for d in available.pcie_device
1568 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
1569 self.log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
1570 return False
1571 elif available.has_field('pcie_device'):
1572 self.log.debug("Rejecting available flavor because pcie_device not required but available")
1573 return False
1574
1575
1576 if required.has_field('mempage_size'):
1577 self.log.debug("Matching mempage_size")
1578 if available.has_field('mempage_size') == False:
1579 self.log.debug("Matching mempage_size failed. Not available in flavor")
1580 return False
1581 else:
1582 if required.mempage_size != available.mempage_size:
1583 self.log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
1584 return False
1585 elif available.has_field('mempage_size'):
1586 self.log.debug("Rejecting available flavor because mempage_size not required but available")
1587 return False
1588
1589 if required.has_field('cpu_pinning_policy'):
1590 self.log.debug("Matching cpu_pinning_policy")
1591 if required.cpu_pinning_policy != 'ANY':
1592 if available.has_field('cpu_pinning_policy') == False:
1593 self.log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1594 return False
1595 else:
1596 if required.cpu_pinning_policy != available.cpu_pinning_policy:
1597 self.log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
1598 return False
1599 elif available.has_field('cpu_pinning_policy'):
1600 self.log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1601 return False
1602
1603 if required.has_field('cpu_thread_pinning_policy'):
1604 self.log.debug("Matching cpu_thread_pinning_policy")
1605 if available.has_field('cpu_thread_pinning_policy') == False:
1606 self.log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1607 return False
1608 else:
1609 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
1610 self.log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
1611 return False
1612 elif available.has_field('cpu_thread_pinning_policy'):
1613 self.log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1614 return False
1615
1616 if required.has_field('trusted_execution'):
1617 self.log.debug("Matching trusted_execution")
1618 if required.trusted_execution == True:
1619 if available.has_field('trusted_execution') == False:
1620 self.log.debug("Matching trusted_execution failed. Not available in flavor")
1621 return False
1622 else:
1623 if required.trusted_execution != available.trusted_execution:
1624 self.log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
1625 return False
1626 elif available.has_field('trusted_execution'):
1627 self.log.debug("Rejecting available flavor because trusted_execution not required but available")
1628 return False
1629
1630 if required.has_field('numa_node_policy'):
1631 self.log.debug("Matching numa_node_policy")
1632 if available.has_field('numa_node_policy') == False:
1633 self.log.debug("Matching numa_node_policy failed. Not available in flavor")
1634 return False
1635 else:
1636 if required.numa_node_policy.has_field('node_cnt'):
1637 self.log.debug("Matching numa_node_policy node_cnt")
1638 if available.numa_node_policy.has_field('node_cnt') == False:
1639 self.log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1640 return False
1641 else:
1642 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
1643 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)
1644 return False
1645 elif available.numa_node_policy.has_field('node_cnt'):
1646 self.log.debug("Rejecting available flavor because numa node count not required but available")
1647 return False
1648
1649 if required.numa_node_policy.has_field('mem_policy'):
1650 self.log.debug("Matching numa_node_policy mem_policy")
1651 if available.numa_node_policy.has_field('mem_policy') == False:
1652 self.log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1653 return False
1654 else:
1655 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
1656 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)
1657 return False
1658 elif available.numa_node_policy.has_field('mem_policy'):
1659 self.log.debug("Rejecting available flavor because num node mem_policy not required but available")
1660 return False
1661
1662 if required.numa_node_policy.has_field('node'):
1663 self.log.debug("Matching numa_node_policy nodes configuration")
1664 if available.numa_node_policy.has_field('node') == False:
1665 self.log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1666 return False
1667 for required_node in required.numa_node_policy.node:
1668 self.log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1669 numa_match = False
1670 for available_node in available.numa_node_policy.node:
1671 if required_node.id != available_node.id:
1672 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1673 continue
1674 if required_node.vcpu != available_node.vcpu:
1675 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1676 continue
1677 if required_node.memory_mb != available_node.memory_mb:
1678 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1679 continue
1680 numa_match = True
1681 if numa_match == False:
1682 return False
1683 elif available.numa_node_policy.has_field('node'):
1684 self.log.debug("Rejecting available flavor because numa nodes not required but available")
1685 return False
1686 elif available.has_field('numa_node_policy'):
1687 self.log.debug("Rejecting available flavor because numa_node_policy not required but available")
1688 return False
1689 self.log.info("Successful match for Guest EPA attributes")
1690 return True
1691
1692 def _match_vswitch_epa(self, required, available):
1693 self.log.debug("VSwitch EPA match found")
1694 return True
1695
1696 def _match_hypervisor_epa(self, required, available):
1697 self.log.debug("Hypervisor EPA match found")
1698 return True
1699
1700 def _match_host_epa(self, required, available):
1701 self.log.info("Matching Host EPA attributes")
1702 if required.has_field('cpu_model'):
1703 self.log.debug("Matching CPU model")
1704 if available.has_field('cpu_model') == False:
1705 self.log.debug("Matching CPU model failed. Not available in flavor")
1706 return False
1707 else:
1708 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1709 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1710 self.log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1711 return False
1712 elif available.has_field('cpu_model'):
1713 self.log.debug("Rejecting available flavor because cpu_model not required but available")
1714 return False
1715
1716 if required.has_field('cpu_arch'):
1717 self.log.debug("Matching CPU architecture")
1718 if available.has_field('cpu_arch') == False:
1719 self.log.debug("Matching CPU architecture failed. Not available in flavor")
1720 return False
1721 else:
1722 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1723 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1724 self.log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1725 return False
1726 elif available.has_field('cpu_arch'):
1727 self.log.debug("Rejecting available flavor because cpu_arch not required but available")
1728 return False
1729
1730 if required.has_field('cpu_vendor'):
1731 self.log.debug("Matching CPU vendor")
1732 if available.has_field('cpu_vendor') == False:
1733 self.log.debug("Matching CPU vendor failed. Not available in flavor")
1734 return False
1735 else:
1736 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1737 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1738 self.log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1739 return False
1740 elif available.has_field('cpu_vendor'):
1741 self.log.debug("Rejecting available flavor because cpu_vendor not required but available")
1742 return False
1743
1744 if required.has_field('cpu_socket_count'):
1745 self.log.debug("Matching CPU socket count")
1746 if available.has_field('cpu_socket_count') == False:
1747 self.log.debug("Matching CPU socket count failed. Not available in flavor")
1748 return False
1749 else:
1750 if required.cpu_socket_count != available.cpu_socket_count:
1751 self.log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1752 return False
1753 elif available.has_field('cpu_socket_count'):
1754 self.log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1755 return False
1756
1757 if required.has_field('cpu_core_count'):
1758 self.log.debug("Matching CPU core count")
1759 if available.has_field('cpu_core_count') == False:
1760 self.log.debug("Matching CPU core count failed. Not available in flavor")
1761 return False
1762 else:
1763 if required.cpu_core_count != available.cpu_core_count:
1764 self.log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1765 return False
1766 elif available.has_field('cpu_core_count'):
1767 self.log.debug("Rejecting available flavor because cpu_core_count not required but available")
1768 return False
1769
1770 if required.has_field('cpu_core_thread_count'):
1771 self.log.debug("Matching CPU core thread count")
1772 if available.has_field('cpu_core_thread_count') == False:
1773 self.log.debug("Matching CPU core thread count failed. Not available in flavor")
1774 return False
1775 else:
1776 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1777 self.log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1778 return False
1779 elif available.has_field('cpu_core_thread_count'):
1780 self.log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1781 return False
1782
1783 if required.has_field('cpu_feature'):
1784 self.log.debug("Matching CPU feature list")
1785 if available.has_field('cpu_feature') == False:
1786 self.log.debug("Matching CPU feature list failed. Not available in flavor")
1787 return False
1788 else:
1789 for feature in required.cpu_feature:
1790 if feature not in available.cpu_feature:
1791 self.log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1792 return False
1793 elif available.has_field('cpu_feature'):
1794 self.log.debug("Rejecting available flavor because cpu_feature not required but available")
1795 return False
1796 self.log.info("Successful match for Host EPA attributes")
1797 return True
1798
1799
1800 def _match_placement_group_inputs(self, required, available):
1801 self.log.info("Matching Host aggregate attributes")
1802
1803 if not required and not available:
1804 # Host aggregate not required and not available => success
1805 self.log.info("Successful match for Host Aggregate attributes")
1806 return True
1807 if required and available:
1808 # Host aggregate requested and available => Do a match and decide
1809 xx = [ x.as_dict() for x in required ]
1810 yy = [ y.as_dict() for y in available ]
1811 for i in xx:
1812 if i not in yy:
1813 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1814 return False
1815 self.log.info("Successful match for Host Aggregate attributes")
1816 return True
1817 else:
1818 # Either of following conditions => Failure
1819 # - Host aggregate required but not available
1820 # - Host aggregate not required but available
1821 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1822 return False
1823
1824 def match_epa_params(self, resource_info, request_params):
1825 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1826 getattr(resource_info, 'vm_flavor'))
1827 if result == False:
1828 self.log.debug("VM Flavor mismatched")
1829 return False
1830
1831 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1832 getattr(resource_info, 'guest_epa'))
1833 if result == False:
1834 self.log.debug("Guest EPA mismatched")
1835 return False
1836
1837 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1838 getattr(resource_info, 'vswitch_epa'))
1839 if result == False:
1840 self.log.debug("Vswitch EPA mismatched")
1841 return False
1842
1843 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1844 getattr(resource_info, 'hypervisor_epa'))
1845 if result == False:
1846 self.log.debug("Hypervisor EPA mismatched")
1847 return False
1848
1849 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1850 getattr(resource_info, 'host_epa'))
1851 if result == False:
1852 self.log.debug("Host EPA mismatched")
1853 return False
1854
1855 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1856 getattr(resource_info, 'host_aggregate'))
1857
1858 if result == False:
1859 self.log.debug("Host Aggregate mismatched")
1860 return False
1861
1862 return True
1863
1864 def _select_resource_flavor(self, account, vdu_init):
1865 """
1866 Select a existing flavor if it matches the request or create new flavor
1867 """
1868 flavor = RwcalYang.FlavorInfoItem()
1869 flavor.name = str(uuid.uuid4())
1870 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1871 epa_dict = {k: v for k, v in vdu_init.as_dict().items() if k in epa_types}
1872 flavor.from_dict(epa_dict)
1873
1874 rc, response = self.do_get_flavor_list(account)
1875 if rc != RwTypes.RwStatus.SUCCESS:
1876 self.log.error("Get-flavor-info-list operation failed for cloud account: %s",
1877 account.name)
1878 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account.name))
1879
1880 flavor_id = None
1881 flavor_list = response.flavorinfo_list
1882 self.log.debug("Received %d flavor information from RW.CAL", len(flavor_list))
1883 for flv in flavor_list:
1884 self.log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1885 vdu_init.name, flv)
1886 if self.match_epa_params(flv, vdu_init):
1887 self.log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1888 vdu_init.name, flv.name, flv.id)
1889 return flv.id
1890
1891 if account.openstack.dynamic_flavor_support is False:
1892 self.log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init.name)
1893 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1894 else:
1895 rc,flavor_id = self.do_create_flavor(account,flavor)
1896 if rc != RwTypes.RwStatus.SUCCESS:
1897 self.log.error("Create-flavor operation failed for cloud account: %s",
1898 account.name)
1899 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account.name))
1900 return flavor_id
1901
1902 def _create_vm(self, account, vduinfo, pci_assignement=None, server_group=None, port_list=None, network_list=None, imageinfo_list=None):
1903 """Create a new virtual machine.
1904
1905 Arguments:
1906 account - a cloud account
1907 vminfo - information that defines the type of VM to create
1908
1909 Returns:
1910 The image id
1911 """
1912 kwargs = {}
1913 kwargs['name'] = vduinfo.name
1914 kwargs['flavor_id'] = vduinfo.flavor_id
1915 if vduinfo.has_field('image_id'):
1916 kwargs['image_id'] = vduinfo.image_id
1917 else:
1918 kwargs['image_id'] = ""
1919
1920 with self._use_driver(account) as drv:
1921 ### If floating_ip is required and we don't have one, better fail before any further allocation
1922 floating_ip = False
1923 pool_name = None
1924 if vduinfo.has_field('allocate_public_address') and vduinfo.allocate_public_address:
1925 if account.openstack.has_field('floating_ip_pool'):
1926 pool_name = account.openstack.floating_ip_pool
1927 floating_ip = True
1928
1929 if vduinfo.has_field('vdu_init') and vduinfo.vdu_init.has_field('userdata'):
1930 kwargs['userdata'] = vduinfo.vdu_init.userdata
1931 else:
1932 kwargs['userdata'] = ''
1933
1934 if account.openstack.security_groups:
1935 kwargs['security_groups'] = account.openstack.security_groups
1936
1937 kwargs['port_list'] = port_list
1938 kwargs['network_list'] = network_list
1939
1940 metadata = {}
1941 files = {}
1942 config_drive = False
1943 # Add all metadata related fields
1944 if vduinfo.has_field('node_id'):
1945 metadata['node_id'] = vduinfo.node_id
1946 if pci_assignement is not None:
1947 metadata['pci_assignement'] = pci_assignement
1948 if vduinfo.has_field('supplemental_boot_data'):
1949 if vduinfo.supplemental_boot_data.has_field('custom_meta_data'):
1950 for custom_meta_item in vduinfo.supplemental_boot_data.custom_meta_data:
1951 if custom_meta_item.data_type == "STRING":
1952 metadata[custom_meta_item.name] = custom_meta_item.value
1953 elif custom_meta_item.data_type == "JSON":
1954 metadata[custom_meta_item.name] = tornado.escape.json_decode(custom_meta_item.value)
1955 else:
1956 raise OpenstackCALOperationFailure("Create-vdu operation failed. Unsupported data-type {} for custom-meta-data name {} ".format(custom_meta_item.data_type, custom_meta_item.name))
1957 if vduinfo.supplemental_boot_data.has_field('config_file'):
1958 for custom_config_file in vduinfo.supplemental_boot_data.config_file:
1959 files[custom_config_file.dest] = custom_config_file.source
1960
1961 if vduinfo.supplemental_boot_data.has_field('boot_data_drive'):
1962 if vduinfo.supplemental_boot_data.boot_data_drive is True:
1963 config_drive = True
1964
1965 kwargs['metadata'] = metadata
1966 kwargs['files'] = files
1967 kwargs['config_drive'] = config_drive
1968
1969 if vduinfo.has_field('availability_zone') and vduinfo.availability_zone.has_field('name'):
1970 kwargs['availability_zone'] = vduinfo.availability_zone
1971 else:
1972 kwargs['availability_zone'] = None
1973
1974 if server_group is not None:
1975 kwargs['scheduler_hints'] = {'group': server_group}
1976 else:
1977 kwargs['scheduler_hints'] = None
1978
1979 kwargs['block_device_mapping_v2'] = None
1980 vol_metadata = False
1981 if vduinfo.has_field('volumes') :
1982 kwargs['block_device_mapping_v2'] = []
1983 with self._use_driver(account) as drv:
1984 # Only support image->volume
1985 for volume in vduinfo.volumes:
1986 block_map = dict()
1987 block_map['boot_index'] = volume.boot_priority
1988 if "image" in volume:
1989 # Support image->volume
1990 # Match retrived image info with volume based image name and checksum
1991 if volume.image is not None:
1992 matching_images = [img for img in imageinfo_list if img['name'] == volume.image]
1993 if volume.image_checksum is not None:
1994 matching_images = [img for img in matching_images if img['checksum'] == volume.image_checksum]
1995 img_id = matching_images[0]['id']
1996 if img_id is None:
1997 raise OpenstackCALOperationFailure("Create-vdu operation failed. Volume image not found for name {} checksum {}".format(volume.name, volume.checksum))
1998 block_map['uuid'] = img_id
1999 block_map['source_type'] = "image"
2000 else:
2001 block_map['source_type'] = "blank"
2002
2003 block_map['device_name'] = volume.name
2004 block_map['destination_type'] = "volume"
2005 block_map['volume_size'] = volume.size
2006 block_map['delete_on_termination'] = True
2007 if volume.has_field('device_type') and volume.device_type == 'cdrom':
2008 block_map['device_type'] = 'cdrom'
2009 if volume.has_field('device_bus') and volume.device_bus == 'ide':
2010 block_map['disk_bus'] = 'ide'
2011 kwargs['block_device_mapping_v2'].append(block_map)
2012
2013
2014 with self._use_driver(account) as drv:
2015 vm_id = drv.nova_server_create(**kwargs)
2016 if floating_ip:
2017 self.prepare_vdu_on_boot(account, vm_id, floating_ip, pool_name, vduinfo.volumes)
2018
2019 return vm_id
2020
2021 def get_openstack_image_info(self, account, image_name, image_checksum=None):
2022 self.log.debug("Looking up image id for image name %s and checksum %s on cloud account: %s",
2023 image_name, image_checksum, account.name
2024 )
2025
2026 image_list = []
2027 with self._use_driver(account) as drv:
2028 image_list = drv.glance_image_list()
2029 matching_images = [img for img in image_list if img['name'] == image_name]
2030
2031 # If the image checksum was filled in then further filter the images by the checksum
2032 if image_checksum is not None:
2033 matching_images = [img for img in matching_images if img['checksum'] == image_checksum]
2034 else:
2035 self.log.warning("Image checksum not provided. Lookup using image name (%s) only.",
2036 image_name)
2037
2038 if len(matching_images) == 0:
2039 raise ResMgrCALOperationFailure("Could not find image name {} (using checksum: {}) for cloud account: {}".format(
2040 image_name, image_checksum, account.name
2041 ))
2042
2043 elif len(matching_images) > 1:
2044 unique_checksums = {i.checksum for i in matching_images}
2045 if len(unique_checksums) > 1:
2046 msg = ("Too many images with different checksums matched "
2047 "image name of %s for cloud account: %s" % (image_name, account.name))
2048 raise ResMgrCALOperationFailure(msg)
2049
2050 return matching_images[0]
2051
2052 @rwcalstatus(ret_on_failure=[""])
2053 def do_create_vdu(self, account, vdu_init):
2054 """Create a new virtual deployment unit
2055
2056 Arguments:
2057 account - a cloud account
2058 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
2059
2060 Returns:
2061 The vdu_id
2062 """
2063 ### First create required number of ports aka connection points
2064 # Add the mgmt_ntwk by default.
2065 mgmt_network_id = None
2066 with self._use_driver(account) as drv:
2067 mgmt_network_id = drv._mgmt_network_id
2068
2069 port_list = []
2070 network_list = []
2071 imageinfo_list = []
2072 is_explicit_mgmt_defined = False
2073 for c_point in vdu_init.connection_points:
2074 # if the user has specified explicit mgmt_network connection point
2075 # then remove the mgmt_network from the VM list
2076 if c_point.virtual_link_id == mgmt_network_id:
2077 is_explicit_mgmt_defined = True
2078 if c_point.virtual_link_id in network_list:
2079 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"
2080 else:
2081 network_list.append(c_point.virtual_link_id)
2082 port_id = self._create_connection_point(account, c_point)
2083 port_list.append(port_id)
2084
2085 if not vdu_init.has_field('flavor_id'):
2086 vdu_init.flavor_id = self._select_resource_flavor(account,vdu_init)
2087
2088 ### Obtain all images for volumes and perform validations
2089 if vdu_init.has_field('volumes'):
2090 for volume in vdu_init.volumes:
2091 if "image" in volume:
2092 image_checksum = volume.image_checksum if volume.has_field("image_checksum") else None
2093 image_info = self.get_openstack_image_info(account, volume.image, image_checksum)
2094 imageinfo_list.append(image_info)
2095 elif vdu_init.has_field('image_id'):
2096 with self._use_driver(account) as drv:
2097 image_info = drv.glance_image_get(vdu_init.image_id)
2098 imageinfo_list.append(image_info)
2099
2100 if not imageinfo_list:
2101 err_str = ("VDU has no image information")
2102 self.log.error(err_str)
2103 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2104
2105 ### Check VDU Virtual Interface type and make sure VM with property exists
2106 if vdu_init.connection_points:
2107 ### All virtual interfaces need to be of the same type for Openstack Accounts
2108 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)):
2109 ### We have a mix of E1000 & VIRTIO/SR_IPOV virtual interface types in the VDU, abort instantiation.
2110 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO/SR_IOV & E1000."
2111
2112 ## 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,
2113 ### we shall assume that all images need to have similar properties
2114 for img_info in imageinfo_list:
2115
2116 virt_intf_type = vdu_init.connection_points[0].type_yang
2117 if virt_intf_type == 'E1000':
2118 if 'hw_vif_model' in img_info and img_info.hw_vif_model == 'e1000':
2119 self.log.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
2120 else:
2121 err_str = ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info.name)
2122 self.log.error(err_str)
2123 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2124 elif virt_intf_type == 'VIRTIO' or virt_intf_type == 'SR_IOV':
2125 if 'hw_vif_model' in img_info:
2126 err_str = ("VDU has Virtual Interface %s, but image '%s' has hw_vif_model mismatch" % virt_intf_type,img_info.name)
2127 self.log.error(err_str)
2128 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2129 else:
2130 self.log.debug("VDU has Virtual Interface %s, found matching image" % virt_intf_type)
2131 else:
2132 err_str = ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type)
2133 self.log.error(err_str)
2134 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2135
2136 with self._use_driver(account) as drv:
2137 ### Now Create VM
2138 vm_network_list = []
2139 if not is_explicit_mgmt_defined:
2140 vm_network_list.append(drv._mgmt_network_id)
2141
2142 if vdu_init.has_field('volumes'):
2143 # Only combination supported: Image->Volume
2144 for volume in vdu_init.volumes:
2145 if "volume" in volume:
2146 err_str = ("VDU Volume source not supported yet")
2147 self.log.error(err_str)
2148 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2149 if not volume.has_field('device_type'):
2150 err_str = ("VDU Volume destination type not defined")
2151 self.log.error(err_str)
2152 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2153 if volume.device_type not in ['disk', 'cdrom'] :
2154 err_str = ("VDU Volume destination type '%s' not supported" % volume.device_type)
2155 self.log.error(err_str)
2156 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
2157
2158
2159 server_group = None
2160 if vdu_init.has_field('server_group'):
2161 ### Get list of server group in openstack for name->id mapping
2162 openstack_group_list = drv.nova_server_group_list()
2163 group_id = [ i['id'] for i in openstack_group_list if i['name'] == vdu_init.server_group.name]
2164 if len(group_id) != 1:
2165 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]))
2166 server_group = group_id[0]
2167
2168 pci_assignement = self.prepare_vpci_metadata(drv, vdu_init)
2169 if pci_assignement != '':
2170 vm.user_tags.pci_assignement = pci_assignement
2171
2172 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)
2173 return vm_id
2174
2175 def prepare_vpci_metadata(self, drv, vdu_init):
2176 pci_assignement = ''
2177 ### TEF specific metadata creation for
2178 virtio_vpci = []
2179 sriov_vpci = []
2180 virtio_meta = ''
2181 sriov_meta = ''
2182 ### For MGMT interface
2183 if vdu_init.has_field('mgmt_vpci'):
2184 xx = 'u\''+ drv._mgmt_network_id + '\' :[[u\'' + vdu_init.mgmt_vpci + '\', ' + '\'\']]'
2185 virtio_vpci.append(xx)
2186
2187 for c_point in vdu_init.connection_points:
2188 if c_point.has_field('vpci'):
2189 if c_point.has_field('vpci') and c_point.type_yang == 'VIRTIO':
2190 xx = 'u\''+c_point.virtual_link_id + '\' :[[u\'' + c_point.vpci + '\', ' + '\'\']]'
2191 virtio_vpci.append(xx)
2192 elif c_point.has_field('vpci') and c_point.type_yang == 'SR_IOV':
2193 xx = '[u\'' + c_point.vpci + '\', ' + '\'\']'
2194 sriov_vpci.append(xx)
2195
2196 if virtio_vpci:
2197 virtio_meta += ','.join(virtio_vpci)
2198
2199 if sriov_vpci:
2200 sriov_meta = 'u\'VF\': ['
2201 sriov_meta += ','.join(sriov_vpci)
2202 sriov_meta += ']'
2203
2204 if virtio_meta != '':
2205 pci_assignement += virtio_meta
2206 pci_assignement += ','
2207
2208 if sriov_meta != '':
2209 pci_assignement += sriov_meta
2210
2211 if pci_assignement != '':
2212 pci_assignement = '{' + pci_assignement + '}'
2213
2214 return pci_assignement
2215
2216
2217
2218 def prepare_vdu_on_boot(self, account, server_id, floating_ip, pool_name, volumes=None):
2219 cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url,
2220 username = account.openstack.key,
2221 password = account.openstack.secret,
2222 tenant_name = account.openstack.tenant,
2223 region = account.openstack.region,
2224 user_domain = account.openstack.user_domain,
2225 project_domain = account.openstack.project_domain,
2226 mgmt_network = account.openstack.mgmt_network,
2227 server_id = server_id)
2228 if floating_ip:
2229 cmd += " --floating_ip"
2230 if pool_name:
2231 cmd += (" --pool_name " + pool_name)
2232
2233 vol_metadata = False
2234 if volumes is not None:
2235 for volume in volumes:
2236 if volume.has_field('custom_meta_data'):
2237 vol_metadata = True
2238 break
2239
2240 if vol_metadata is True:
2241 tmp_file = None
2242 with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_file:
2243 vol_list = list()
2244 for volume in volumes:
2245 vol_dict = volume.as_dict()
2246 vol_list.append(vol_dict)
2247
2248 yaml.dump(vol_list, tmp_file)
2249 cmd += (" --vol_metadata {}").format(tmp_file.name)
2250
2251 exec_path = 'python3 ' + os.path.dirname(openstack_drv.__file__)
2252 exec_cmd = exec_path+'/'+cmd
2253 self.log.info("Running command: %s" %(exec_cmd))
2254 subprocess.call(exec_cmd, shell=True)
2255
2256 @rwstatus
2257 def do_modify_vdu(self, account, vdu_modify):
2258 """Modify Properties of existing virtual deployment unit
2259
2260 Arguments:
2261 account - a cloud account
2262 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2263 """
2264 ### First create required number of ports aka connection points
2265 port_list = []
2266 network_list = []
2267 for c_point in vdu_modify.connection_points_add:
2268 if c_point.virtual_link_id in network_list:
2269 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"
2270 else:
2271 network_list.append(c_point.virtual_link_id)
2272 port_id = self._create_connection_point(account, c_point)
2273 port_list.append(port_id)
2274
2275 ### Now add the ports to VM
2276 for port_id in port_list:
2277 with self._use_driver(account) as drv:
2278 drv.nova_server_add_port(vdu_modify.vdu_id, port_id)
2279
2280 ### Delete the requested connection_points
2281 for c_point in vdu_modify.connection_points_remove:
2282 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
2283
2284 if vdu_modify.has_field('image_id'):
2285 with self._use_driver(account) as drv:
2286 drv.nova_server_rebuild(vdu_modify.vdu_id, vdu_modify.image_id)
2287
2288
2289 @rwstatus
2290 def do_delete_vdu(self, account, vdu_id):
2291 """Delete a virtual deployment unit
2292
2293 Arguments:
2294 account - a cloud account
2295 vdu_id - id for the vdu to be deleted
2296
2297 Returns:
2298 None
2299 """
2300 if not vdu_id:
2301 self.log.error("empty vdu_id during the vdu deletion")
2302 return
2303
2304 with self._use_driver(account) as drv:
2305 ### Get list of floating_ips associated with this instance and delete them
2306 floating_ips = [ f for f in drv.nova_floating_ip_list() if f.instance_id == vdu_id ]
2307 for f in floating_ips:
2308 drv.nova_drv.floating_ip_delete(f)
2309
2310 ### Get list of port on VM and delete them.
2311 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2312
2313 for port in port_list:
2314 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
2315 self.do_delete_port(account, port['id'], no_rwstatus=True)
2316
2317 self.do_delete_vm(account, vdu_id, no_rwstatus=True)
2318
2319
2320 @rwstatus(ret_on_failure=[None])
2321 def do_get_vdu(self, account, vdu_id):
2322 """Get information about a virtual deployment unit.
2323
2324 Arguments:
2325 account - a cloud account
2326 vdu_id - id for the vdu
2327
2328 Returns:
2329 Object of type RwcalYang.VDUInfoParams
2330 """
2331 with self._use_driver(account) as drv:
2332 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2333
2334 vm = drv.nova_server_get(vdu_id)
2335
2336 flavor_info = None
2337 if ('flavor' in vm) and ('id' in vm['flavor']):
2338 try:
2339 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2340 except Exception as e:
2341 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2342
2343 openstack_group_list = drv.nova_server_group_list()
2344 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2345 openstack_srv_volume_list = drv.nova_volume_list(vm['id'])
2346 vdu_info = RwcalOpenstackPlugin._fill_vdu_info(drv, vm,
2347 flavor_info,
2348 account.openstack.mgmt_network,
2349 port_list,
2350 server_group,
2351 volume_list = openstack_srv_volume_list)
2352 if vdu_info.state == 'active':
2353 try:
2354 console_info = drv.nova_server_console(vdu_info.vdu_id)
2355 except Exception as e:
2356 pass
2357 else:
2358 vdu_info.console_url = console_info['console']['url']
2359 pass
2360
2361 return vdu_info
2362
2363
2364 @rwstatus(ret_on_failure=[None])
2365 def do_get_vdu_list(self, account):
2366 """Get information about all the virtual deployment units
2367
2368 Arguments:
2369 account - a cloud account
2370
2371 Returns:
2372 A list of objects of type RwcalYang.VDUInfoParams
2373 """
2374 vnf_resources = RwcalYang.VNFResources()
2375 with self._use_driver(account) as drv:
2376 vms = drv.nova_server_list()
2377 for vm in vms:
2378 port_list = drv.neutron_port_list(**{'device_id': vm['id']})
2379
2380 flavor_info = None
2381
2382 if ('flavor' in vm) and ('id' in vm['flavor']):
2383 try:
2384 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2385 except Exception as e:
2386 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2387
2388 else:
2389 flavor_info = None
2390
2391 openstack_group_list = drv.nova_server_group_list()
2392 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2393
2394 openstack_srv_volume_list = drv.nova_volume_list(vm['id'])
2395 vdu = RwcalOpenstackPlugin._fill_vdu_info(drv, vm,
2396 flavor_info,
2397 account.openstack.mgmt_network,
2398 port_list,
2399 server_group,
2400 volume_list = openstack_srv_volume_list)
2401 if vdu.state == 'active':
2402 try:
2403 console_info = drv.nova_server_console(vdu.vdu_id)
2404 except Exception as e:
2405 pass
2406 else:
2407 vdu.console_url = console_info['console']['url']
2408 pass
2409 vnf_resources.vdu_info_list.append(vdu)
2410 return vnf_resources
2411
2412