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