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