RIFT-14308 Meaningful logs while initalizing Openstack driver
[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 kwargs['image_id'] = vminfo.image_id
380
381 with self._use_driver(account) as drv:
382 ### If floating_ip is required and we don't have one, better fail before any further allocation
383 if vminfo.has_field('allocate_public_address') and vminfo.allocate_public_address:
384 if account.openstack.has_field('floating_ip_pool'):
385 pool_name = account.openstack.floating_ip_pool
386 else:
387 pool_name = None
388 floating_ip = self._allocate_floating_ip(drv, pool_name)
389 else:
390 floating_ip = None
391
392 if vminfo.has_field('cloud_init') and vminfo.cloud_init.has_field('userdata'):
393 kwargs['userdata'] = vminfo.cloud_init.userdata
394 else:
395 kwargs['userdata'] = ''
396
397 if account.openstack.security_groups:
398 kwargs['security_groups'] = account.openstack.security_groups
399
400 port_list = []
401 for port in vminfo.port_list:
402 port_list.append(port.port_id)
403
404 if port_list:
405 kwargs['port_list'] = port_list
406
407 network_list = []
408 for network in vminfo.network_list:
409 network_list.append(network.network_id)
410
411 if network_list:
412 kwargs['network_list'] = network_list
413
414 metadata = {}
415 for field in vminfo.user_tags.fields:
416 if vminfo.user_tags.has_field(field):
417 metadata[field] = getattr(vminfo.user_tags, field)
418 kwargs['metadata'] = metadata
419
420 if vminfo.has_field('availability_zone'):
421 kwargs['availability_zone'] = vminfo.availability_zone
422 else:
423 kwargs['availability_zone'] = None
424
425 if vminfo.has_field('server_group'):
426 kwargs['scheduler_hints'] = {'group': vminfo.server_group }
427 else:
428 kwargs['scheduler_hints'] = None
429
430 with self._use_driver(account) as drv:
431 vm_id = drv.nova_server_create(**kwargs)
432 if floating_ip:
433 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
434
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 'mac_address' in port_info :
1183 c_point.mac_addr = port_info['mac_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):
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 return vdu
1290
1291 @rwcalstatus(ret_on_failure=[""])
1292 def do_create_virtual_link(self, account, link_params):
1293 """Create a new virtual link
1294
1295 Arguments:
1296 account - a cloud account
1297 link_params - information that defines the type of VDU to create
1298
1299 Returns:
1300 The vdu_id
1301 """
1302 kwargs = {}
1303 kwargs['name'] = link_params.name
1304 kwargs['admin_state_up'] = True
1305 kwargs['external_router'] = False
1306 kwargs['shared'] = False
1307
1308 if link_params.has_field('provider_network'):
1309 if link_params.provider_network.has_field('physical_network'):
1310 kwargs['physical_network'] = link_params.provider_network.physical_network
1311 if link_params.provider_network.has_field('overlay_type'):
1312 kwargs['network_type'] = link_params.provider_network.overlay_type.lower()
1313 if link_params.provider_network.has_field('segmentation_id'):
1314 kwargs['segmentation_id'] = link_params.provider_network.segmentation_id
1315
1316
1317 with self._use_driver(account) as drv:
1318 try:
1319 network_id = drv.neutron_network_create(**kwargs)
1320 except Exception as e:
1321 self.log.error("Encountered exceptions during network creation. Exception: %s", str(e))
1322 raise
1323
1324 kwargs = {'network_id' : network_id,
1325 'dhcp_params': {'enable_dhcp': True},
1326 'gateway_ip' : None,}
1327
1328 if link_params.ip_profile_params.has_field('ip_version'):
1329 kwargs['ip_version'] = 6 if link_params.ip_profile_params.ip_version == 'ipv6' else 4
1330 else:
1331 kwargs['ip_version'] = 4
1332
1333 if link_params.ip_profile_params.has_field('subnet_address'):
1334 kwargs['cidr'] = link_params.ip_profile_params.subnet_address
1335 elif link_params.ip_profile_params.has_field('subnet_prefix_pool'):
1336 subnet_pool = drv.netruon_subnetpool_by_name(link_params.ip_profile_params.subnet_prefix_pool)
1337 if subnet_pool is None:
1338 self.log.error("Could not find subnet pool with name :%s to be used for network: %s",
1339 link_params.ip_profile_params.subnet_prefix_pool,
1340 link_params.name)
1341 raise NeutronException.NotFound("SubnetPool with name %s not found"%(link_params.ip_profile_params.subnet_prefix_pool))
1342
1343 kwargs['subnetpool_id'] = subnet_pool['id']
1344 elif link_params.has_field('subnet'):
1345 kwargs['cidr'] = link_params.subnet
1346 else:
1347 assert 0, "No IP Prefix or Pool name specified"
1348
1349 if link_params.ip_profile_params.has_field('dhcp_params'):
1350 if link_params.ip_profile_params.dhcp_params.has_field('enabled'):
1351 kwargs['dhcp_params']['enable_dhcp'] = link_params.ip_profile_params.dhcp_params.enabled
1352 if link_params.ip_profile_params.dhcp_params.has_field('start_address'):
1353 kwargs['dhcp_params']['start_address'] = link_params.ip_profile_params.dhcp_params.start_address
1354 if link_params.ip_profile_params.dhcp_params.has_field('count'):
1355 kwargs['dhcp_params']['count'] = link_params.ip_profile_params.dhcp_params.count
1356
1357 if link_params.ip_profile_params.has_field('dns_server'):
1358 kwargs['dns_server'] = []
1359 for server in link_params.ip_profile_params.dns_server:
1360 kwargs['dns_server'].append(server.address)
1361
1362 if link_params.ip_profile_params.has_field('gateway_address'):
1363 kwargs['gateway_ip'] = link_params.ip_profile_params.gateway_address
1364
1365 drv.neutron_subnet_create(**kwargs)
1366
1367 return network_id
1368
1369
1370 @rwstatus
1371 def do_delete_virtual_link(self, account, link_id):
1372 """Delete a virtual link
1373
1374 Arguments:
1375 account - a cloud account
1376 link_id - id for the virtual-link to be deleted
1377
1378 Returns:
1379 None
1380 """
1381 if not link_id:
1382 self.log.error("Empty link_id during the virtual link deletion")
1383 raise Exception("Empty link_id during the virtual link deletion")
1384
1385 with self._use_driver(account) as drv:
1386 port_list = drv.neutron_port_list(**{'network_id': link_id})
1387
1388 for port in port_list:
1389 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
1390 self.do_delete_port(account, port['id'], no_rwstatus=True)
1391 self.do_delete_network(account, link_id, no_rwstatus=True)
1392
1393 @rwstatus(ret_on_failure=[None])
1394 def do_get_virtual_link(self, account, link_id):
1395 """Get information about virtual link.
1396
1397 Arguments:
1398 account - a cloud account
1399 link_id - id for the virtual-link
1400
1401 Returns:
1402 Object of type RwcalYang.VirtualLinkInfoParams
1403 """
1404 if not link_id:
1405 self.log.error("Empty link_id during the virtual link get request")
1406 raise Exception("Empty link_id during the virtual link get request")
1407
1408 with self._use_driver(account) as drv:
1409 network = drv.neutron_network_get(link_id)
1410 if network:
1411 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1412 if 'subnets' in network:
1413 subnet = drv.neutron_subnet_get(network['subnets'][0])
1414 else:
1415 subnet = None
1416 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1417 else:
1418 virtual_link = None
1419 return virtual_link
1420
1421 @rwstatus(ret_on_failure=[None])
1422 def do_get_virtual_link_list(self, account):
1423 """Get information about all the virtual links
1424
1425 Arguments:
1426 account - a cloud account
1427
1428 Returns:
1429 A list of objects of type RwcalYang.VirtualLinkInfoParams
1430 """
1431 vnf_resources = RwcalYang.VNFResources()
1432 with self._use_driver(account) as drv:
1433 networks = drv.neutron_network_list()
1434 for network in networks:
1435 port_list = drv.neutron_port_list(**{'network_id': network['id']})
1436 if ('subnets' in network) and (network['subnets']):
1437 subnet = drv.neutron_subnet_get(network['subnets'][0])
1438 else:
1439 subnet = None
1440 virtual_link = RwcalOpenstackPlugin._fill_virtual_link_info(network, port_list, subnet)
1441 vnf_resources.virtual_link_info_list.append(virtual_link)
1442 return vnf_resources
1443
1444 def _create_connection_point(self, account, c_point):
1445 """
1446 Create a connection point
1447 Arguments:
1448 account - a cloud account
1449 c_point - connection_points
1450 """
1451 kwargs = {}
1452 kwargs['name'] = c_point.name
1453 kwargs['network_id'] = c_point.virtual_link_id
1454 kwargs['admin_state_up'] = True
1455
1456 if c_point.type_yang == 'VIRTIO' or c_point.type_yang == 'E1000':
1457 kwargs['port_type'] = 'normal'
1458 elif c_point.type_yang == 'SR_IOV':
1459 kwargs['port_type'] = 'direct'
1460 else:
1461 raise NotImplementedError("Port Type: %s not supported" %(c_point.type_yang))
1462
1463 with self._use_driver(account) as drv:
1464 if c_point.has_field('security_group'):
1465 group = drv.neutron_security_group_by_name(c_point.security_group)
1466 if group is not None:
1467 kwargs['security_groups'] = [group['id']]
1468 return drv.neutron_port_create(**kwargs)
1469
1470 def _allocate_floating_ip(self, drv, pool_name):
1471 """
1472 Allocate a floating_ip. If unused floating_ip exists then its reused.
1473 Arguments:
1474 drv: OpenstackDriver instance
1475 pool_name: Floating IP pool name
1476
1477 Returns:
1478 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1479 """
1480
1481 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1482
1483 # if pool_name is not None:
1484 # ### Filter further based on IP address
1485 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1486
1487 # if not available_ip:
1488 # floating_ip = drv.nova_floating_ip_create(pool_name)
1489 # else:
1490 # floating_ip = available_ip[0]
1491
1492 floating_ip = drv.nova_floating_ip_create(pool_name)
1493 return floating_ip
1494
1495 def _match_vm_flavor(self, required, available):
1496 self.log.info("Matching VM Flavor attributes")
1497 if available.vcpu_count != required.vcpu_count:
1498 self.log.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1499 required.vcpu_count,
1500 available.vcpu_count)
1501 return False
1502 if available.memory_mb != required.memory_mb:
1503 self.log.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1504 required.memory_mb,
1505 available.memory_mb)
1506 return False
1507 if available.storage_gb != required.storage_gb:
1508 self.log.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1509 required.storage_gb,
1510 available.storage_gb)
1511 return False
1512 self.log.debug("VM Flavor match found")
1513 return True
1514
1515 def _match_guest_epa(self, required, available):
1516 self.log.info("Matching Guest EPA attributes")
1517 if required.has_field('pcie_device'):
1518 self.log.debug("Matching pcie_device")
1519 if available.has_field('pcie_device') == False:
1520 self.log.debug("Matching pcie_device failed. Not available in flavor")
1521 return False
1522 else:
1523 for dev in required.pcie_device:
1524 if not [ d for d in available.pcie_device
1525 if ((d.device_id == dev.device_id) and (d.count == dev.count)) ]:
1526 self.log.debug("Matching pcie_device failed. Required: %s, Available: %s", required.pcie_device, available.pcie_device)
1527 return False
1528 elif available.has_field('pcie_device'):
1529 self.log.debug("Rejecting available flavor because pcie_device not required but available")
1530 return False
1531
1532
1533 if required.has_field('mempage_size'):
1534 self.log.debug("Matching mempage_size")
1535 if available.has_field('mempage_size') == False:
1536 self.log.debug("Matching mempage_size failed. Not available in flavor")
1537 return False
1538 else:
1539 if required.mempage_size != available.mempage_size:
1540 self.log.debug("Matching mempage_size failed. Required: %s, Available: %s", required.mempage_size, available.mempage_size)
1541 return False
1542 elif available.has_field('mempage_size'):
1543 self.log.debug("Rejecting available flavor because mempage_size not required but available")
1544 return False
1545
1546 if required.has_field('cpu_pinning_policy'):
1547 self.log.debug("Matching cpu_pinning_policy")
1548 if required.cpu_pinning_policy != 'ANY':
1549 if available.has_field('cpu_pinning_policy') == False:
1550 self.log.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1551 return False
1552 else:
1553 if required.cpu_pinning_policy != available.cpu_pinning_policy:
1554 self.log.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required.cpu_pinning_policy, available.cpu_pinning_policy)
1555 return False
1556 elif available.has_field('cpu_pinning_policy'):
1557 self.log.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1558 return False
1559
1560 if required.has_field('cpu_thread_pinning_policy'):
1561 self.log.debug("Matching cpu_thread_pinning_policy")
1562 if available.has_field('cpu_thread_pinning_policy') == False:
1563 self.log.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1564 return False
1565 else:
1566 if required.cpu_thread_pinning_policy != available.cpu_thread_pinning_policy:
1567 self.log.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required.cpu_thread_pinning_policy, available.cpu_thread_pinning_policy)
1568 return False
1569 elif available.has_field('cpu_thread_pinning_policy'):
1570 self.log.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1571 return False
1572
1573 if required.has_field('trusted_execution'):
1574 self.log.debug("Matching trusted_execution")
1575 if required.trusted_execution == True:
1576 if available.has_field('trusted_execution') == False:
1577 self.log.debug("Matching trusted_execution failed. Not available in flavor")
1578 return False
1579 else:
1580 if required.trusted_execution != available.trusted_execution:
1581 self.log.debug("Matching trusted_execution failed. Required: %s, Available: %s", required.trusted_execution, available.trusted_execution)
1582 return False
1583 elif available.has_field('trusted_execution'):
1584 self.log.debug("Rejecting available flavor because trusted_execution not required but available")
1585 return False
1586
1587 if required.has_field('numa_node_policy'):
1588 self.log.debug("Matching numa_node_policy")
1589 if available.has_field('numa_node_policy') == False:
1590 self.log.debug("Matching numa_node_policy failed. Not available in flavor")
1591 return False
1592 else:
1593 if required.numa_node_policy.has_field('node_cnt'):
1594 self.log.debug("Matching numa_node_policy node_cnt")
1595 if available.numa_node_policy.has_field('node_cnt') == False:
1596 self.log.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1597 return False
1598 else:
1599 if required.numa_node_policy.node_cnt != available.numa_node_policy.node_cnt:
1600 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)
1601 return False
1602 elif available.numa_node_policy.has_field('node_cnt'):
1603 self.log.debug("Rejecting available flavor because numa node count not required but available")
1604 return False
1605
1606 if required.numa_node_policy.has_field('mem_policy'):
1607 self.log.debug("Matching numa_node_policy mem_policy")
1608 if available.numa_node_policy.has_field('mem_policy') == False:
1609 self.log.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1610 return False
1611 else:
1612 if required.numa_node_policy.mem_policy != available.numa_node_policy.mem_policy:
1613 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)
1614 return False
1615 elif available.numa_node_policy.has_field('mem_policy'):
1616 self.log.debug("Rejecting available flavor because num node mem_policy not required but available")
1617 return False
1618
1619 if required.numa_node_policy.has_field('node'):
1620 self.log.debug("Matching numa_node_policy nodes configuration")
1621 if available.numa_node_policy.has_field('node') == False:
1622 self.log.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1623 return False
1624 for required_node in required.numa_node_policy.node:
1625 self.log.debug("Matching numa_node_policy nodes configuration for node %s", required_node)
1626 numa_match = False
1627 for available_node in available.numa_node_policy.node:
1628 if required_node.id != available_node.id:
1629 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1630 continue
1631 if required_node.vcpu != available_node.vcpu:
1632 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1633 continue
1634 if required_node.memory_mb != available_node.memory_mb:
1635 self.log.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node, available_node)
1636 continue
1637 numa_match = True
1638 if numa_match == False:
1639 return False
1640 elif available.numa_node_policy.has_field('node'):
1641 self.log.debug("Rejecting available flavor because numa nodes not required but available")
1642 return False
1643 elif available.has_field('numa_node_policy'):
1644 self.log.debug("Rejecting available flavor because numa_node_policy not required but available")
1645 return False
1646 self.log.info("Successful match for Guest EPA attributes")
1647 return True
1648
1649 def _match_vswitch_epa(self, required, available):
1650 self.log.debug("VSwitch EPA match found")
1651 return True
1652
1653 def _match_hypervisor_epa(self, required, available):
1654 self.log.debug("Hypervisor EPA match found")
1655 return True
1656
1657 def _match_host_epa(self, required, available):
1658 self.log.info("Matching Host EPA attributes")
1659 if required.has_field('cpu_model'):
1660 self.log.debug("Matching CPU model")
1661 if available.has_field('cpu_model') == False:
1662 self.log.debug("Matching CPU model failed. Not available in flavor")
1663 return False
1664 else:
1665 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1666 if required.cpu_model.replace('PREFER', 'REQUIRE') != available.cpu_model:
1667 self.log.debug("Matching CPU model failed. Required: %s, Available: %s", required.cpu_model, available.cpu_model)
1668 return False
1669 elif available.has_field('cpu_model'):
1670 self.log.debug("Rejecting available flavor because cpu_model not required but available")
1671 return False
1672
1673 if required.has_field('cpu_arch'):
1674 self.log.debug("Matching CPU architecture")
1675 if available.has_field('cpu_arch') == False:
1676 self.log.debug("Matching CPU architecture failed. Not available in flavor")
1677 return False
1678 else:
1679 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1680 if required.cpu_arch.replace('PREFER', 'REQUIRE') != available.cpu_arch:
1681 self.log.debug("Matching CPU architecture failed. Required: %s, Available: %s", required.cpu_arch, available.cpu_arch)
1682 return False
1683 elif available.has_field('cpu_arch'):
1684 self.log.debug("Rejecting available flavor because cpu_arch not required but available")
1685 return False
1686
1687 if required.has_field('cpu_vendor'):
1688 self.log.debug("Matching CPU vendor")
1689 if available.has_field('cpu_vendor') == False:
1690 self.log.debug("Matching CPU vendor failed. Not available in flavor")
1691 return False
1692 else:
1693 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1694 if required.cpu_vendor.replace('PREFER', 'REQUIRE') != available.cpu_vendor:
1695 self.log.debug("Matching CPU vendor failed. Required: %s, Available: %s", required.cpu_vendor, available.cpu_vendor)
1696 return False
1697 elif available.has_field('cpu_vendor'):
1698 self.log.debug("Rejecting available flavor because cpu_vendor not required but available")
1699 return False
1700
1701 if required.has_field('cpu_socket_count'):
1702 self.log.debug("Matching CPU socket count")
1703 if available.has_field('cpu_socket_count') == False:
1704 self.log.debug("Matching CPU socket count failed. Not available in flavor")
1705 return False
1706 else:
1707 if required.cpu_socket_count != available.cpu_socket_count:
1708 self.log.debug("Matching CPU socket count failed. Required: %s, Available: %s", required.cpu_socket_count, available.cpu_socket_count)
1709 return False
1710 elif available.has_field('cpu_socket_count'):
1711 self.log.debug("Rejecting available flavor because cpu_socket_count not required but available")
1712 return False
1713
1714 if required.has_field('cpu_core_count'):
1715 self.log.debug("Matching CPU core count")
1716 if available.has_field('cpu_core_count') == False:
1717 self.log.debug("Matching CPU core count failed. Not available in flavor")
1718 return False
1719 else:
1720 if required.cpu_core_count != available.cpu_core_count:
1721 self.log.debug("Matching CPU core count failed. Required: %s, Available: %s", required.cpu_core_count, available.cpu_core_count)
1722 return False
1723 elif available.has_field('cpu_core_count'):
1724 self.log.debug("Rejecting available flavor because cpu_core_count not required but available")
1725 return False
1726
1727 if required.has_field('cpu_core_thread_count'):
1728 self.log.debug("Matching CPU core thread count")
1729 if available.has_field('cpu_core_thread_count') == False:
1730 self.log.debug("Matching CPU core thread count failed. Not available in flavor")
1731 return False
1732 else:
1733 if required.cpu_core_thread_count != available.cpu_core_thread_count:
1734 self.log.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required.cpu_core_thread_count, available.cpu_core_thread_count)
1735 return False
1736 elif available.has_field('cpu_core_thread_count'):
1737 self.log.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1738 return False
1739
1740 if required.has_field('cpu_feature'):
1741 self.log.debug("Matching CPU feature list")
1742 if available.has_field('cpu_feature') == False:
1743 self.log.debug("Matching CPU feature list failed. Not available in flavor")
1744 return False
1745 else:
1746 for feature in required.cpu_feature:
1747 if feature not in available.cpu_feature:
1748 self.log.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature, available.cpu_feature)
1749 return False
1750 elif available.has_field('cpu_feature'):
1751 self.log.debug("Rejecting available flavor because cpu_feature not required but available")
1752 return False
1753 self.log.info("Successful match for Host EPA attributes")
1754 return True
1755
1756
1757 def _match_placement_group_inputs(self, required, available):
1758 self.log.info("Matching Host aggregate attributes")
1759
1760 if not required and not available:
1761 # Host aggregate not required and not available => success
1762 self.log.info("Successful match for Host Aggregate attributes")
1763 return True
1764 if required and available:
1765 # Host aggregate requested and available => Do a match and decide
1766 xx = [ x.as_dict() for x in required ]
1767 yy = [ y.as_dict() for y in available ]
1768 for i in xx:
1769 if i not in yy:
1770 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1771 return False
1772 self.log.info("Successful match for Host Aggregate attributes")
1773 return True
1774 else:
1775 # Either of following conditions => Failure
1776 # - Host aggregate required but not available
1777 # - Host aggregate not required but available
1778 self.log.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required, available)
1779 return False
1780
1781 def match_epa_params(self, resource_info, request_params):
1782 result = self._match_vm_flavor(getattr(request_params, 'vm_flavor'),
1783 getattr(resource_info, 'vm_flavor'))
1784 if result == False:
1785 self.log.debug("VM Flavor mismatched")
1786 return False
1787
1788 result = self._match_guest_epa(getattr(request_params, 'guest_epa'),
1789 getattr(resource_info, 'guest_epa'))
1790 if result == False:
1791 self.log.debug("Guest EPA mismatched")
1792 return False
1793
1794 result = self._match_vswitch_epa(getattr(request_params, 'vswitch_epa'),
1795 getattr(resource_info, 'vswitch_epa'))
1796 if result == False:
1797 self.log.debug("Vswitch EPA mismatched")
1798 return False
1799
1800 result = self._match_hypervisor_epa(getattr(request_params, 'hypervisor_epa'),
1801 getattr(resource_info, 'hypervisor_epa'))
1802 if result == False:
1803 self.log.debug("Hypervisor EPA mismatched")
1804 return False
1805
1806 result = self._match_host_epa(getattr(request_params, 'host_epa'),
1807 getattr(resource_info, 'host_epa'))
1808 if result == False:
1809 self.log.debug("Host EPA mismatched")
1810 return False
1811
1812 result = self._match_placement_group_inputs(getattr(request_params, 'host_aggregate'),
1813 getattr(resource_info, 'host_aggregate'))
1814
1815 if result == False:
1816 self.log.debug("Host Aggregate mismatched")
1817 return False
1818
1819 return True
1820
1821 def _select_resource_flavor(self, account, vdu_init):
1822 """
1823 Select a existing flavor if it matches the request or create new flavor
1824 """
1825 flavor = RwcalYang.FlavorInfoItem()
1826 flavor.name = str(uuid.uuid4())
1827 epa_types = ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1828 epa_dict = {k: v for k, v in vdu_init.as_dict().items() if k in epa_types}
1829 flavor.from_dict(epa_dict)
1830
1831 rc, response = self.do_get_flavor_list(account)
1832 if rc != RwTypes.RwStatus.SUCCESS:
1833 self.log.error("Get-flavor-info-list operation failed for cloud account: %s",
1834 account.name)
1835 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account.name))
1836
1837 flavor_id = None
1838 flavor_list = response.flavorinfo_list
1839 self.log.debug("Received %d flavor information from RW.CAL", len(flavor_list))
1840 for flv in flavor_list:
1841 self.log.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1842 vdu_init.name, flv)
1843 if self.match_epa_params(flv, vdu_init):
1844 self.log.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1845 vdu_init.name, flv.name, flv.id)
1846 return flv.id
1847
1848 if account.openstack.dynamic_flavor_support is False:
1849 self.log.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init.name)
1850 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1851 else:
1852 rc,flavor_id = self.do_create_flavor(account,flavor)
1853 if rc != RwTypes.RwStatus.SUCCESS:
1854 self.log.error("Create-flavor operation failed for cloud account: %s",
1855 account.name)
1856 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account.name))
1857 return flavor_id
1858
1859 @rwcalstatus(ret_on_failure=[""])
1860 def do_create_vdu(self, account, vdu_init):
1861 """Create a new virtual deployment unit
1862
1863 Arguments:
1864 account - a cloud account
1865 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
1866
1867 Returns:
1868 The vdu_id
1869 """
1870 ### First create required number of ports aka connection points
1871 with self._use_driver(account) as drv:
1872 ### If floating_ip is required and we don't have one, better fail before any further allocation
1873 if vdu_init.has_field('allocate_public_address') and vdu_init.allocate_public_address:
1874 if account.openstack.has_field('floating_ip_pool'):
1875 pool_name = account.openstack.floating_ip_pool
1876 else:
1877 pool_name = None
1878 floating_ip = self._allocate_floating_ip(drv, pool_name)
1879 else:
1880 floating_ip = None
1881
1882 port_list = []
1883 network_list = []
1884 for c_point in vdu_init.connection_points:
1885 if c_point.virtual_link_id in network_list:
1886 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"
1887 else:
1888 network_list.append(c_point.virtual_link_id)
1889 port_id = self._create_connection_point(account, c_point)
1890 port_list.append(port_id)
1891
1892 if not vdu_init.has_field('flavor_id'):
1893 vdu_init.flavor_id = self._select_resource_flavor(account,vdu_init)
1894
1895 ### Check VDU Virtual Interface type and make sure VM with property exists
1896 if vdu_init.connection_points is not None:
1897 ### All virtual interfaces need to be of the same type for Openstack Accounts
1898 if not all(cp.type_yang == vdu_init.connection_points[0].type_yang for cp in vdu_init.connection_points):
1899 ### We have a mix of E1000 & VIRTIO virtual interface types in the VDU, abort instantiation.
1900 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO & E1000."
1901
1902 with self._use_driver(account) as drv:
1903 img_info = drv.glance_image_get(vdu_init.image_id)
1904
1905 virt_intf_type = vdu_init.connection_points[0].type_yang
1906 if virt_intf_type == 'E1000':
1907 if 'hw_vif_model' in img_info and img_info.hw_vif_model == 'e1000':
1908 self.log.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
1909 else:
1910 err_str = ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info.name)
1911 self.log.error(err_str)
1912 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1913 elif virt_intf_type == 'VIRTIO':
1914 if 'hw_vif_model' in img_info:
1915 err_str = ("VDU has Virtual Interface VIRTIO, but image '%s' has hw_vif_model mismatch" % img_info.name)
1916 self.log.error(err_str)
1917 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1918 else:
1919 self.log.debug("VDU has Virtual Interface VIRTIO, found matching image")
1920 else:
1921 err_str = ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type)
1922 self.log.error(err_str)
1923 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str)
1924
1925 with self._use_driver(account) as drv:
1926 ### Now Create VM
1927 vm = RwcalYang.VMInfoItem()
1928 vm.vm_name = vdu_init.name
1929 vm.flavor_id = vdu_init.flavor_id
1930 vm.image_id = vdu_init.image_id
1931 vm_network = vm.network_list.add()
1932 vm_network.network_id = drv._mgmt_network_id
1933 if vdu_init.has_field('vdu_init') and vdu_init.vdu_init.has_field('userdata'):
1934 vm.cloud_init.userdata = vdu_init.vdu_init.userdata
1935
1936 if vdu_init.has_field('node_id'):
1937 vm.user_tags.node_id = vdu_init.node_id;
1938
1939 if vdu_init.has_field('availability_zone') and vdu_init.availability_zone.has_field('name'):
1940 vm.availability_zone = vdu_init.availability_zone.name
1941
1942 if vdu_init.has_field('server_group'):
1943 ### Get list of server group in openstack for name->id mapping
1944 openstack_group_list = drv.nova_server_group_list()
1945 group_id = [ i['id'] for i in openstack_group_list if i['name'] == vdu_init.server_group.name]
1946 if len(group_id) != 1:
1947 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]))
1948 vm.server_group = group_id[0]
1949
1950 for port_id in port_list:
1951 port = vm.port_list.add()
1952 port.port_id = port_id
1953
1954 pci_assignement = self.prepare_vpci_metadata(drv, vdu_init)
1955 if pci_assignement != '':
1956 vm.user_tags.pci_assignement = pci_assignement
1957
1958 vm_id = self.do_create_vm(account, vm, no_rwstatus=True)
1959 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
1960 return vm_id
1961
1962 def prepare_vpci_metadata(self, drv, vdu_init):
1963 pci_assignement = ''
1964 ### TEF specific metadata creation for
1965 virtio_vpci = []
1966 sriov_vpci = []
1967 virtio_meta = ''
1968 sriov_meta = ''
1969 ### For MGMT interface
1970 if vdu_init.has_field('mgmt_vpci'):
1971 xx = 'u\''+ drv._mgmt_network_id + '\' :[[u\'' + vdu_init.mgmt_vpci + '\', ' + '\'\']]'
1972 virtio_vpci.append(xx)
1973
1974 for c_point in vdu_init.connection_points:
1975 if c_point.has_field('vpci'):
1976 if c_point.has_field('vpci') and c_point.type_yang == 'VIRTIO':
1977 xx = 'u\''+c_point.virtual_link_id + '\' :[[u\'' + c_point.vpci + '\', ' + '\'\']]'
1978 virtio_vpci.append(xx)
1979 elif c_point.has_field('vpci') and c_point.type_yang == 'SR_IOV':
1980 xx = '[u\'' + c_point.vpci + '\', ' + '\'\']'
1981 sriov_vpci.append(xx)
1982
1983 if virtio_vpci:
1984 virtio_meta += ','.join(virtio_vpci)
1985
1986 if sriov_vpci:
1987 sriov_meta = 'u\'VF\': ['
1988 sriov_meta += ','.join(sriov_vpci)
1989 sriov_meta += ']'
1990
1991 if virtio_meta != '':
1992 pci_assignement += virtio_meta
1993 pci_assignement += ','
1994
1995 if sriov_meta != '':
1996 pci_assignement += sriov_meta
1997
1998 if pci_assignement != '':
1999 pci_assignement = '{' + pci_assignement + '}'
2000
2001 return pci_assignement
2002
2003
2004
2005 def prepare_vdu_on_boot(self, account, server_id, floating_ip):
2006 cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url,
2007 username = account.openstack.key,
2008 password = account.openstack.secret,
2009 tenant_name = account.openstack.tenant,
2010 mgmt_network = account.openstack.mgmt_network,
2011 server_id = server_id)
2012
2013 if floating_ip is not None:
2014 cmd += (" --floating_ip "+ floating_ip.ip)
2015
2016 exec_path = 'python3 ' + os.path.dirname(openstack_drv.__file__)
2017 exec_cmd = exec_path+'/'+cmd
2018 self.log.info("Running command: %s" %(exec_cmd))
2019 subprocess.call(exec_cmd, shell=True)
2020
2021 @rwstatus
2022 def do_modify_vdu(self, account, vdu_modify):
2023 """Modify Properties of existing virtual deployment unit
2024
2025 Arguments:
2026 account - a cloud account
2027 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2028 """
2029 ### First create required number of ports aka connection points
2030 port_list = []
2031 network_list = []
2032 for c_point in vdu_modify.connection_points_add:
2033 if c_point.virtual_link_id in network_list:
2034 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"
2035 else:
2036 network_list.append(c_point.virtual_link_id)
2037 port_id = self._create_connection_point(account, c_point)
2038 port_list.append(port_id)
2039
2040 ### Now add the ports to VM
2041 for port_id in port_list:
2042 with self._use_driver(account) as drv:
2043 drv.nova_server_add_port(vdu_modify.vdu_id, port_id)
2044
2045 ### Delete the requested connection_points
2046 for c_point in vdu_modify.connection_points_remove:
2047 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
2048
2049 if vdu_modify.has_field('image_id'):
2050 with self._use_driver(account) as drv:
2051 drv.nova_server_rebuild(vdu_modify.vdu_id, vdu_modify.image_id)
2052
2053
2054 @rwstatus
2055 def do_delete_vdu(self, account, vdu_id):
2056 """Delete a virtual deployment unit
2057
2058 Arguments:
2059 account - a cloud account
2060 vdu_id - id for the vdu to be deleted
2061
2062 Returns:
2063 None
2064 """
2065 if not vdu_id:
2066 self.log.error("empty vdu_id during the vdu deletion")
2067 return
2068
2069 with self._use_driver(account) as drv:
2070 ### Get list of floating_ips associated with this instance and delete them
2071 floating_ips = [ f for f in drv.nova_floating_ip_list() if f.instance_id == vdu_id ]
2072 for f in floating_ips:
2073 drv.nova_drv.floating_ip_delete(f)
2074
2075 ### Get list of port on VM and delete them.
2076 port_list = drv.neutron_port_list(**{'device_id': vdu_id})
2077
2078 for port in port_list:
2079 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
2080 self.do_delete_port(account, port['id'], no_rwstatus=True)
2081
2082 self.do_delete_vm(account, vdu_id, no_rwstatus=True)
2083
2084
2085 @rwstatus(ret_on_failure=[None])
2086 def do_get_vdu(self, account, vdu_id):
2087 """Get information about a virtual deployment unit.
2088
2089 Arguments:
2090 account - a cloud account
2091 vdu_id - id for the vdu
2092
2093 Returns:
2094 Object of type RwcalYang.VDUInfoParams
2095 """
2096 with self._use_driver(account) as drv:
2097
2098 ### Get list of ports excluding the one for management network
2099 port_list = [p for p in drv.neutron_port_list(**{'device_id': vdu_id}) if p['network_id'] != drv.get_mgmt_network_id()]
2100
2101 vm = drv.nova_server_get(vdu_id)
2102
2103 flavor_info = None
2104 if ('flavor' in vm) and ('id' in vm['flavor']):
2105 try:
2106 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2107 except Exception as e:
2108 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2109
2110 openstack_group_list = drv.nova_server_group_list()
2111 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2112 vdu_info = RwcalOpenstackPlugin._fill_vdu_info(vm,
2113 flavor_info,
2114 account.openstack.mgmt_network,
2115 port_list,
2116 server_group)
2117 if vdu_info.state == 'active':
2118 try:
2119 console_info = drv.nova_server_console(vdu_info.vdu_id)
2120 except Exception as e:
2121 pass
2122 else:
2123 vdu_info.console_url = console_info['console']['url']
2124 pass
2125
2126 return vdu_info
2127
2128
2129 @rwstatus(ret_on_failure=[None])
2130 def do_get_vdu_list(self, account):
2131 """Get information about all the virtual deployment units
2132
2133 Arguments:
2134 account - a cloud account
2135
2136 Returns:
2137 A list of objects of type RwcalYang.VDUInfoParams
2138 """
2139 vnf_resources = RwcalYang.VNFResources()
2140 with self._use_driver(account) as drv:
2141 vms = drv.nova_server_list()
2142 for vm in vms:
2143 ### Get list of ports excluding one for management network
2144 port_list = [p for p in drv.neutron_port_list(**{'device_id': vm['id']}) if p['network_id'] != drv.get_mgmt_network_id()]
2145
2146 flavor_info = None
2147
2148 if ('flavor' in vm) and ('id' in vm['flavor']):
2149 try:
2150 flavor_info = drv.nova_flavor_get(vm['flavor']['id'])
2151 except Exception as e:
2152 self.log.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm['flavor']['id'], str(e)))
2153
2154 else:
2155 flavor_info = None
2156
2157 openstack_group_list = drv.nova_server_group_list()
2158 server_group = [ i['name'] for i in openstack_group_list if vm['id'] in i['members']]
2159
2160 vdu = RwcalOpenstackPlugin._fill_vdu_info(vm,
2161 flavor_info,
2162 account.openstack.mgmt_network,
2163 port_list,
2164 server_group)
2165 if vdu.state == 'active':
2166 try:
2167 console_info = drv.nova_server_console(vdu.vdu_id)
2168 except Exception as e:
2169 pass
2170 else:
2171 vdu.console_url = console_info['console']['url']
2172 pass
2173 vnf_resources.vdu_info_list.append(vdu)
2174 return vnf_resources
2175
2176