3 # Copyright 2016 RIFT.IO Inc
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
26 import rift
.rwcal
.openstack
as openstack_drv
28 import rift
.cal
.rwcal_status
as rwcal_status
30 import neutronclient
.common
.exceptions
as NeutronException
31 import keystoneclient
.exceptions
as KeystoneExceptions
35 gi
.require_version('RwSdn', '1.0')
37 from gi
.repository
import (
43 PREPARE_VM_CMD
= "prepare_vm.py --auth_url {auth_url} --username {username} --password {password} --tenant_name {tenant_name} --region {region} --user_domain {user_domain} --project_domain {project_domain} --mgmt_network {mgmt_network} --server_id {server_id} --port_metadata "
45 rwstatus_exception_map
= { IndexError: RwTypes
.RwStatus
.NOTFOUND
,
46 KeyError: RwTypes
.RwStatus
.NOTFOUND
,
47 NotImplementedError: RwTypes
.RwStatus
.NOT_IMPLEMENTED
,}
49 rwstatus
= rw_status
.rwstatus_from_exc_map(rwstatus_exception_map
)
50 rwcalstatus
= rwcal_status
.rwcalstatus_from_exc_map(rwstatus_exception_map
)
53 espec_utils
= openstack_drv
.OpenstackExtraSpecUtils()
55 class OpenstackCALOperationFailure(Exception):
58 class UninitializedPluginError(Exception):
62 class OpenstackServerGroupError(Exception):
66 class ImageUploadError(Exception):
70 class RwcalOpenstackPlugin(GObject
.Object
, RwCal
.Cloud
):
71 """This class implements the CAL VALA methods for openstack."""
76 GObject
.Object
.__init
__(self
)
77 self
._driver
_class
= openstack_drv
.OpenstackDriver
78 self
.log
= logging
.getLogger('rwcal.openstack.%s' % RwcalOpenstackPlugin
.instance_num
)
79 self
.log
.setLevel(logging
.DEBUG
)
81 self
._rwlog
_handler
= None
82 RwcalOpenstackPlugin
.instance_num
+= 1
84 @contextlib.contextmanager
85 def _use_driver(self
, account
):
86 if self
._rwlog
_handler
is None:
87 raise UninitializedPluginError("Must call init() in CAL plugin before use.")
89 with rwlogger
.rwlog_root_handler(self
._rwlog
_handler
):
91 drv
= self
._driver
_class
(username
= account
.openstack
.key
,
92 password
= account
.openstack
.secret
,
93 auth_url
= account
.openstack
.auth_url
,
94 tenant_name
= account
.openstack
.tenant
,
95 mgmt_network
= account
.openstack
.mgmt_network
,
96 cert_validate
= account
.openstack
.cert_validate
,
97 user_domain_name
= account
.openstack
.user_domain
,
98 project_domain_name
= account
.openstack
.project_domain
,
99 region
= account
.openstack
.region
)
100 except (KeystoneExceptions
.Unauthorized
, KeystoneExceptions
.AuthorizationFailure
,
101 NeutronException
.NotFound
) as e
:
103 except Exception as e
:
104 self
.log
.error("RwcalOpenstackPlugin: OpenstackDriver init failed. Exception: %s" %(str(e
)))
111 def do_init(self
, rwlog_ctx
):
112 self
._rwlog
_handler
= rwlogger
.RwLogger(
113 category
="rw-cal-log",
114 subcategory
="openstack",
117 self
.log
.addHandler(self
._rwlog
_handler
)
118 self
.log
.propagate
= False
120 @rwstatus(ret_on_failure
=[None])
121 def do_validate_cloud_creds(self
, account
):
123 Validates the cloud account credentials for the specified account.
124 Performs an access to the resources using Keystone API. If creds
125 are not valid, returns an error code & reason string
127 account - a cloud account to validate
130 Validation Code and Details String
132 status
= RwcalYang
.CloudConnectionStatus()
134 with self
._use
_driver
(account
) as drv
:
135 drv
.validate_account_creds()
137 except KeystoneExceptions
.Unauthorized
as e
:
138 self
.log
.error("Invalid credentials given for VIM account %s" %account
.name
)
139 status
.status
= "failure"
140 status
.details
= "Invalid Credentials: %s" % str(e
)
142 except KeystoneExceptions
.AuthorizationFailure
as e
:
143 self
.log
.error("Bad authentication URL given for VIM account %s. Given auth url: %s" % (
144 account
.name
, account
.openstack
.auth_url
))
145 status
.status
= "failure"
146 status
.details
= "Invalid auth url: %s" % str(e
)
148 except NeutronException
.NotFound
as e
:
149 self
.log
.error("Given management network %s could not be found for VIM account %s" % (
150 account
.openstack
.mgmt_network
, account
.name
))
151 status
.status
= "failure"
152 status
.details
= "mgmt network does not exist: %s" % str(e
)
154 except openstack_drv
.ValidationError
as e
:
155 self
.log
.error("RwcalOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e
))
156 status
.status
= "failure"
157 status
.details
= "Invalid Credentials: %s" % str(e
)
159 except Exception as e
:
160 msg
= "RwcalOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e
))
162 status
.status
= "failure"
166 status
.status
= "success"
167 status
.details
= "Connection was successful"
171 @rwstatus(ret_on_failure
=[""])
172 def do_get_management_network(self
, account
):
174 Returns the management network associated with the specified account.
176 account - a cloud account
179 The management network
181 return account
.openstack
.mgmt_network
183 @rwstatus(ret_on_failure
=[""])
184 def do_create_tenant(self
, account
, name
):
185 """Create a new tenant.
188 account - a cloud account
189 name - name of the tenant
194 raise NotImplementedError
197 def do_delete_tenant(self
, account
, tenant_id
):
201 account - a cloud account
202 tenant_id - id of the tenant
204 raise NotImplementedError
206 @rwstatus(ret_on_failure
=[[]])
207 def do_get_tenant_list(self
, account
):
211 account - a cloud account
216 raise NotImplementedError
218 @rwstatus(ret_on_failure
=[""])
219 def do_create_role(self
, account
, name
):
220 """Create a new user.
223 account - a cloud account
224 name - name of the user
229 raise NotImplementedError
232 def do_delete_role(self
, account
, role_id
):
236 account - a cloud account
237 role_id - id of the user
239 raise NotImplementedError
241 @rwstatus(ret_on_failure
=[[]])
242 def do_get_role_list(self
, account
):
246 account - a cloud account
251 raise NotImplementedError
253 @rwstatus(ret_on_failure
=[""])
254 def do_create_image(self
, account
, image
):
258 account - a cloud account
259 image - a description of the image to create
266 # If the use passed in a file descriptor, use that to
268 if image
.has_field("fileno"):
269 new_fileno
= os
.dup(image
.fileno
)
270 hdl
= os
.fdopen(new_fileno
, 'rb')
272 hdl
= open(image
.location
, "rb")
273 except Exception as e
:
274 self
.log
.error("Could not open file for upload. Exception received: %s", str(e
))
279 kwargs
['name'] = image
.name
281 if image
.disk_format
:
282 kwargs
['disk_format'] = image
.disk_format
283 if image
.container_format
:
284 kwargs
['container_format'] = image
.container_format
286 with self
._use
_driver
(account
) as drv
:
288 image_id
= drv
.glance_image_create(**kwargs
)
290 drv
.glance_image_upload(image_id
, fd
)
293 stored_image
= drv
.glance_image_get(image_id
)
294 if stored_image
.checksum
!= image
.checksum
:
295 drv
.glance_image_delete(image_id
=image_id
)
296 raise ImageUploadError(
297 "image checksum did not match (actual: %s, expected: %s). Deleting." %
298 (stored_image
.checksum
, image
.checksum
)
304 def do_delete_image(self
, account
, image_id
):
305 """Delete a vm image.
308 account - a cloud account
309 image_id - id of the image to delete
311 with self
._use
_driver
(account
) as drv
:
312 drv
.glance_image_delete(image_id
=image_id
)
316 def _fill_image_info(img_info
):
317 """Create a GI object from image info dictionary
319 Converts image information dictionary object returned by openstack
320 driver into Protobuf Gi Object
323 account - a cloud account
324 img_info - image information dictionary object from openstack
329 img
= RwcalYang
.ImageInfoItem()
330 img
.name
= img_info
['name']
331 img
.id = img_info
['id']
332 img
.checksum
= img_info
['checksum']
333 img
.disk_format
= img_info
['disk_format']
334 img
.container_format
= img_info
['container_format']
335 if img_info
['status'] == 'active':
338 img
.state
= 'inactive'
341 @rwstatus(ret_on_failure
=[[]])
342 def do_get_image_list(self
, account
):
343 """Return a list of the names of all available images.
346 account - a cloud account
349 The the list of images in VimResources object
351 response
= RwcalYang
.VimResources()
352 with self
._use
_driver
(account
) as drv
:
353 images
= drv
.glance_image_list()
355 response
.imageinfo_list
.append(RwcalOpenstackPlugin
._fill
_image
_info
(img
))
358 @rwstatus(ret_on_failure
=[None])
359 def do_get_image(self
, account
, image_id
):
360 """Return a image information.
363 account - a cloud account
364 image_id - an id of the image
367 ImageInfoItem object containing image information.
369 with self
._use
_driver
(account
) as drv
:
370 image
= drv
.glance_image_get(image_id
)
371 return RwcalOpenstackPlugin
._fill
_image
_info
(image
)
373 # This is being deprecated. Please do not use for new SW development
374 @rwstatus(ret_on_failure
=[""])
375 def do_create_vm(self
, account
, vminfo
):
376 """Create a new virtual machine.
379 account - a cloud account
380 vminfo - information that defines the type of VM to create
386 kwargs
['name'] = vminfo
.vm_name
387 kwargs
['flavor_id'] = vminfo
.flavor_id
388 if vminfo
.has_field('image_id'):
389 kwargs
['image_id'] = vminfo
.image_id
391 with self
._use
_driver
(account
) as drv
:
392 ### If floating_ip is required and we don't have one, better fail before any further allocation
395 if vminfo
.has_field('allocate_public_address') and vminfo
.allocate_public_address
:
396 if account
.openstack
.has_field('floating_ip_pool'):
397 pool_name
= account
.openstack
.floating_ip_pool
400 if vminfo
.has_field('cloud_init') and vminfo
.cloud_init
.has_field('userdata'):
401 kwargs
['userdata'] = vminfo
.cloud_init
.userdata
403 kwargs
['userdata'] = ''
405 if account
.openstack
.security_groups
:
406 kwargs
['security_groups'] = account
.openstack
.security_groups
409 for port
in vminfo
.port_list
:
410 port_list
.append(port
.port_id
)
413 kwargs
['port_list'] = port_list
416 for network
in vminfo
.network_list
:
417 network_list
.append(network
.network_id
)
420 kwargs
['network_list'] = network_list
423 for field
in vminfo
.user_tags
.fields
:
424 if vminfo
.user_tags
.has_field(field
):
425 metadata
[field
] = getattr(vminfo
.user_tags
, field
)
426 kwargs
['metadata'] = metadata
428 if vminfo
.has_field('availability_zone'):
429 kwargs
['availability_zone'] = vminfo
.availability_zone
431 kwargs
['availability_zone'] = None
433 if vminfo
.has_field('server_group'):
434 kwargs
['scheduler_hints'] = {'group': vminfo
.server_group
}
436 kwargs
['scheduler_hints'] = None
438 with self
._use
_driver
(account
) as drv
:
439 vm_id
= drv
.nova_server_create(**kwargs
)
441 self
.prepare_vdu_on_boot(account
, vm_id
, floating_ip
)
446 def do_start_vm(self
, account
, vm_id
):
447 """Start an existing virtual machine.
450 account - a cloud account
451 vm_id - an id of the VM
453 with self
._use
_driver
(account
) as drv
:
454 drv
.nova_server_start(vm_id
)
457 def do_stop_vm(self
, account
, vm_id
):
458 """Stop a running virtual machine.
461 account - a cloud account
462 vm_id - an id of the VM
464 with self
._use
_driver
(account
) as drv
:
465 drv
.nova_server_stop(vm_id
)
468 def do_delete_vm(self
, account
, vm_id
):
469 """Delete a virtual machine.
472 account - a cloud account
473 vm_id - an id of the VM
475 with self
._use
_driver
(account
) as drv
:
476 drv
.nova_server_delete(vm_id
)
479 def do_reboot_vm(self
, account
, vm_id
):
480 """Reboot a virtual machine.
483 account - a cloud account
484 vm_id - an id of the VM
486 with self
._use
_driver
(account
) as drv
:
487 drv
.nova_server_reboot(vm_id
)
490 def _fill_vm_info(vm_info
, mgmt_network
):
491 """Create a GI object from vm info dictionary
493 Converts VM information dictionary object returned by openstack
494 driver into Protobuf Gi Object
497 vm_info - VM information from openstack
498 mgmt_network - Management network
501 Protobuf Gi object for VM
503 vm
= RwcalYang
.VMInfoItem()
504 vm
.vm_id
= vm_info
['id']
505 vm
.vm_name
= vm_info
['name']
506 vm
.image_id
= vm_info
['image']['id']
507 vm
.flavor_id
= vm_info
['flavor']['id']
508 vm
.state
= vm_info
['status']
509 for network_name
, network_info
in vm_info
['addresses'].items():
511 if network_name
== mgmt_network
:
512 vm
.public_ip
= next((item
['addr']
513 for item
in network_info
514 if item
['OS-EXT-IPS:type'] == 'floating'),
515 network_info
[0]['addr'])
516 vm
.management_ip
= network_info
[0]['addr']
518 for interface
in network_info
:
519 addr
= vm
.private_ip_list
.add()
520 addr
.ip_address
= interface
['addr']
522 for network_name
, network_info
in vm_info
['addresses'].items():
523 if network_info
and network_name
== mgmt_network
and not vm
.public_ip
:
524 for interface
in network_info
:
525 if 'OS-EXT-IPS:type' in interface
and interface
['OS-EXT-IPS:type'] == 'floating':
526 vm
.public_ip
= interface
['addr']
528 # Look for any metadata
529 for key
, value
in vm_info
['metadata'].items():
530 if key
in vm
.user_tags
.fields
:
531 setattr(vm
.user_tags
, key
, value
)
532 if 'OS-EXT-SRV-ATTR:host' in vm_info
:
533 if vm_info
['OS-EXT-SRV-ATTR:host'] != None:
534 vm
.host_name
= vm_info
['OS-EXT-SRV-ATTR:host']
535 if 'OS-EXT-AZ:availability_zone' in vm_info
:
536 if vm_info
['OS-EXT-AZ:availability_zone'] != None:
537 vm
.availability_zone
= vm_info
['OS-EXT-AZ:availability_zone']
540 @rwstatus(ret_on_failure
=[[]])
541 def do_get_vm_list(self
, account
):
542 """Return a list of the VMs as vala boxed objects
545 account - a cloud account
548 List containing VM information
550 response
= RwcalYang
.VimResources()
551 with self
._use
_driver
(account
) as drv
:
552 vms
= drv
.nova_server_list()
554 response
.vminfo_list
.append(RwcalOpenstackPlugin
._fill
_vm
_info
(vm
, account
.openstack
.mgmt_network
))
557 @rwstatus(ret_on_failure
=[None])
558 def do_get_vm(self
, account
, id):
559 """Return vm information.
562 account - a cloud account
563 id - an id for the VM
568 with self
._use
_driver
(account
) as drv
:
569 vm
= drv
.nova_server_get(id)
570 return RwcalOpenstackPlugin
._fill
_vm
_info
(vm
, account
.openstack
.mgmt_network
)
573 def _get_guest_epa_specs(guest_epa
):
575 Returns EPA Specs dictionary for guest_epa attributes
578 if guest_epa
.has_field('mempage_size'):
579 mempage_size
= espec_utils
.guest
.mano_to_extra_spec_mempage_size(guest_epa
.mempage_size
)
580 if mempage_size
is not None:
581 epa_specs
['hw:mem_page_size'] = mempage_size
583 if guest_epa
.has_field('cpu_pinning_policy'):
584 cpu_pinning_policy
= espec_utils
.guest
.mano_to_extra_spec_cpu_pinning_policy(guest_epa
.cpu_pinning_policy
)
585 if cpu_pinning_policy
is not None:
586 epa_specs
['hw:cpu_policy'] = cpu_pinning_policy
588 if guest_epa
.has_field('cpu_thread_pinning_policy'):
589 cpu_thread_pinning_policy
= espec_utils
.guest
.mano_to_extra_spec_cpu_thread_pinning_policy(guest_epa
.cpu_thread_pinning_policy
)
590 if cpu_thread_pinning_policy
is None:
591 epa_specs
['hw:cpu_threads_policy'] = cpu_thread_pinning_policy
593 if guest_epa
.has_field('trusted_execution'):
594 trusted_execution
= espec_utils
.guest
.mano_to_extra_spec_trusted_execution(guest_epa
.trusted_execution
)
595 if trusted_execution
is not None:
596 epa_specs
['trust:trusted_host'] = trusted_execution
598 if guest_epa
.has_field('numa_node_policy'):
599 if guest_epa
.numa_node_policy
.has_field('node_cnt'):
600 numa_node_count
= espec_utils
.guest
.mano_to_extra_spec_numa_node_count(guest_epa
.numa_node_policy
.node_cnt
)
601 if numa_node_count
is not None:
602 epa_specs
['hw:numa_nodes'] = numa_node_count
604 if guest_epa
.numa_node_policy
.has_field('mem_policy'):
605 numa_memory_policy
= espec_utils
.guest
.mano_to_extra_spec_numa_memory_policy(guest_epa
.numa_node_policy
.mem_policy
)
606 if numa_memory_policy
is not None:
607 epa_specs
['hw:numa_mempolicy'] = numa_memory_policy
609 if guest_epa
.numa_node_policy
.has_field('node'):
610 for node
in guest_epa
.numa_node_policy
.node
:
611 if node
.has_field('vcpu') and node
.vcpu
:
612 epa_specs
['hw:numa_cpus.'+str(node
.id)] = ','.join([str(j
.id) for j
in node
.vcpu
])
614 epa_specs
['hw:numa_mem.'+str(node
.id)] = str(node
.memory_mb
)
616 if guest_epa
.has_field('pcie_device'):
618 for device
in guest_epa
.pcie_device
:
619 pci_devices
.append(device
.device_id
+':'+str(device
.count
))
620 epa_specs
['pci_passthrough:alias'] = ','.join(pci_devices
)
625 def _get_host_epa_specs(host_epa
):
627 Returns EPA Specs dictionary for host_epa attributes
632 if host_epa
.has_field('cpu_model'):
633 cpu_model
= espec_utils
.host
.mano_to_extra_spec_cpu_model(host_epa
.cpu_model
)
634 if cpu_model
is not None:
635 epa_specs
['capabilities:cpu_info:model'] = cpu_model
637 if host_epa
.has_field('cpu_arch'):
638 cpu_arch
= espec_utils
.host
.mano_to_extra_spec_cpu_arch(host_epa
.cpu_arch
)
639 if cpu_arch
is not None:
640 epa_specs
['capabilities:cpu_info:arch'] = cpu_arch
642 if host_epa
.has_field('cpu_vendor'):
643 cpu_vendor
= espec_utils
.host
.mano_to_extra_spec_cpu_vendor(host_epa
.cpu_vendor
)
644 if cpu_vendor
is not None:
645 epa_specs
['capabilities:cpu_info:vendor'] = cpu_vendor
647 if host_epa
.has_field('cpu_socket_count'):
648 cpu_socket_count
= espec_utils
.host
.mano_to_extra_spec_cpu_socket_count(host_epa
.cpu_socket_count
)
649 if cpu_socket_count
is not None:
650 epa_specs
['capabilities:cpu_info:topology:sockets'] = cpu_socket_count
652 if host_epa
.has_field('cpu_core_count'):
653 cpu_core_count
= espec_utils
.host
.mano_to_extra_spec_cpu_core_count(host_epa
.cpu_core_count
)
654 if cpu_core_count
is not None:
655 epa_specs
['capabilities:cpu_info:topology:cores'] = cpu_core_count
657 if host_epa
.has_field('cpu_core_thread_count'):
658 cpu_core_thread_count
= espec_utils
.host
.mano_to_extra_spec_cpu_core_thread_count(host_epa
.cpu_core_thread_count
)
659 if cpu_core_thread_count
is not None:
660 epa_specs
['capabilities:cpu_info:topology:threads'] = cpu_core_thread_count
662 if host_epa
.has_field('cpu_feature'):
664 espec_cpu_features
= []
665 for feature
in host_epa
.cpu_feature
:
666 cpu_features
.append(feature
.feature
)
667 espec_cpu_features
= espec_utils
.host
.mano_to_extra_spec_cpu_features(cpu_features
)
668 if espec_cpu_features
is not None:
669 epa_specs
['capabilities:cpu_info:features'] = espec_cpu_features
673 def _get_hypervisor_epa_specs(guest_epa
):
675 Returns EPA Specs dictionary for hypervisor_epa attributes
678 return hypervisor_epa
681 def _get_vswitch_epa_specs(guest_epa
):
683 Returns EPA Specs dictionary for vswitch_epa attributes
689 def _get_host_aggregate_epa_specs(host_aggregate
):
691 Returns EPA Specs dictionary for host aggregates
694 for aggregate
in host_aggregate
:
695 epa_specs
['aggregate_instance_extra_specs:'+aggregate
.metadata_key
] = aggregate
.metadata_value
700 def _get_epa_specs(flavor
):
702 Returns epa_specs dictionary based on flavor information
705 if flavor
.has_field('guest_epa'):
706 guest_epa
= RwcalOpenstackPlugin
._get
_guest
_epa
_specs
(flavor
.guest_epa
)
707 epa_specs
.update(guest_epa
)
708 if flavor
.has_field('host_epa'):
709 host_epa
= RwcalOpenstackPlugin
._get
_host
_epa
_specs
(flavor
.host_epa
)
710 epa_specs
.update(host_epa
)
711 if flavor
.has_field('hypervisor_epa'):
712 hypervisor_epa
= RwcalOpenstackPlugin
._get
_hypervisor
_epa
_specs
(flavor
.hypervisor_epa
)
713 epa_specs
.update(hypervisor_epa
)
714 if flavor
.has_field('vswitch_epa'):
715 vswitch_epa
= RwcalOpenstackPlugin
._get
_vswitch
_epa
_specs
(flavor
.vswitch_epa
)
716 epa_specs
.update(vswitch_epa
)
717 if flavor
.has_field('host_aggregate'):
718 host_aggregate
= RwcalOpenstackPlugin
._get
_host
_aggregate
_epa
_specs
(flavor
.host_aggregate
)
719 epa_specs
.update(host_aggregate
)
722 @rwstatus(ret_on_failure
=[""])
723 def do_create_flavor(self
, account
, flavor
):
724 """Create new flavor.
727 account - a cloud account
728 flavor - flavor of the VM
733 epa_specs
= RwcalOpenstackPlugin
._get
_epa
_specs
(flavor
)
734 with self
._use
_driver
(account
) as drv
:
735 return drv
.nova_flavor_create(name
= flavor
.name
,
736 ram
= flavor
.vm_flavor
.memory_mb
,
737 vcpus
= flavor
.vm_flavor
.vcpu_count
,
738 disk
= flavor
.vm_flavor
.storage_gb
,
739 epa_specs
= epa_specs
)
743 def do_delete_flavor(self
, account
, flavor_id
):
747 account - a cloud account
748 flavor_id - id flavor of the VM
750 with self
._use
_driver
(account
) as drv
:
751 drv
.nova_flavor_delete(flavor_id
)
754 def _fill_epa_attributes(flavor
, flavor_info
):
755 """Helper function to populate the EPA attributes
758 flavor : Object with EPA attributes
759 flavor_info: A dictionary of flavor_info received from openstack
763 getattr(flavor
, 'vm_flavor').vcpu_count
= flavor_info
['vcpus']
764 getattr(flavor
, 'vm_flavor').memory_mb
= flavor_info
['ram']
765 getattr(flavor
, 'vm_flavor').storage_gb
= flavor_info
['disk']
767 ### If extra_specs in flavor_info
768 if not 'extra_specs' in flavor_info
:
771 for attr
in flavor_info
['extra_specs']:
772 if attr
== 'hw:cpu_policy':
773 cpu_pinning_policy
= espec_utils
.guest
.extra_spec_to_mano_cpu_pinning_policy(flavor_info
['extra_specs']['hw:cpu_policy'])
774 if cpu_pinning_policy
is not None:
775 getattr(flavor
, 'guest_epa').cpu_pinning_policy
= cpu_pinning_policy
777 elif attr
== 'hw:cpu_threads_policy':
778 cpu_thread_pinning_policy
= espec_utils
.guest
.extra_spec_to_mano_cpu_thread_pinning_policy(flavor_info
['extra_specs']['hw:cpu_threads_policy'])
779 if cpu_thread_pinning_policy
is not None:
780 getattr(flavor
, 'guest_epa').cpu_thread_pinning_policy
= cpu_thread_pinning_policy
782 elif attr
== 'hw:mem_page_size':
783 mempage_size
= espec_utils
.guest
.extra_spec_to_mano_mempage_size(flavor_info
['extra_specs']['hw:mem_page_size'])
784 if mempage_size
is not None:
785 getattr(flavor
, 'guest_epa').mempage_size
= mempage_size
788 elif attr
== 'hw:numa_nodes':
789 numa_node_count
= espec_utils
.guest
.extra_specs_to_mano_numa_node_count(flavor_info
['extra_specs']['hw:numa_nodes'])
790 if numa_node_count
is not None:
791 getattr(flavor
,'guest_epa').numa_node_policy
.node_cnt
= numa_node_count
793 elif attr
.startswith('hw:numa_cpus.'):
794 node_id
= attr
.split('.')[1]
795 nodes
= [ n
for n
in flavor
.guest_epa
.numa_node_policy
.node
if n
.id == int(node_id
) ]
799 numa_node
= getattr(flavor
,'guest_epa').numa_node_policy
.node
.add()
800 numa_node
.id = int(node_id
)
802 for x
in flavor_info
['extra_specs'][attr
].split(','):
803 numa_node_vcpu
= numa_node
.vcpu
.add()
804 numa_node_vcpu
.id = int(x
)
806 elif attr
.startswith('hw:numa_mem.'):
807 node_id
= attr
.split('.')[1]
808 nodes
= [ n
for n
in flavor
.guest_epa
.numa_node_policy
.node
if n
.id == int(node_id
) ]
812 numa_node
= getattr(flavor
,'guest_epa').numa_node_policy
.node
.add()
813 numa_node
.id = int(node_id
)
815 numa_node
.memory_mb
= int(flavor_info
['extra_specs'][attr
])
817 elif attr
== 'hw:numa_mempolicy':
818 numa_memory_policy
= espec_utils
.guest
.extra_to_mano_spec_numa_memory_policy(flavor_info
['extra_specs']['hw:numa_mempolicy'])
819 if numa_memory_policy
is not None:
820 getattr(flavor
,'guest_epa').numa_node_policy
.mem_policy
= numa_memory_policy
822 elif attr
== 'trust:trusted_host':
823 trusted_execution
= espec_utils
.guest
.extra_spec_to_mano_trusted_execution(flavor_info
['extra_specs']['trust:trusted_host'])
824 if trusted_execution
is not None:
825 getattr(flavor
,'guest_epa').trusted_execution
= trusted_execution
827 elif attr
== 'pci_passthrough:alias':
828 device_types
= flavor_info
['extra_specs']['pci_passthrough:alias']
829 for device
in device_types
.split(','):
830 dev
= getattr(flavor
,'guest_epa').pcie_device
.add()
831 dev
.device_id
= device
.split(':')[0]
832 dev
.count
= int(device
.split(':')[1])
834 elif attr
== 'capabilities:cpu_info:model':
835 cpu_model
= espec_utils
.host
.extra_specs_to_mano_cpu_model(flavor_info
['extra_specs']['capabilities:cpu_info:model'])
836 if cpu_model
is not None:
837 getattr(flavor
, 'host_epa').cpu_model
= cpu_model
839 elif attr
== 'capabilities:cpu_info:arch':
840 cpu_arch
= espec_utils
.host
.extra_specs_to_mano_cpu_arch(flavor_info
['extra_specs']['capabilities:cpu_info:arch'])
841 if cpu_arch
is not None:
842 getattr(flavor
, 'host_epa').cpu_arch
= cpu_arch
844 elif attr
== 'capabilities:cpu_info:vendor':
845 cpu_vendor
= espec_utils
.host
.extra_spec_to_mano_cpu_vendor(flavor_info
['extra_specs']['capabilities:cpu_info:vendor'])
846 if cpu_vendor
is not None:
847 getattr(flavor
, 'host_epa').cpu_vendor
= cpu_vendor
849 elif attr
== 'capabilities:cpu_info:topology:sockets':
850 cpu_sockets
= espec_utils
.host
.extra_spec_to_mano_cpu_socket_count(flavor_info
['extra_specs']['capabilities:cpu_info:topology:sockets'])
851 if cpu_sockets
is not None:
852 getattr(flavor
, 'host_epa').cpu_socket_count
= cpu_sockets
854 elif attr
== 'capabilities:cpu_info:topology:cores':
855 cpu_cores
= espec_utils
.host
.extra_spec_to_mano_cpu_core_count(flavor_info
['extra_specs']['capabilities:cpu_info:topology:cores'])
856 if cpu_cores
is not None:
857 getattr(flavor
, 'host_epa').cpu_core_count
= cpu_cores
859 elif attr
== 'capabilities:cpu_info:topology:threads':
860 cpu_threads
= espec_utils
.host
.extra_spec_to_mano_cpu_core_thread_count(flavor_info
['extra_specs']['capabilities:cpu_info:topology:threads'])
861 if cpu_threads
is not None:
862 getattr(flavor
, 'host_epa').cpu_core_thread_count
= cpu_threads
864 elif attr
== 'capabilities:cpu_info:features':
865 cpu_features
= espec_utils
.host
.extra_spec_to_mano_cpu_features(flavor_info
['extra_specs']['capabilities:cpu_info:features'])
866 if cpu_features
is not None:
867 for feature
in cpu_features
:
868 getattr(flavor
, 'host_epa').cpu_feature
.append(feature
)
869 elif attr
.startswith('aggregate_instance_extra_specs:'):
870 aggregate
= getattr(flavor
, 'host_aggregate').add()
871 aggregate
.metadata_key
= ":".join(attr
.split(':')[1::])
872 aggregate
.metadata_value
= flavor_info
['extra_specs'][attr
]
875 def _fill_flavor_info(flavor_info
):
876 """Create a GI object from flavor info dictionary
878 Converts Flavor information dictionary object returned by openstack
879 driver into Protobuf Gi Object
882 flavor_info: Flavor information from openstack
885 Object of class FlavorInfoItem
887 flavor
= RwcalYang
.FlavorInfoItem()
888 flavor
.name
= flavor_info
['name']
889 flavor
.id = flavor_info
['id']
890 RwcalOpenstackPlugin
._fill
_epa
_attributes
(flavor
, flavor_info
)
894 @rwstatus(ret_on_failure
=[[]])
895 def do_get_flavor_list(self
, account
):
896 """Return flavor information.
899 account - a cloud account
904 response
= RwcalYang
.VimResources()
905 with self
._use
_driver
(account
) as drv
:
906 flavors
= drv
.nova_flavor_list()
908 response
.flavorinfo_list
.append(RwcalOpenstackPlugin
._fill
_flavor
_info
(flv
))
911 @rwstatus(ret_on_failure
=[None])
912 def do_get_flavor(self
, account
, id):
913 """Return flavor information.
916 account - a cloud account
917 id - an id for the flavor
922 with self
._use
_driver
(account
) as drv
:
923 flavor
= drv
.nova_flavor_get(id)
924 return RwcalOpenstackPlugin
._fill
_flavor
_info
(flavor
)
927 def _fill_network_info(self
, network_info
, account
):
928 """Create a GI object from network info dictionary
930 Converts Network information dictionary object returned by openstack
931 driver into Protobuf Gi Object
934 network_info - Network information from openstack
935 account - a cloud account
940 network
= RwcalYang
.NetworkInfoItem()
941 network
.network_name
= network_info
['name']
942 network
.network_id
= network_info
['id']
943 if ('provider:network_type' in network_info
) and (network_info
['provider:network_type'] != None):
944 network
.provider_network
.overlay_type
= network_info
['provider:network_type'].upper()
945 if ('provider:segmentation_id' in network_info
) and (network_info
['provider:segmentation_id']):
946 network
.provider_network
.segmentation_id
= network_info
['provider:segmentation_id']
947 if ('provider:physical_network' in network_info
) and (network_info
['provider:physical_network']):
948 network
.provider_network
.physical_network
= network_info
['provider:physical_network'].upper()
950 if 'subnets' in network_info
and network_info
['subnets']:
951 subnet_id
= network_info
['subnets'][0]
952 with self
._use
_driver
(account
) as drv
:
953 subnet
= drv
.neutron_subnet_get(subnet_id
)
954 network
.subnet
= subnet
['cidr']
957 @rwstatus(ret_on_failure
=[[]])
958 def do_get_network_list(self
, account
):
959 """Return a list of networks
962 account - a cloud account
967 response
= RwcalYang
.VimResources()
968 with self
._use
_driver
(account
) as drv
:
969 networks
= drv
.neutron_network_list()
970 for network
in networks
:
971 response
.networkinfo_list
.append(self
._fill
_network
_info
(network
, account
))
974 @rwstatus(ret_on_failure
=[None])
975 def do_get_network(self
, account
, id):
979 account - a cloud account
980 id - an id for the network
985 with self
._use
_driver
(account
) as drv
:
986 network
= drv
.neutron_network_get(id)
987 return self
._fill
_network
_info
(network
, account
)
989 @rwstatus(ret_on_failure
=[""])
990 def do_create_network(self
, account
, network
):
991 """Create a new network
994 account - a cloud account
995 network - Network object
1001 kwargs
['name'] = network
.network_name
1002 kwargs
['admin_state_up'] = True
1003 kwargs
['external_router'] = False
1004 kwargs
['shared'] = False
1006 if network
.has_field('provider_network'):
1007 if network
.provider_network
.has_field('physical_network'):
1008 kwargs
['physical_network'] = network
.provider_network
.physical_network
1009 if network
.provider_network
.has_field('overlay_type'):
1010 kwargs
['network_type'] = network
.provider_network
.overlay_type
.lower()
1011 if network
.provider_network
.has_field('segmentation_id'):
1012 kwargs
['segmentation_id'] = network
.provider_network
.segmentation_id
1014 with self
._use
_driver
(account
) as drv
:
1015 network_id
= drv
.neutron_network_create(**kwargs
)
1016 drv
.neutron_subnet_create(network_id
= network_id
,
1017 cidr
= network
.subnet
)
1021 def do_delete_network(self
, account
, network_id
):
1025 account - a cloud account
1026 network_id - an id for the network
1028 with self
._use
_driver
(account
) as drv
:
1029 drv
.neutron_network_delete(network_id
)
1032 def _fill_port_info(port_info
):
1033 """Create a GI object from port info dictionary
1035 Converts Port information dictionary object returned by openstack
1036 driver into Protobuf Gi Object
1039 port_info - Port information from openstack
1044 port
= RwcalYang
.PortInfoItem()
1046 port
.port_name
= port_info
['name']
1047 port
.port_id
= port_info
['id']
1048 port
.network_id
= port_info
['network_id']
1049 port
.port_state
= port_info
['status']
1050 if 'device_id' in port_info
:
1051 port
.vm_id
= port_info
['device_id']
1052 if 'fixed_ips' in port_info
:
1053 port
.ip_address
= port_info
['fixed_ips'][0]['ip_address']
1056 @rwstatus(ret_on_failure
=[None])
1057 def do_get_port(self
, account
, port_id
):
1061 account - a cloud account
1062 port_id - an id for the port
1067 with self
._use
_driver
(account
) as drv
:
1068 port
= drv
.neutron_port_get(port_id
)
1070 return RwcalOpenstackPlugin
._fill
_port
_info
(port
)
1072 @rwstatus(ret_on_failure
=[[]])
1073 def do_get_port_list(self
, account
):
1074 """Return a list of ports
1077 account - a cloud account
1082 response
= RwcalYang
.VimResources()
1083 with self
._use
_driver
(account
) as drv
:
1084 ports
= drv
.neutron_port_list(*{})
1086 response
.portinfo_list
.append(RwcalOpenstackPlugin
._fill
_port
_info
(port
))
1089 @rwstatus(ret_on_failure
=[""])
1090 def do_create_port(self
, account
, port
):
1091 """Create a new port
1094 account - a cloud account
1101 kwargs
['name'] = port
.port_name
1102 kwargs
['network_id'] = port
.network_id
1103 kwargs
['admin_state_up'] = True
1104 if port
.has_field('vm_id'):
1105 kwargs
['vm_id'] = port
.vm_id
1106 if port
.has_field('port_type'):
1107 kwargs
['port_type'] = port
.port_type
1109 kwargs
['port_type'] = "normal"
1111 with self
._use
_driver
(account
) as drv
:
1112 return drv
.neutron_port_create(**kwargs
)
1115 def do_delete_port(self
, account
, port_id
):
1119 account - a cloud account
1120 port_id - an id for port
1122 with self
._use
_driver
(account
) as drv
:
1123 drv
.neutron_port_delete(port_id
)
1125 @rwstatus(ret_on_failure
=[""])
1126 def do_add_host(self
, account
, host
):
1130 account - a cloud account
1131 host - a host object
1136 raise NotImplementedError
1139 def do_remove_host(self
, account
, host_id
):
1143 account - a cloud account
1144 host_id - an id for the host
1146 raise NotImplementedError
1148 @rwstatus(ret_on_failure
=[None])
1149 def do_get_host(self
, account
, host_id
):
1153 account - a cloud account
1154 host_id - an id for host
1159 raise NotImplementedError
1161 @rwstatus(ret_on_failure
=[[]])
1162 def do_get_host_list(self
, account
):
1163 """Return a list of hosts
1166 account - a cloud account
1171 raise NotImplementedError
1174 def _fill_connection_point_info(c_point
, port_info
):
1175 """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints()
1177 Converts Port information dictionary object returned by openstack
1178 driver into Protobuf Gi Object
1181 port_info - Port information from openstack
1183 Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints
1185 c_point
.name
= port_info
['name']
1186 c_point
.connection_point_id
= port_info
['id']
1187 if ('fixed_ips' in port_info
) and (len(port_info
['fixed_ips']) >= 1):
1188 if 'ip_address' in port_info
['fixed_ips'][0]:
1189 c_point
.ip_address
= port_info
['fixed_ips'][0]['ip_address']
1190 if 'mac_address' in port_info
:
1191 c_point
.mac_addr
= port_info
['mac_address']
1192 if port_info
['status'] == 'ACTIVE':
1193 c_point
.state
= 'active'
1195 c_point
.state
= 'inactive'
1196 if 'network_id' in port_info
:
1197 c_point
.virtual_link_id
= port_info
['network_id']
1198 if ('device_id' in port_info
) and (port_info
['device_id']):
1199 c_point
.vdu_id
= port_info
['device_id']
1202 def _fill_virtual_link_info(network_info
, port_list
, subnet
):
1203 """Create a GI object for VirtualLinkInfoParams
1205 Converts Network and Port information dictionary object
1206 returned by openstack driver into Protobuf Gi Object
1209 network_info - Network information from openstack
1210 port_list - A list of port information from openstack
1211 subnet: Subnet information from openstack
1213 Protobuf Gi object for VirtualLinkInfoParams
1215 link
= RwcalYang
.VirtualLinkInfoParams()
1216 link
.name
= network_info
['name']
1217 if network_info
['status'] == 'ACTIVE':
1218 link
.state
= 'active'
1220 link
.state
= 'inactive'
1221 link
.virtual_link_id
= network_info
['id']
1222 for port
in port_list
:
1223 if port
['device_owner'] == 'compute:None':
1224 c_point
= link
.connection_points
.add()
1225 RwcalOpenstackPlugin
._fill
_connection
_point
_info
(c_point
, port
)
1228 link
.subnet
= subnet
['cidr']
1230 if ('provider:network_type' in network_info
) and (network_info
['provider:network_type'] != None):
1231 link
.provider_network
.overlay_type
= network_info
['provider:network_type'].upper()
1232 if ('provider:segmentation_id' in network_info
) and (network_info
['provider:segmentation_id']):
1233 link
.provider_network
.segmentation_id
= network_info
['provider:segmentation_id']
1234 if ('provider:physical_network' in network_info
) and (network_info
['provider:physical_network']):
1235 link
.provider_network
.physical_network
= network_info
['provider:physical_network'].upper()
1240 def _fill_vdu_info(drv
, vm_info
, flavor_info
, mgmt_network
, port_list
, server_group
, volume_list
= None):
1241 """Create a GI object for VDUInfoParams
1243 Converts VM information dictionary object returned by openstack
1244 driver into Protobuf Gi Object
1247 vm_info - VM information from openstack
1248 flavor_info - VM Flavor information from openstack
1249 mgmt_network - Management network
1250 port_list - A list of port information from openstack
1251 server_group - A list (with one element or empty list) of server group to which this VM belongs
1253 Protobuf Gi object for VDUInfoParams
1255 vdu
= RwcalYang
.VDUInfoParams()
1256 vdu
.name
= vm_info
['name']
1257 vdu
.vdu_id
= vm_info
['id']
1258 for network_name
, network_info
in vm_info
['addresses'].items():
1259 if network_info
and network_name
== mgmt_network
:
1260 for interface
in network_info
:
1261 if 'OS-EXT-IPS:type' in interface
:
1262 if interface
['OS-EXT-IPS:type'] == 'fixed':
1263 vdu
.management_ip
= interface
['addr']
1264 elif interface
['OS-EXT-IPS:type'] == 'floating':
1265 vdu
.public_ip
= interface
['addr']
1267 # Look for any metadata
1268 # for key, value in vm_info['metadata'].items():
1269 # if key == 'node_id':
1270 # vdu.node_id = value
1272 # custommetadata = vdu.supplemental_boot_data.custom_meta_data.add()
1273 # custommetadata.name = key
1274 # custommetadata.value = str(value)
1276 # Look for config_drive
1277 if ('config_drive' in vm_info
):
1278 vdu
.supplemental_boot_data
.boot_data_drive
= vm_info
['config_drive']
1279 if ('image' in vm_info
) and ('id' in vm_info
['image']):
1280 vdu
.image_id
= vm_info
['image']['id']
1281 if ('flavor' in vm_info
) and ('id' in vm_info
['flavor']):
1282 vdu
.flavor_id
= vm_info
['flavor']['id']
1284 if vm_info
['status'] == 'ACTIVE':
1285 vdu
.state
= 'active'
1286 elif vm_info
['status'] == 'ERROR':
1287 vdu
.state
= 'failed'
1289 vdu
.state
= 'inactive'
1291 if 'availability_zone' in vm_info
:
1292 vdu
.availability_zone
= vm_info
['availability_zone']
1295 vdu
.server_group
.name
= server_group
[0]
1297 vdu
.cloud_type
= 'openstack'
1298 # Fill the port information
1299 for port
in port_list
:
1300 c_point
= vdu
.connection_points
.add()
1301 RwcalOpenstackPlugin
._fill
_connection
_point
_info
(c_point
, port
)
1303 if flavor_info
is not None:
1304 RwcalOpenstackPlugin
._fill
_epa
_attributes
(vdu
, flavor_info
)
1306 # Fill the volume information
1307 if volume_list
is not None:
1308 for os_volume
in volume_list
:
1309 volr
= vdu
.volumes
.add()
1311 " Device name is of format /dev/vda"
1312 vol_name
= (os_volume
['device']).split('/')[2]
1315 volr
.name
= vol_name
1316 volr
.volume_id
= os_volume
['volumeId']
1318 vol_details
= drv
.cinder_volume_get(volr
.volume_id
)
1321 if vol_details
is None:
1323 for key
, value
in vol_details
.metadata
.items():
1324 volmd
= volr
.custom_meta_data
.add()
1330 @rwcalstatus(ret_on_failure
=[""])
1331 def do_create_virtual_link(self
, account
, link_params
):
1332 """Create a new virtual link
1335 account - a cloud account
1336 link_params - information that defines the type of VDU to create
1342 kwargs
['name'] = link_params
.name
1343 kwargs
['admin_state_up'] = True
1344 kwargs
['external_router'] = False
1345 kwargs
['shared'] = False
1347 if link_params
.has_field('provider_network'):
1348 if link_params
.provider_network
.has_field('physical_network'):
1349 kwargs
['physical_network'] = link_params
.provider_network
.physical_network
1350 if link_params
.provider_network
.has_field('overlay_type'):
1351 kwargs
['network_type'] = link_params
.provider_network
.overlay_type
.lower()
1352 if link_params
.provider_network
.has_field('segmentation_id'):
1353 kwargs
['segmentation_id'] = link_params
.provider_network
.segmentation_id
1356 with self
._use
_driver
(account
) as drv
:
1358 network_id
= drv
.neutron_network_create(**kwargs
)
1359 except Exception as e
:
1360 self
.log
.error("Encountered exceptions during network creation. Exception: %s", str(e
))
1363 kwargs
= {'network_id' : network_id
,
1364 'dhcp_params': {'enable_dhcp': True},
1365 'gateway_ip' : None,}
1367 if link_params
.ip_profile_params
.has_field('ip_version'):
1368 kwargs
['ip_version'] = 6 if link_params
.ip_profile_params
.ip_version
== 'ipv6' else 4
1370 kwargs
['ip_version'] = 4
1372 if link_params
.ip_profile_params
.has_field('subnet_address'):
1373 kwargs
['cidr'] = link_params
.ip_profile_params
.subnet_address
1374 elif link_params
.ip_profile_params
.has_field('subnet_prefix_pool'):
1375 subnet_pool
= drv
.netruon_subnetpool_by_name(link_params
.ip_profile_params
.subnet_prefix_pool
)
1376 if subnet_pool
is None:
1377 self
.log
.error("Could not find subnet pool with name :%s to be used for network: %s",
1378 link_params
.ip_profile_params
.subnet_prefix_pool
,
1380 raise NeutronException
.NotFound("SubnetPool with name %s not found"%(link_params
.ip_profile_params
.subnet_prefix_pool
))
1382 kwargs
['subnetpool_id'] = subnet_pool
['id']
1383 elif link_params
.has_field('subnet'):
1384 kwargs
['cidr'] = link_params
.subnet
1386 assert 0, "No IP Prefix or Pool name specified"
1388 if link_params
.ip_profile_params
.has_field('dhcp_params'):
1389 if link_params
.ip_profile_params
.dhcp_params
.has_field('enabled'):
1390 kwargs
['dhcp_params']['enable_dhcp'] = link_params
.ip_profile_params
.dhcp_params
.enabled
1391 if link_params
.ip_profile_params
.dhcp_params
.has_field('start_address'):
1392 kwargs
['dhcp_params']['start_address'] = link_params
.ip_profile_params
.dhcp_params
.start_address
1393 if link_params
.ip_profile_params
.dhcp_params
.has_field('count'):
1394 kwargs
['dhcp_params']['count'] = link_params
.ip_profile_params
.dhcp_params
.count
1396 if link_params
.ip_profile_params
.has_field('dns_server'):
1397 kwargs
['dns_server'] = []
1398 for server
in link_params
.ip_profile_params
.dns_server
:
1399 kwargs
['dns_server'].append(server
.address
)
1401 if link_params
.ip_profile_params
.has_field('gateway_address'):
1402 kwargs
['gateway_ip'] = link_params
.ip_profile_params
.gateway_address
1404 drv
.neutron_subnet_create(**kwargs
)
1410 def do_delete_virtual_link(self
, account
, link_id
):
1411 """Delete a virtual link
1414 account - a cloud account
1415 link_id - id for the virtual-link to be deleted
1421 self
.log
.error("Empty link_id during the virtual link deletion")
1422 raise Exception("Empty link_id during the virtual link deletion")
1424 with self
._use
_driver
(account
) as drv
:
1425 port_list
= drv
.neutron_port_list(**{'network_id': link_id
})
1427 for port
in port_list
:
1428 if ((port
['device_owner'] == 'compute:None') or (port
['device_owner'] == '')):
1429 self
.do_delete_port(account
, port
['id'], no_rwstatus
=True)
1430 self
.do_delete_network(account
, link_id
, no_rwstatus
=True)
1432 @rwstatus(ret_on_failure
=[None])
1433 def do_get_virtual_link(self
, account
, link_id
):
1434 """Get information about virtual link.
1437 account - a cloud account
1438 link_id - id for the virtual-link
1441 Object of type RwcalYang.VirtualLinkInfoParams
1444 self
.log
.error("Empty link_id during the virtual link get request")
1445 raise Exception("Empty link_id during the virtual link get request")
1447 with self
._use
_driver
(account
) as drv
:
1448 network
= drv
.neutron_network_get(link_id
)
1450 port_list
= drv
.neutron_port_list(**{'network_id': network
['id']})
1451 if 'subnets' in network
:
1452 subnet
= drv
.neutron_subnet_get(network
['subnets'][0])
1455 virtual_link
= RwcalOpenstackPlugin
._fill
_virtual
_link
_info
(network
, port_list
, subnet
)
1460 @rwstatus(ret_on_failure
=[None])
1461 def do_get_virtual_link_list(self
, account
):
1462 """Get information about all the virtual links
1465 account - a cloud account
1468 A list of objects of type RwcalYang.VirtualLinkInfoParams
1470 vnf_resources
= RwcalYang
.VNFResources()
1471 with self
._use
_driver
(account
) as drv
:
1472 networks
= drv
.neutron_network_list()
1473 for network
in networks
:
1474 port_list
= drv
.neutron_port_list(**{'network_id': network
['id']})
1475 if ('subnets' in network
) and (network
['subnets']):
1476 subnet
= drv
.neutron_subnet_get(network
['subnets'][0])
1479 virtual_link
= RwcalOpenstackPlugin
._fill
_virtual
_link
_info
(network
, port_list
, subnet
)
1480 vnf_resources
.virtual_link_info_list
.append(virtual_link
)
1481 return vnf_resources
1483 def _create_connection_point(self
, account
, c_point
):
1485 Create a connection point
1487 account - a cloud account
1488 c_point - connection_points
1491 kwargs
['name'] = c_point
.name
1492 kwargs
['network_id'] = c_point
.virtual_link_id
1493 kwargs
['admin_state_up'] = True
1495 if c_point
.type_yang
== 'VIRTIO' or c_point
.type_yang
== 'E1000':
1496 kwargs
['port_type'] = 'normal'
1497 elif c_point
.type_yang
== 'SR_IOV':
1498 kwargs
['port_type'] = 'direct'
1500 raise NotImplementedError("Port Type: %s not supported" %(c_point
.type_yang
))
1502 # By default port gets created with post_security enaled as True
1503 if 'port_security_enabled' in c_point
:
1504 kwargs
['port_security_enabled'] = c_point
.port_security_enabled
1506 with self
._use
_driver
(account
) as drv
:
1507 if c_point
.has_field('security_group'):
1508 group
= drv
.neutron_security_group_by_name(c_point
.security_group
)
1509 if group
is not None:
1510 kwargs
['security_groups'] = [group
['id']]
1511 return drv
.neutron_port_create(**kwargs
)
1513 def _allocate_floating_ip(self
, drv
, pool_name
):
1515 Allocate a floating_ip. If unused floating_ip exists then its reused.
1517 drv: OpenstackDriver instance
1518 pool_name: Floating IP pool name
1521 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1524 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1526 # if pool_name is not None:
1527 # ### Filter further based on IP address
1528 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1530 # if not available_ip:
1531 # floating_ip = drv.nova_floating_ip_create(pool_name)
1533 # floating_ip = available_ip[0]
1535 floating_ip
= drv
.nova_floating_ip_create(pool_name
)
1538 def _match_vm_flavor(self
, required
, available
):
1539 self
.log
.info("Matching VM Flavor attributes")
1540 if available
.vcpu_count
!= required
.vcpu_count
:
1541 self
.log
.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1542 required
.vcpu_count
,
1543 available
.vcpu_count
)
1545 if available
.memory_mb
!= required
.memory_mb
:
1546 self
.log
.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1548 available
.memory_mb
)
1550 if available
.storage_gb
!= required
.storage_gb
:
1551 self
.log
.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1552 required
.storage_gb
,
1553 available
.storage_gb
)
1555 self
.log
.debug("VM Flavor match found")
1558 def _match_guest_epa(self
, required
, available
):
1559 self
.log
.info("Matching Guest EPA attributes")
1560 if required
.has_field('pcie_device'):
1561 self
.log
.debug("Matching pcie_device")
1562 if available
.has_field('pcie_device') == False:
1563 self
.log
.debug("Matching pcie_device failed. Not available in flavor")
1566 for dev
in required
.pcie_device
:
1567 if not [ d
for d
in available
.pcie_device
1568 if ((d
.device_id
== dev
.device_id
) and (d
.count
== dev
.count
)) ]:
1569 self
.log
.debug("Matching pcie_device failed. Required: %s, Available: %s", required
.pcie_device
, available
.pcie_device
)
1571 elif available
.has_field('pcie_device'):
1572 self
.log
.debug("Rejecting available flavor because pcie_device not required but available")
1576 if required
.has_field('mempage_size'):
1577 self
.log
.debug("Matching mempage_size")
1578 if available
.has_field('mempage_size') == False:
1579 self
.log
.debug("Matching mempage_size failed. Not available in flavor")
1582 if required
.mempage_size
!= available
.mempage_size
:
1583 self
.log
.debug("Matching mempage_size failed. Required: %s, Available: %s", required
.mempage_size
, available
.mempage_size
)
1585 elif available
.has_field('mempage_size'):
1586 self
.log
.debug("Rejecting available flavor because mempage_size not required but available")
1589 if required
.has_field('cpu_pinning_policy'):
1590 self
.log
.debug("Matching cpu_pinning_policy")
1591 if required
.cpu_pinning_policy
!= 'ANY':
1592 if available
.has_field('cpu_pinning_policy') == False:
1593 self
.log
.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1596 if required
.cpu_pinning_policy
!= available
.cpu_pinning_policy
:
1597 self
.log
.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required
.cpu_pinning_policy
, available
.cpu_pinning_policy
)
1599 elif available
.has_field('cpu_pinning_policy'):
1600 self
.log
.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1603 if required
.has_field('cpu_thread_pinning_policy'):
1604 self
.log
.debug("Matching cpu_thread_pinning_policy")
1605 if available
.has_field('cpu_thread_pinning_policy') == False:
1606 self
.log
.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1609 if required
.cpu_thread_pinning_policy
!= available
.cpu_thread_pinning_policy
:
1610 self
.log
.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required
.cpu_thread_pinning_policy
, available
.cpu_thread_pinning_policy
)
1612 elif available
.has_field('cpu_thread_pinning_policy'):
1613 self
.log
.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1616 if required
.has_field('trusted_execution'):
1617 self
.log
.debug("Matching trusted_execution")
1618 if required
.trusted_execution
== True:
1619 if available
.has_field('trusted_execution') == False:
1620 self
.log
.debug("Matching trusted_execution failed. Not available in flavor")
1623 if required
.trusted_execution
!= available
.trusted_execution
:
1624 self
.log
.debug("Matching trusted_execution failed. Required: %s, Available: %s", required
.trusted_execution
, available
.trusted_execution
)
1626 elif available
.has_field('trusted_execution'):
1627 self
.log
.debug("Rejecting available flavor because trusted_execution not required but available")
1630 if required
.has_field('numa_node_policy'):
1631 self
.log
.debug("Matching numa_node_policy")
1632 if available
.has_field('numa_node_policy') == False:
1633 self
.log
.debug("Matching numa_node_policy failed. Not available in flavor")
1636 if required
.numa_node_policy
.has_field('node_cnt'):
1637 self
.log
.debug("Matching numa_node_policy node_cnt")
1638 if available
.numa_node_policy
.has_field('node_cnt') == False:
1639 self
.log
.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1642 if required
.numa_node_policy
.node_cnt
!= available
.numa_node_policy
.node_cnt
:
1643 self
.log
.debug("Matching numa_node_policy node_cnt failed. Required: %s, Available: %s",required
.numa_node_policy
.node_cnt
, available
.numa_node_policy
.node_cnt
)
1645 elif available
.numa_node_policy
.has_field('node_cnt'):
1646 self
.log
.debug("Rejecting available flavor because numa node count not required but available")
1649 if required
.numa_node_policy
.has_field('mem_policy'):
1650 self
.log
.debug("Matching numa_node_policy mem_policy")
1651 if available
.numa_node_policy
.has_field('mem_policy') == False:
1652 self
.log
.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1655 if required
.numa_node_policy
.mem_policy
!= available
.numa_node_policy
.mem_policy
:
1656 self
.log
.debug("Matching numa_node_policy mem_policy failed. Required: %s, Available: %s", required
.numa_node_policy
.mem_policy
, available
.numa_node_policy
.mem_policy
)
1658 elif available
.numa_node_policy
.has_field('mem_policy'):
1659 self
.log
.debug("Rejecting available flavor because num node mem_policy not required but available")
1662 if required
.numa_node_policy
.has_field('node'):
1663 self
.log
.debug("Matching numa_node_policy nodes configuration")
1664 if available
.numa_node_policy
.has_field('node') == False:
1665 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1667 for required_node
in required
.numa_node_policy
.node
:
1668 self
.log
.debug("Matching numa_node_policy nodes configuration for node %s", required_node
)
1670 for available_node
in available
.numa_node_policy
.node
:
1671 if required_node
.id != available_node
.id:
1672 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node
, available_node
)
1674 if required_node
.vcpu
!= available_node
.vcpu
:
1675 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node
, available_node
)
1677 if required_node
.memory_mb
!= available_node
.memory_mb
:
1678 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node
, available_node
)
1681 if numa_match
== False:
1683 elif available
.numa_node_policy
.has_field('node'):
1684 self
.log
.debug("Rejecting available flavor because numa nodes not required but available")
1686 elif available
.has_field('numa_node_policy'):
1687 self
.log
.debug("Rejecting available flavor because numa_node_policy not required but available")
1689 self
.log
.info("Successful match for Guest EPA attributes")
1692 def _match_vswitch_epa(self
, required
, available
):
1693 self
.log
.debug("VSwitch EPA match found")
1696 def _match_hypervisor_epa(self
, required
, available
):
1697 self
.log
.debug("Hypervisor EPA match found")
1700 def _match_host_epa(self
, required
, available
):
1701 self
.log
.info("Matching Host EPA attributes")
1702 if required
.has_field('cpu_model'):
1703 self
.log
.debug("Matching CPU model")
1704 if available
.has_field('cpu_model') == False:
1705 self
.log
.debug("Matching CPU model failed. Not available in flavor")
1708 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1709 if required
.cpu_model
.replace('PREFER', 'REQUIRE') != available
.cpu_model
:
1710 self
.log
.debug("Matching CPU model failed. Required: %s, Available: %s", required
.cpu_model
, available
.cpu_model
)
1712 elif available
.has_field('cpu_model'):
1713 self
.log
.debug("Rejecting available flavor because cpu_model not required but available")
1716 if required
.has_field('cpu_arch'):
1717 self
.log
.debug("Matching CPU architecture")
1718 if available
.has_field('cpu_arch') == False:
1719 self
.log
.debug("Matching CPU architecture failed. Not available in flavor")
1722 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1723 if required
.cpu_arch
.replace('PREFER', 'REQUIRE') != available
.cpu_arch
:
1724 self
.log
.debug("Matching CPU architecture failed. Required: %s, Available: %s", required
.cpu_arch
, available
.cpu_arch
)
1726 elif available
.has_field('cpu_arch'):
1727 self
.log
.debug("Rejecting available flavor because cpu_arch not required but available")
1730 if required
.has_field('cpu_vendor'):
1731 self
.log
.debug("Matching CPU vendor")
1732 if available
.has_field('cpu_vendor') == False:
1733 self
.log
.debug("Matching CPU vendor failed. Not available in flavor")
1736 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1737 if required
.cpu_vendor
.replace('PREFER', 'REQUIRE') != available
.cpu_vendor
:
1738 self
.log
.debug("Matching CPU vendor failed. Required: %s, Available: %s", required
.cpu_vendor
, available
.cpu_vendor
)
1740 elif available
.has_field('cpu_vendor'):
1741 self
.log
.debug("Rejecting available flavor because cpu_vendor not required but available")
1744 if required
.has_field('cpu_socket_count'):
1745 self
.log
.debug("Matching CPU socket count")
1746 if available
.has_field('cpu_socket_count') == False:
1747 self
.log
.debug("Matching CPU socket count failed. Not available in flavor")
1750 if required
.cpu_socket_count
!= available
.cpu_socket_count
:
1751 self
.log
.debug("Matching CPU socket count failed. Required: %s, Available: %s", required
.cpu_socket_count
, available
.cpu_socket_count
)
1753 elif available
.has_field('cpu_socket_count'):
1754 self
.log
.debug("Rejecting available flavor because cpu_socket_count not required but available")
1757 if required
.has_field('cpu_core_count'):
1758 self
.log
.debug("Matching CPU core count")
1759 if available
.has_field('cpu_core_count') == False:
1760 self
.log
.debug("Matching CPU core count failed. Not available in flavor")
1763 if required
.cpu_core_count
!= available
.cpu_core_count
:
1764 self
.log
.debug("Matching CPU core count failed. Required: %s, Available: %s", required
.cpu_core_count
, available
.cpu_core_count
)
1766 elif available
.has_field('cpu_core_count'):
1767 self
.log
.debug("Rejecting available flavor because cpu_core_count not required but available")
1770 if required
.has_field('cpu_core_thread_count'):
1771 self
.log
.debug("Matching CPU core thread count")
1772 if available
.has_field('cpu_core_thread_count') == False:
1773 self
.log
.debug("Matching CPU core thread count failed. Not available in flavor")
1776 if required
.cpu_core_thread_count
!= available
.cpu_core_thread_count
:
1777 self
.log
.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required
.cpu_core_thread_count
, available
.cpu_core_thread_count
)
1779 elif available
.has_field('cpu_core_thread_count'):
1780 self
.log
.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1783 if required
.has_field('cpu_feature'):
1784 self
.log
.debug("Matching CPU feature list")
1785 if available
.has_field('cpu_feature') == False:
1786 self
.log
.debug("Matching CPU feature list failed. Not available in flavor")
1789 for feature
in required
.cpu_feature
:
1790 if feature
not in available
.cpu_feature
:
1791 self
.log
.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature
, available
.cpu_feature
)
1793 elif available
.has_field('cpu_feature'):
1794 self
.log
.debug("Rejecting available flavor because cpu_feature not required but available")
1796 self
.log
.info("Successful match for Host EPA attributes")
1800 def _match_placement_group_inputs(self
, required
, available
):
1801 self
.log
.info("Matching Host aggregate attributes")
1803 if not required
and not available
:
1804 # Host aggregate not required and not available => success
1805 self
.log
.info("Successful match for Host Aggregate attributes")
1807 if required
and available
:
1808 # Host aggregate requested and available => Do a match and decide
1809 xx
= [ x
.as_dict() for x
in required
]
1810 yy
= [ y
.as_dict() for y
in available
]
1813 self
.log
.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required
, available
)
1815 self
.log
.info("Successful match for Host Aggregate attributes")
1818 # Either of following conditions => Failure
1819 # - Host aggregate required but not available
1820 # - Host aggregate not required but available
1821 self
.log
.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required
, available
)
1824 def match_epa_params(self
, resource_info
, request_params
):
1825 result
= self
._match
_vm
_flavor
(getattr(request_params
, 'vm_flavor'),
1826 getattr(resource_info
, 'vm_flavor'))
1828 self
.log
.debug("VM Flavor mismatched")
1831 result
= self
._match
_guest
_epa
(getattr(request_params
, 'guest_epa'),
1832 getattr(resource_info
, 'guest_epa'))
1834 self
.log
.debug("Guest EPA mismatched")
1837 result
= self
._match
_vswitch
_epa
(getattr(request_params
, 'vswitch_epa'),
1838 getattr(resource_info
, 'vswitch_epa'))
1840 self
.log
.debug("Vswitch EPA mismatched")
1843 result
= self
._match
_hypervisor
_epa
(getattr(request_params
, 'hypervisor_epa'),
1844 getattr(resource_info
, 'hypervisor_epa'))
1846 self
.log
.debug("Hypervisor EPA mismatched")
1849 result
= self
._match
_host
_epa
(getattr(request_params
, 'host_epa'),
1850 getattr(resource_info
, 'host_epa'))
1852 self
.log
.debug("Host EPA mismatched")
1855 result
= self
._match
_placement
_group
_inputs
(getattr(request_params
, 'host_aggregate'),
1856 getattr(resource_info
, 'host_aggregate'))
1859 self
.log
.debug("Host Aggregate mismatched")
1864 def _select_resource_flavor(self
, account
, vdu_init
):
1866 Select a existing flavor if it matches the request or create new flavor
1868 flavor
= RwcalYang
.FlavorInfoItem()
1869 flavor
.name
= str(uuid
.uuid4())
1870 epa_types
= ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1871 epa_dict
= {k
: v
for k
, v
in vdu_init
.as_dict().items() if k
in epa_types
}
1872 flavor
.from_dict(epa_dict
)
1874 rc
, response
= self
.do_get_flavor_list(account
)
1875 if rc
!= RwTypes
.RwStatus
.SUCCESS
:
1876 self
.log
.error("Get-flavor-info-list operation failed for cloud account: %s",
1878 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account
.name
))
1881 flavor_list
= response
.flavorinfo_list
1882 self
.log
.debug("Received %d flavor information from RW.CAL", len(flavor_list
))
1883 for flv
in flavor_list
:
1884 self
.log
.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1886 if self
.match_epa_params(flv
, vdu_init
):
1887 self
.log
.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1888 vdu_init
.name
, flv
.name
, flv
.id)
1891 if account
.openstack
.dynamic_flavor_support
is False:
1892 self
.log
.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init
.name
)
1893 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1895 rc
,flavor_id
= self
.do_create_flavor(account
,flavor
)
1896 if rc
!= RwTypes
.RwStatus
.SUCCESS
:
1897 self
.log
.error("Create-flavor operation failed for cloud account: %s",
1899 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account
.name
))
1902 def _create_vm(self
, account
, vduinfo
, pci_assignement
=None, server_group
=None, port_list
=None, network_list
=None, imageinfo_list
=None):
1903 """Create a new virtual machine.
1906 account - a cloud account
1907 vminfo - information that defines the type of VM to create
1913 kwargs
['name'] = vduinfo
.name
1914 kwargs
['flavor_id'] = vduinfo
.flavor_id
1915 if vduinfo
.has_field('image_id'):
1916 kwargs
['image_id'] = vduinfo
.image_id
1918 kwargs
['image_id'] = ""
1920 with self
._use
_driver
(account
) as drv
:
1921 ### If floating_ip is required and we don't have one, better fail before any further allocation
1924 if vduinfo
.has_field('allocate_public_address') and vduinfo
.allocate_public_address
:
1925 if account
.openstack
.has_field('floating_ip_pool'):
1926 pool_name
= account
.openstack
.floating_ip_pool
1929 if vduinfo
.has_field('vdu_init') and vduinfo
.vdu_init
.has_field('userdata'):
1930 kwargs
['userdata'] = vduinfo
.vdu_init
.userdata
1932 kwargs
['userdata'] = ''
1934 if account
.openstack
.security_groups
:
1935 kwargs
['security_groups'] = account
.openstack
.security_groups
1937 kwargs
['port_list'] = port_list
1938 kwargs
['network_list'] = network_list
1942 config_drive
= False
1943 # Add all metadata related fields
1944 if vduinfo
.has_field('node_id'):
1945 metadata
['node_id'] = vduinfo
.node_id
1946 if pci_assignement
is not None:
1947 metadata
['pci_assignement'] = pci_assignement
1948 if vduinfo
.has_field('supplemental_boot_data'):
1949 if vduinfo
.supplemental_boot_data
.has_field('custom_meta_data'):
1950 for custom_meta_item
in vduinfo
.supplemental_boot_data
.custom_meta_data
:
1951 if custom_meta_item
.data_type
== "STRING":
1952 metadata
[custom_meta_item
.name
] = custom_meta_item
.value
1953 elif custom_meta_item
.data_type
== "JSON":
1954 metadata
[custom_meta_item
.name
] = tornado
.escape
.json_decode(custom_meta_item
.value
)
1956 raise OpenstackCALOperationFailure("Create-vdu operation failed. Unsupported data-type {} for custom-meta-data name {} ".format(custom_meta_item
.data_type
, custom_meta_item
.name
))
1957 if vduinfo
.supplemental_boot_data
.has_field('config_file'):
1958 for custom_config_file
in vduinfo
.supplemental_boot_data
.config_file
:
1959 files
[custom_config_file
.dest
] = custom_config_file
.source
1961 if vduinfo
.supplemental_boot_data
.has_field('boot_data_drive'):
1962 if vduinfo
.supplemental_boot_data
.boot_data_drive
is True:
1965 kwargs
['metadata'] = metadata
1966 kwargs
['files'] = files
1967 kwargs
['config_drive'] = config_drive
1969 if vduinfo
.has_field('availability_zone') and vduinfo
.availability_zone
.has_field('name'):
1970 kwargs
['availability_zone'] = vduinfo
.availability_zone
1972 kwargs
['availability_zone'] = None
1974 if server_group
is not None:
1975 kwargs
['scheduler_hints'] = {'group': server_group
}
1977 kwargs
['scheduler_hints'] = None
1979 kwargs
['block_device_mapping_v2'] = None
1980 vol_metadata
= False
1981 if vduinfo
.has_field('volumes') :
1982 kwargs
['block_device_mapping_v2'] = []
1983 with self
._use
_driver
(account
) as drv
:
1984 # Only support image->volume
1985 for volume
in vduinfo
.volumes
:
1987 block_map
['boot_index'] = volume
.boot_priority
1988 if "image" in volume
:
1989 # Support image->volume
1990 # Match retrived image info with volume based image name and checksum
1991 if volume
.image
is not None:
1992 matching_images
= [img
for img
in imageinfo_list
if img
['name'] == volume
.image
]
1993 if volume
.image_checksum
is not None:
1994 matching_images
= [img
for img
in matching_images
if img
['checksum'] == volume
.image_checksum
]
1995 img_id
= matching_images
[0]['id']
1997 raise OpenstackCALOperationFailure("Create-vdu operation failed. Volume image not found for name {} checksum {}".format(volume
.name
, volume
.checksum
))
1998 block_map
['uuid'] = img_id
1999 block_map
['source_type'] = "image"
2001 block_map
['source_type'] = "blank"
2003 block_map
['device_name'] = volume
.name
2004 block_map
['destination_type'] = "volume"
2005 block_map
['volume_size'] = volume
.size
2006 block_map
['delete_on_termination'] = True
2007 if volume
.has_field('device_type') and volume
.device_type
== 'cdrom':
2008 block_map
['device_type'] = 'cdrom'
2009 if volume
.has_field('device_bus') and volume
.device_bus
== 'ide':
2010 block_map
['disk_bus'] = 'ide'
2011 kwargs
['block_device_mapping_v2'].append(block_map
)
2014 with self
._use
_driver
(account
) as drv
:
2015 vm_id
= drv
.nova_server_create(**kwargs
)
2017 self
.prepare_vdu_on_boot(account
, vm_id
, floating_ip
, pool_name
, vduinfo
.volumes
)
2021 def get_openstack_image_info(self
, account
, image_name
, image_checksum
=None):
2022 self
.log
.debug("Looking up image id for image name %s and checksum %s on cloud account: %s",
2023 image_name
, image_checksum
, account
.name
2027 with self
._use
_driver
(account
) as drv
:
2028 image_list
= drv
.glance_image_list()
2029 matching_images
= [img
for img
in image_list
if img
['name'] == image_name
]
2031 # If the image checksum was filled in then further filter the images by the checksum
2032 if image_checksum
is not None:
2033 matching_images
= [img
for img
in matching_images
if img
['checksum'] == image_checksum
]
2035 self
.log
.warning("Image checksum not provided. Lookup using image name (%s) only.",
2038 if len(matching_images
) == 0:
2039 raise ResMgrCALOperationFailure("Could not find image name {} (using checksum: {}) for cloud account: {}".format(
2040 image_name
, image_checksum
, account
.name
2043 elif len(matching_images
) > 1:
2044 unique_checksums
= {i
.checksum
for i
in matching_images
}
2045 if len(unique_checksums
) > 1:
2046 msg
= ("Too many images with different checksums matched "
2047 "image name of %s for cloud account: %s" % (image_name
, account
.name
))
2048 raise ResMgrCALOperationFailure(msg
)
2050 return matching_images
[0]
2052 @rwcalstatus(ret_on_failure
=[""])
2053 def do_create_vdu(self
, account
, vdu_init
):
2054 """Create a new virtual deployment unit
2057 account - a cloud account
2058 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
2063 ### First create required number of ports aka connection points
2064 # Add the mgmt_ntwk by default.
2065 mgmt_network_id
= None
2066 with self
._use
_driver
(account
) as drv
:
2067 mgmt_network_id
= drv
._mgmt
_network
_id
2072 is_explicit_mgmt_defined
= False
2073 for c_point
in vdu_init
.connection_points
:
2074 # if the user has specified explicit mgmt_network connection point
2075 # then remove the mgmt_network from the VM list
2076 if c_point
.virtual_link_id
== mgmt_network_id
:
2077 is_explicit_mgmt_defined
= True
2078 if c_point
.virtual_link_id
in network_list
:
2079 assert False, "Only one port per network supported. Refer: http://specs.openstack.org/openstack/nova-specs/specs/juno/implemented/nfv-multiple-if-1-net.html"
2081 network_list
.append(c_point
.virtual_link_id
)
2082 port_id
= self
._create
_connection
_point
(account
, c_point
)
2083 port_list
.append(port_id
)
2085 if not vdu_init
.has_field('flavor_id'):
2086 vdu_init
.flavor_id
= self
._select
_resource
_flavor
(account
,vdu_init
)
2088 ### Obtain all images for volumes and perform validations
2089 if vdu_init
.has_field('volumes'):
2090 for volume
in vdu_init
.volumes
:
2091 if "image" in volume
:
2092 image_checksum
= volume
.image_checksum
if volume
.has_field("image_checksum") else None
2093 image_info
= self
.get_openstack_image_info(account
, volume
.image
, image_checksum
)
2094 imageinfo_list
.append(image_info
)
2095 elif vdu_init
.has_field('image_id'):
2096 with self
._use
_driver
(account
) as drv
:
2097 image_info
= drv
.glance_image_get(vdu_init
.image_id
)
2098 imageinfo_list
.append(image_info
)
2100 if not imageinfo_list
:
2101 err_str
= ("VDU has no image information")
2102 self
.log
.error(err_str
)
2103 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2105 ### Check VDU Virtual Interface type and make sure VM with property exists
2106 if vdu_init
.connection_points
:
2107 ### All virtual interfaces need to be of the same type for Openstack Accounts
2108 if not (all(cp
.type_yang
== 'E1000' for cp
in vdu_init
.connection_points
) or all(cp
.type_yang
!= 'E1000' for cp
in vdu_init
.connection_points
)):
2109 ### We have a mix of E1000 & VIRTIO/SR_IPOV virtual interface types in the VDU, abort instantiation.
2110 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO/SR_IOV & E1000."
2112 ## It is not clear if all the images need to checked for HW properties. In the absence of model info describing each im age's properties,
2113 ### we shall assume that all images need to have similar properties
2114 for img_info
in imageinfo_list
:
2116 virt_intf_type
= vdu_init
.connection_points
[0].type_yang
2117 if virt_intf_type
== 'E1000':
2118 if 'hw_vif_model' in img_info
and img_info
.hw_vif_model
== 'e1000':
2119 self
.log
.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
2121 err_str
= ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info
.name
)
2122 self
.log
.error(err_str
)
2123 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2124 elif virt_intf_type
== 'VIRTIO' or virt_intf_type
== 'SR_IOV':
2125 if 'hw_vif_model' in img_info
:
2126 err_str
= ("VDU has Virtual Interface %s, but image '%s' has hw_vif_model mismatch" % virt_intf_type
,img_info
.name
)
2127 self
.log
.error(err_str
)
2128 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2130 self
.log
.debug("VDU has Virtual Interface %s, found matching image" % virt_intf_type
)
2132 err_str
= ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type
)
2133 self
.log
.error(err_str
)
2134 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2136 with self
._use
_driver
(account
) as drv
:
2138 vm_network_list
= []
2139 if not is_explicit_mgmt_defined
:
2140 vm_network_list
.append(drv
._mgmt
_network
_id
)
2142 if vdu_init
.has_field('volumes'):
2143 # Only combination supported: Image->Volume
2144 for volume
in vdu_init
.volumes
:
2145 if "volume" in volume
:
2146 err_str
= ("VDU Volume source not supported yet")
2147 self
.log
.error(err_str
)
2148 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2149 if not volume
.has_field('device_type'):
2150 err_str
= ("VDU Volume destination type not defined")
2151 self
.log
.error(err_str
)
2152 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2153 if volume
.device_type
not in ['disk', 'cdrom'] :
2154 err_str
= ("VDU Volume destination type '%s' not supported" % volume
.device_type
)
2155 self
.log
.error(err_str
)
2156 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2160 if vdu_init
.has_field('server_group'):
2161 ### Get list of server group in openstack for name->id mapping
2162 openstack_group_list
= drv
.nova_server_group_list()
2163 group_id
= [ i
['id'] for i
in openstack_group_list
if i
['name'] == vdu_init
.server_group
.name
]
2164 if len(group_id
) != 1:
2165 raise OpenstackServerGroupError("VM placement failed. Server Group %s not found in openstack. Available groups" %(vdu_init
.server_group
.name
, [i
['name'] for i
in openstack_group_list
]))
2166 server_group
= group_id
[0]
2168 pci_assignement
= self
.prepare_vpci_metadata(drv
, vdu_init
)
2169 if pci_assignement
!= '':
2170 vm
.user_tags
.pci_assignement
= pci_assignement
2172 vm_id
= self
._create
_vm
(account
, vdu_init
, pci_assignement
=pci_assignement
, server_group
=server_group
, port_list
=port_list
, network_list
=vm_network_list
, imageinfo_list
= imageinfo_list
)
2175 def prepare_vpci_metadata(self
, drv
, vdu_init
):
2176 pci_assignement
= ''
2177 ### TEF specific metadata creation for
2182 ### For MGMT interface
2183 if vdu_init
.has_field('mgmt_vpci'):
2184 xx
= 'u\''+ drv
._mgmt
_network
_id
+ '\' :[[u\'' + vdu_init
.mgmt_vpci
+ '\', ' + '\'\']]'
2185 virtio_vpci
.append(xx
)
2187 for c_point
in vdu_init
.connection_points
:
2188 if c_point
.has_field('vpci'):
2189 if c_point
.has_field('vpci') and c_point
.type_yang
== 'VIRTIO':
2190 xx
= 'u\''+c_point
.virtual_link_id
+ '\' :[[u\'' + c_point
.vpci
+ '\', ' + '\'\']]'
2191 virtio_vpci
.append(xx
)
2192 elif c_point
.has_field('vpci') and c_point
.type_yang
== 'SR_IOV':
2193 xx
= '[u\'' + c_point
.vpci
+ '\', ' + '\'\']'
2194 sriov_vpci
.append(xx
)
2197 virtio_meta
+= ','.join(virtio_vpci
)
2200 sriov_meta
= 'u\'VF\': ['
2201 sriov_meta
+= ','.join(sriov_vpci
)
2204 if virtio_meta
!= '':
2205 pci_assignement
+= virtio_meta
2206 pci_assignement
+= ','
2208 if sriov_meta
!= '':
2209 pci_assignement
+= sriov_meta
2211 if pci_assignement
!= '':
2212 pci_assignement
= '{' + pci_assignement
+ '}'
2214 return pci_assignement
2218 def prepare_vdu_on_boot(self
, account
, server_id
, floating_ip
, pool_name
, volumes
=None):
2219 cmd
= PREPARE_VM_CMD
.format(auth_url
= account
.openstack
.auth_url
,
2220 username
= account
.openstack
.key
,
2221 password
= account
.openstack
.secret
,
2222 tenant_name
= account
.openstack
.tenant
,
2223 region
= account
.openstack
.region
,
2224 user_domain
= account
.openstack
.user_domain
,
2225 project_domain
= account
.openstack
.project_domain
,
2226 mgmt_network
= account
.openstack
.mgmt_network
,
2227 server_id
= server_id
)
2229 cmd
+= " --floating_ip"
2231 cmd
+= (" --pool_name " + pool_name
)
2233 vol_metadata
= False
2234 if volumes
is not None:
2235 for volume
in volumes
:
2236 if volume
.has_field('custom_meta_data'):
2240 if vol_metadata
is True:
2242 with tempfile
.NamedTemporaryFile(mode
='w', delete
=False) as tmp_file
:
2244 for volume
in volumes
:
2245 vol_dict
= volume
.as_dict()
2246 vol_list
.append(vol_dict
)
2248 yaml
.dump(vol_list
, tmp_file
)
2249 cmd
+= (" --vol_metadata {}").format(tmp_file
.name
)
2251 exec_path
= 'python3 ' + os
.path
.dirname(openstack_drv
.__file
__)
2252 exec_cmd
= exec_path
+'/'+cmd
2253 self
.log
.info("Running command: %s" %(exec_cmd))
2254 subprocess
.call(exec_cmd
, shell
=True)
2257 def do_modify_vdu(self
, account
, vdu_modify
):
2258 """Modify Properties of existing virtual deployment unit
2261 account - a cloud account
2262 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2264 ### First create required number of ports aka connection points
2267 for c_point
in vdu_modify
.connection_points_add
:
2268 if c_point
.virtual_link_id
in network_list
:
2269 assert False, "Only one port per network supported. Refer: http://specs.openstack.org/openstack/nova-specs/specs/juno/implemented/nfv-multiple-if-1-net.html"
2271 network_list
.append(c_point
.virtual_link_id
)
2272 port_id
= self
._create
_connection
_point
(account
, c_point
)
2273 port_list
.append(port_id
)
2275 ### Now add the ports to VM
2276 for port_id
in port_list
:
2277 with self
._use
_driver
(account
) as drv
:
2278 drv
.nova_server_add_port(vdu_modify
.vdu_id
, port_id
)
2280 ### Delete the requested connection_points
2281 for c_point
in vdu_modify
.connection_points_remove
:
2282 self
.do_delete_port(account
, c_point
.connection_point_id
, no_rwstatus
=True)
2284 if vdu_modify
.has_field('image_id'):
2285 with self
._use
_driver
(account
) as drv
:
2286 drv
.nova_server_rebuild(vdu_modify
.vdu_id
, vdu_modify
.image_id
)
2290 def do_delete_vdu(self
, account
, vdu_id
):
2291 """Delete a virtual deployment unit
2294 account - a cloud account
2295 vdu_id - id for the vdu to be deleted
2301 self
.log
.error("empty vdu_id during the vdu deletion")
2304 with self
._use
_driver
(account
) as drv
:
2305 ### Get list of floating_ips associated with this instance and delete them
2306 floating_ips
= [ f
for f
in drv
.nova_floating_ip_list() if f
.instance_id
== vdu_id
]
2307 for f
in floating_ips
:
2308 drv
.nova_drv
.floating_ip_delete(f
)
2310 ### Get list of port on VM and delete them.
2311 port_list
= drv
.neutron_port_list(**{'device_id': vdu_id
})
2313 for port
in port_list
:
2314 if ((port
['device_owner'] == 'compute:None') or (port
['device_owner'] == '')):
2315 self
.do_delete_port(account
, port
['id'], no_rwstatus
=True)
2317 self
.do_delete_vm(account
, vdu_id
, no_rwstatus
=True)
2320 @rwstatus(ret_on_failure
=[None])
2321 def do_get_vdu(self
, account
, vdu_id
):
2322 """Get information about a virtual deployment unit.
2325 account - a cloud account
2326 vdu_id - id for the vdu
2329 Object of type RwcalYang.VDUInfoParams
2331 with self
._use
_driver
(account
) as drv
:
2332 port_list
= drv
.neutron_port_list(**{'device_id': vdu_id
})
2334 vm
= drv
.nova_server_get(vdu_id
)
2337 if ('flavor' in vm
) and ('id' in vm
['flavor']):
2339 flavor_info
= drv
.nova_flavor_get(vm
['flavor']['id'])
2340 except Exception as e
:
2341 self
.log
.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm
['flavor']['id'], str(e
)))
2343 openstack_group_list
= drv
.nova_server_group_list()
2344 server_group
= [ i
['name'] for i
in openstack_group_list
if vm
['id'] in i
['members']]
2345 openstack_srv_volume_list
= drv
.nova_volume_list(vm
['id'])
2346 vdu_info
= RwcalOpenstackPlugin
._fill
_vdu
_info
(drv
, vm
,
2348 account
.openstack
.mgmt_network
,
2351 volume_list
= openstack_srv_volume_list
)
2352 if vdu_info
.state
== 'active':
2354 console_info
= drv
.nova_server_console(vdu_info
.vdu_id
)
2355 except Exception as e
:
2358 vdu_info
.console_url
= console_info
['console']['url']
2364 @rwstatus(ret_on_failure
=[None])
2365 def do_get_vdu_list(self
, account
):
2366 """Get information about all the virtual deployment units
2369 account - a cloud account
2372 A list of objects of type RwcalYang.VDUInfoParams
2374 vnf_resources
= RwcalYang
.VNFResources()
2375 with self
._use
_driver
(account
) as drv
:
2376 vms
= drv
.nova_server_list()
2378 port_list
= drv
.neutron_port_list(**{'device_id': vm
['id']})
2382 if ('flavor' in vm
) and ('id' in vm
['flavor']):
2384 flavor_info
= drv
.nova_flavor_get(vm
['flavor']['id'])
2385 except Exception as e
:
2386 self
.log
.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm
['flavor']['id'], str(e
)))
2391 openstack_group_list
= drv
.nova_server_group_list()
2392 server_group
= [ i
['name'] for i
in openstack_group_list
if vm
['id'] in i
['members']]
2394 openstack_srv_volume_list
= drv
.nova_volume_list(vm
['id'])
2395 vdu
= RwcalOpenstackPlugin
._fill
_vdu
_info
(drv
, vm
,
2397 account
.openstack
.mgmt_network
,
2400 volume_list
= openstack_srv_volume_list
)
2401 if vdu
.state
== 'active':
2403 console_info
= drv
.nova_server_console(vdu
.vdu_id
)
2404 except Exception as e
:
2407 vdu
.console_url
= console_info
['console']['url']
2409 vnf_resources
.vdu_info_list
.append(vdu
)
2410 return vnf_resources