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