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} --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 except (KeystoneExceptions
.Unauthorized
, KeystoneExceptions
.AuthorizationFailure
,
100 NeutronException
.NotFound
) as e
:
102 except Exception as e
:
103 self
.log
.error("RwcalOpenstackPlugin: OpenstackDriver init failed. Exception: %s" %(str(e
)))
110 def do_init(self
, rwlog_ctx
):
111 self
._rwlog
_handler
= rwlogger
.RwLogger(
112 category
="rw-cal-log",
113 subcategory
="openstack",
116 self
.log
.addHandler(self
._rwlog
_handler
)
117 self
.log
.propagate
= False
119 @rwstatus(ret_on_failure
=[None])
120 def do_validate_cloud_creds(self
, account
):
122 Validates the cloud account credentials for the specified account.
123 Performs an access to the resources using Keystone API. If creds
124 are not valid, returns an error code & reason string
126 account - a cloud account to validate
129 Validation Code and Details String
131 status
= RwcalYang
.CloudConnectionStatus()
133 with self
._use
_driver
(account
) as drv
:
134 drv
.validate_account_creds()
136 except KeystoneExceptions
.Unauthorized
as e
:
137 self
.log
.error("Invalid credentials given for VIM account %s" %account
.name
)
138 status
.status
= "failure"
139 status
.details
= "Invalid Credentials: %s" % str(e
)
141 except KeystoneExceptions
.AuthorizationFailure
as e
:
142 self
.log
.error("Bad authentication URL given for VIM account %s. Given auth url: %s" % (
143 account
.name
, account
.openstack
.auth_url
))
144 status
.status
= "failure"
145 status
.details
= "Invalid auth url: %s" % str(e
)
147 except NeutronException
.NotFound
as e
:
148 self
.log
.error("Given management network %s could not be found for VIM account %s" % (
149 account
.openstack
.mgmt_network
, account
.name
))
150 status
.status
= "failure"
151 status
.details
= "mgmt network does not exist: %s" % str(e
)
153 except openstack_drv
.ValidationError
as e
:
154 self
.log
.error("RwcalOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e
))
155 status
.status
= "failure"
156 status
.details
= "Invalid Credentials: %s" % str(e
)
158 except Exception as e
:
159 msg
= "RwcalOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e
))
161 status
.status
= "failure"
165 status
.status
= "success"
166 status
.details
= "Connection was successful"
170 @rwstatus(ret_on_failure
=[""])
171 def do_get_management_network(self
, account
):
173 Returns the management network associated with the specified account.
175 account - a cloud account
178 The management network
180 return account
.openstack
.mgmt_network
182 @rwstatus(ret_on_failure
=[""])
183 def do_create_tenant(self
, account
, name
):
184 """Create a new tenant.
187 account - a cloud account
188 name - name of the tenant
193 raise NotImplementedError
196 def do_delete_tenant(self
, account
, tenant_id
):
200 account - a cloud account
201 tenant_id - id of the tenant
203 raise NotImplementedError
205 @rwstatus(ret_on_failure
=[[]])
206 def do_get_tenant_list(self
, account
):
210 account - a cloud account
215 raise NotImplementedError
217 @rwstatus(ret_on_failure
=[""])
218 def do_create_role(self
, account
, name
):
219 """Create a new user.
222 account - a cloud account
223 name - name of the user
228 raise NotImplementedError
231 def do_delete_role(self
, account
, role_id
):
235 account - a cloud account
236 role_id - id of the user
238 raise NotImplementedError
240 @rwstatus(ret_on_failure
=[[]])
241 def do_get_role_list(self
, account
):
245 account - a cloud account
250 raise NotImplementedError
252 @rwstatus(ret_on_failure
=[""])
253 def do_create_image(self
, account
, image
):
257 account - a cloud account
258 image - a description of the image to create
265 # If the use passed in a file descriptor, use that to
267 if image
.has_field("fileno"):
268 new_fileno
= os
.dup(image
.fileno
)
269 hdl
= os
.fdopen(new_fileno
, 'rb')
271 hdl
= open(image
.location
, "rb")
272 except Exception as e
:
273 self
.log
.error("Could not open file for upload. Exception received: %s", str(e
))
278 kwargs
['name'] = image
.name
280 if image
.disk_format
:
281 kwargs
['disk_format'] = image
.disk_format
282 if image
.container_format
:
283 kwargs
['container_format'] = image
.container_format
285 with self
._use
_driver
(account
) as drv
:
287 image_id
= drv
.glance_image_create(**kwargs
)
289 drv
.glance_image_upload(image_id
, fd
)
292 stored_image
= drv
.glance_image_get(image_id
)
293 if stored_image
.checksum
!= image
.checksum
:
294 drv
.glance_image_delete(image_id
=image_id
)
295 raise ImageUploadError(
296 "image checksum did not match (actual: %s, expected: %s). Deleting." %
297 (stored_image
.checksum
, image
.checksum
)
303 def do_delete_image(self
, account
, image_id
):
304 """Delete a vm image.
307 account - a cloud account
308 image_id - id of the image to delete
310 with self
._use
_driver
(account
) as drv
:
311 drv
.glance_image_delete(image_id
=image_id
)
315 def _fill_image_info(img_info
):
316 """Create a GI object from image info dictionary
318 Converts image information dictionary object returned by openstack
319 driver into Protobuf Gi Object
322 account - a cloud account
323 img_info - image information dictionary object from openstack
328 img
= RwcalYang
.ImageInfoItem()
329 img
.name
= img_info
['name']
330 img
.id = img_info
['id']
331 img
.checksum
= img_info
['checksum']
332 img
.disk_format
= img_info
['disk_format']
333 img
.container_format
= img_info
['container_format']
334 if img_info
['status'] == 'active':
337 img
.state
= 'inactive'
340 @rwstatus(ret_on_failure
=[[]])
341 def do_get_image_list(self
, account
):
342 """Return a list of the names of all available images.
345 account - a cloud account
348 The the list of images in VimResources object
350 response
= RwcalYang
.VimResources()
351 with self
._use
_driver
(account
) as drv
:
352 images
= drv
.glance_image_list()
354 response
.imageinfo_list
.append(RwcalOpenstackPlugin
._fill
_image
_info
(img
))
357 @rwstatus(ret_on_failure
=[None])
358 def do_get_image(self
, account
, image_id
):
359 """Return a image information.
362 account - a cloud account
363 image_id - an id of the image
366 ImageInfoItem object containing image information.
368 with self
._use
_driver
(account
) as drv
:
369 image
= drv
.glance_image_get(image_id
)
370 return RwcalOpenstackPlugin
._fill
_image
_info
(image
)
372 # This is being deprecated. Please do not use for new SW development
373 @rwstatus(ret_on_failure
=[""])
374 def do_create_vm(self
, account
, vminfo
):
375 """Create a new virtual machine.
378 account - a cloud account
379 vminfo - information that defines the type of VM to create
385 kwargs
['name'] = vminfo
.vm_name
386 kwargs
['flavor_id'] = vminfo
.flavor_id
387 if vminfo
.has_field('image_id'):
388 kwargs
['image_id'] = vminfo
.image_id
390 with self
._use
_driver
(account
) as drv
:
391 ### If floating_ip is required and we don't have one, better fail before any further allocation
392 if vminfo
.has_field('allocate_public_address') and vminfo
.allocate_public_address
:
393 if account
.openstack
.has_field('floating_ip_pool'):
394 pool_name
= account
.openstack
.floating_ip_pool
397 floating_ip
= self
._allocate
_floating
_ip
(drv
, pool_name
)
401 if vminfo
.has_field('cloud_init') and vminfo
.cloud_init
.has_field('userdata'):
402 kwargs
['userdata'] = vminfo
.cloud_init
.userdata
404 kwargs
['userdata'] = ''
406 if account
.openstack
.security_groups
:
407 kwargs
['security_groups'] = account
.openstack
.security_groups
410 for port
in vminfo
.port_list
:
411 port_list
.append(port
.port_id
)
414 kwargs
['port_list'] = port_list
417 for network
in vminfo
.network_list
:
418 network_list
.append(network
.network_id
)
421 kwargs
['network_list'] = network_list
424 for field
in vminfo
.user_tags
.fields
:
425 if vminfo
.user_tags
.has_field(field
):
426 metadata
[field
] = getattr(vminfo
.user_tags
, field
)
427 kwargs
['metadata'] = metadata
429 if vminfo
.has_field('availability_zone'):
430 kwargs
['availability_zone'] = vminfo
.availability_zone
432 kwargs
['availability_zone'] = None
434 if vminfo
.has_field('server_group'):
435 kwargs
['scheduler_hints'] = {'group': vminfo
.server_group
}
437 kwargs
['scheduler_hints'] = None
439 with self
._use
_driver
(account
) as drv
:
440 vm_id
= drv
.nova_server_create(**kwargs
)
442 self
.prepare_vdu_on_boot(account
, vm_id
, floating_ip
)
447 def do_start_vm(self
, account
, vm_id
):
448 """Start an existing virtual machine.
451 account - a cloud account
452 vm_id - an id of the VM
454 with self
._use
_driver
(account
) as drv
:
455 drv
.nova_server_start(vm_id
)
458 def do_stop_vm(self
, account
, vm_id
):
459 """Stop a running virtual machine.
462 account - a cloud account
463 vm_id - an id of the VM
465 with self
._use
_driver
(account
) as drv
:
466 drv
.nova_server_stop(vm_id
)
469 def do_delete_vm(self
, account
, vm_id
):
470 """Delete a virtual machine.
473 account - a cloud account
474 vm_id - an id of the VM
476 with self
._use
_driver
(account
) as drv
:
477 drv
.nova_server_delete(vm_id
)
480 def do_reboot_vm(self
, account
, vm_id
):
481 """Reboot a virtual machine.
484 account - a cloud account
485 vm_id - an id of the VM
487 with self
._use
_driver
(account
) as drv
:
488 drv
.nova_server_reboot(vm_id
)
491 def _fill_vm_info(vm_info
, mgmt_network
):
492 """Create a GI object from vm info dictionary
494 Converts VM information dictionary object returned by openstack
495 driver into Protobuf Gi Object
498 vm_info - VM information from openstack
499 mgmt_network - Management network
502 Protobuf Gi object for VM
504 vm
= RwcalYang
.VMInfoItem()
505 vm
.vm_id
= vm_info
['id']
506 vm
.vm_name
= vm_info
['name']
507 vm
.image_id
= vm_info
['image']['id']
508 vm
.flavor_id
= vm_info
['flavor']['id']
509 vm
.state
= vm_info
['status']
510 for network_name
, network_info
in vm_info
['addresses'].items():
512 if network_name
== mgmt_network
:
513 vm
.public_ip
= next((item
['addr']
514 for item
in network_info
515 if item
['OS-EXT-IPS:type'] == 'floating'),
516 network_info
[0]['addr'])
517 vm
.management_ip
= network_info
[0]['addr']
519 for interface
in network_info
:
520 addr
= vm
.private_ip_list
.add()
521 addr
.ip_address
= interface
['addr']
523 for network_name
, network_info
in vm_info
['addresses'].items():
524 if network_info
and network_name
== mgmt_network
and not vm
.public_ip
:
525 for interface
in network_info
:
526 if 'OS-EXT-IPS:type' in interface
and interface
['OS-EXT-IPS:type'] == 'floating':
527 vm
.public_ip
= interface
['addr']
529 # Look for any metadata
530 for key
, value
in vm_info
['metadata'].items():
531 if key
in vm
.user_tags
.fields
:
532 setattr(vm
.user_tags
, key
, value
)
533 if 'OS-EXT-SRV-ATTR:host' in vm_info
:
534 if vm_info
['OS-EXT-SRV-ATTR:host'] != None:
535 vm
.host_name
= vm_info
['OS-EXT-SRV-ATTR:host']
536 if 'OS-EXT-AZ:availability_zone' in vm_info
:
537 if vm_info
['OS-EXT-AZ:availability_zone'] != None:
538 vm
.availability_zone
= vm_info
['OS-EXT-AZ:availability_zone']
541 @rwstatus(ret_on_failure
=[[]])
542 def do_get_vm_list(self
, account
):
543 """Return a list of the VMs as vala boxed objects
546 account - a cloud account
549 List containing VM information
551 response
= RwcalYang
.VimResources()
552 with self
._use
_driver
(account
) as drv
:
553 vms
= drv
.nova_server_list()
555 response
.vminfo_list
.append(RwcalOpenstackPlugin
._fill
_vm
_info
(vm
, account
.openstack
.mgmt_network
))
558 @rwstatus(ret_on_failure
=[None])
559 def do_get_vm(self
, account
, id):
560 """Return vm information.
563 account - a cloud account
564 id - an id for the VM
569 with self
._use
_driver
(account
) as drv
:
570 vm
= drv
.nova_server_get(id)
571 return RwcalOpenstackPlugin
._fill
_vm
_info
(vm
, account
.openstack
.mgmt_network
)
574 def _get_guest_epa_specs(guest_epa
):
576 Returns EPA Specs dictionary for guest_epa attributes
579 if guest_epa
.has_field('mempage_size'):
580 mempage_size
= espec_utils
.guest
.mano_to_extra_spec_mempage_size(guest_epa
.mempage_size
)
581 if mempage_size
is not None:
582 epa_specs
['hw:mem_page_size'] = mempage_size
584 if guest_epa
.has_field('cpu_pinning_policy'):
585 cpu_pinning_policy
= espec_utils
.guest
.mano_to_extra_spec_cpu_pinning_policy(guest_epa
.cpu_pinning_policy
)
586 if cpu_pinning_policy
is not None:
587 epa_specs
['hw:cpu_policy'] = cpu_pinning_policy
589 if guest_epa
.has_field('cpu_thread_pinning_policy'):
590 cpu_thread_pinning_policy
= espec_utils
.guest
.mano_to_extra_spec_cpu_thread_pinning_policy(guest_epa
.cpu_thread_pinning_policy
)
591 if cpu_thread_pinning_policy
is None:
592 epa_specs
['hw:cpu_threads_policy'] = cpu_thread_pinning_policy
594 if guest_epa
.has_field('trusted_execution'):
595 trusted_execution
= espec_utils
.guest
.mano_to_extra_spec_trusted_execution(guest_epa
.trusted_execution
)
596 if trusted_execution
is not None:
597 epa_specs
['trust:trusted_host'] = trusted_execution
599 if guest_epa
.has_field('numa_node_policy'):
600 if guest_epa
.numa_node_policy
.has_field('node_cnt'):
601 numa_node_count
= espec_utils
.guest
.mano_to_extra_spec_numa_node_count(guest_epa
.numa_node_policy
.node_cnt
)
602 if numa_node_count
is not None:
603 epa_specs
['hw:numa_nodes'] = numa_node_count
605 if guest_epa
.numa_node_policy
.has_field('mem_policy'):
606 numa_memory_policy
= espec_utils
.guest
.mano_to_extra_spec_numa_memory_policy(guest_epa
.numa_node_policy
.mem_policy
)
607 if numa_memory_policy
is not None:
608 epa_specs
['hw:numa_mempolicy'] = numa_memory_policy
610 if guest_epa
.numa_node_policy
.has_field('node'):
611 for node
in guest_epa
.numa_node_policy
.node
:
612 if node
.has_field('vcpu') and node
.vcpu
:
613 epa_specs
['hw:numa_cpus.'+str(node
.id)] = ','.join([str(j
.id) for j
in node
.vcpu
])
615 epa_specs
['hw:numa_mem.'+str(node
.id)] = str(node
.memory_mb
)
617 if guest_epa
.has_field('pcie_device'):
619 for device
in guest_epa
.pcie_device
:
620 pci_devices
.append(device
.device_id
+':'+str(device
.count
))
621 epa_specs
['pci_passthrough:alias'] = ','.join(pci_devices
)
626 def _get_host_epa_specs(host_epa
):
628 Returns EPA Specs dictionary for host_epa attributes
633 if host_epa
.has_field('cpu_model'):
634 cpu_model
= espec_utils
.host
.mano_to_extra_spec_cpu_model(host_epa
.cpu_model
)
635 if cpu_model
is not None:
636 epa_specs
['capabilities:cpu_info:model'] = cpu_model
638 if host_epa
.has_field('cpu_arch'):
639 cpu_arch
= espec_utils
.host
.mano_to_extra_spec_cpu_arch(host_epa
.cpu_arch
)
640 if cpu_arch
is not None:
641 epa_specs
['capabilities:cpu_info:arch'] = cpu_arch
643 if host_epa
.has_field('cpu_vendor'):
644 cpu_vendor
= espec_utils
.host
.mano_to_extra_spec_cpu_vendor(host_epa
.cpu_vendor
)
645 if cpu_vendor
is not None:
646 epa_specs
['capabilities:cpu_info:vendor'] = cpu_vendor
648 if host_epa
.has_field('cpu_socket_count'):
649 cpu_socket_count
= espec_utils
.host
.mano_to_extra_spec_cpu_socket_count(host_epa
.cpu_socket_count
)
650 if cpu_socket_count
is not None:
651 epa_specs
['capabilities:cpu_info:topology:sockets'] = cpu_socket_count
653 if host_epa
.has_field('cpu_core_count'):
654 cpu_core_count
= espec_utils
.host
.mano_to_extra_spec_cpu_core_count(host_epa
.cpu_core_count
)
655 if cpu_core_count
is not None:
656 epa_specs
['capabilities:cpu_info:topology:cores'] = cpu_core_count
658 if host_epa
.has_field('cpu_core_thread_count'):
659 cpu_core_thread_count
= espec_utils
.host
.mano_to_extra_spec_cpu_core_thread_count(host_epa
.cpu_core_thread_count
)
660 if cpu_core_thread_count
is not None:
661 epa_specs
['capabilities:cpu_info:topology:threads'] = cpu_core_thread_count
663 if host_epa
.has_field('cpu_feature'):
665 espec_cpu_features
= []
666 for feature
in host_epa
.cpu_feature
:
667 cpu_features
.append(feature
.feature
)
668 espec_cpu_features
= espec_utils
.host
.mano_to_extra_spec_cpu_features(cpu_features
)
669 if espec_cpu_features
is not None:
670 epa_specs
['capabilities:cpu_info:features'] = espec_cpu_features
674 def _get_hypervisor_epa_specs(guest_epa
):
676 Returns EPA Specs dictionary for hypervisor_epa attributes
679 return hypervisor_epa
682 def _get_vswitch_epa_specs(guest_epa
):
684 Returns EPA Specs dictionary for vswitch_epa attributes
690 def _get_host_aggregate_epa_specs(host_aggregate
):
692 Returns EPA Specs dictionary for host aggregates
695 for aggregate
in host_aggregate
:
696 epa_specs
['aggregate_instance_extra_specs:'+aggregate
.metadata_key
] = aggregate
.metadata_value
701 def _get_epa_specs(flavor
):
703 Returns epa_specs dictionary based on flavor information
706 if flavor
.has_field('guest_epa'):
707 guest_epa
= RwcalOpenstackPlugin
._get
_guest
_epa
_specs
(flavor
.guest_epa
)
708 epa_specs
.update(guest_epa
)
709 if flavor
.has_field('host_epa'):
710 host_epa
= RwcalOpenstackPlugin
._get
_host
_epa
_specs
(flavor
.host_epa
)
711 epa_specs
.update(host_epa
)
712 if flavor
.has_field('hypervisor_epa'):
713 hypervisor_epa
= RwcalOpenstackPlugin
._get
_hypervisor
_epa
_specs
(flavor
.hypervisor_epa
)
714 epa_specs
.update(hypervisor_epa
)
715 if flavor
.has_field('vswitch_epa'):
716 vswitch_epa
= RwcalOpenstackPlugin
._get
_vswitch
_epa
_specs
(flavor
.vswitch_epa
)
717 epa_specs
.update(vswitch_epa
)
718 if flavor
.has_field('host_aggregate'):
719 host_aggregate
= RwcalOpenstackPlugin
._get
_host
_aggregate
_epa
_specs
(flavor
.host_aggregate
)
720 epa_specs
.update(host_aggregate
)
723 @rwstatus(ret_on_failure
=[""])
724 def do_create_flavor(self
, account
, flavor
):
725 """Create new flavor.
728 account - a cloud account
729 flavor - flavor of the VM
734 epa_specs
= RwcalOpenstackPlugin
._get
_epa
_specs
(flavor
)
735 with self
._use
_driver
(account
) as drv
:
736 return drv
.nova_flavor_create(name
= flavor
.name
,
737 ram
= flavor
.vm_flavor
.memory_mb
,
738 vcpus
= flavor
.vm_flavor
.vcpu_count
,
739 disk
= flavor
.vm_flavor
.storage_gb
,
740 epa_specs
= epa_specs
)
744 def do_delete_flavor(self
, account
, flavor_id
):
748 account - a cloud account
749 flavor_id - id flavor of the VM
751 with self
._use
_driver
(account
) as drv
:
752 drv
.nova_flavor_delete(flavor_id
)
755 def _fill_epa_attributes(flavor
, flavor_info
):
756 """Helper function to populate the EPA attributes
759 flavor : Object with EPA attributes
760 flavor_info: A dictionary of flavor_info received from openstack
764 getattr(flavor
, 'vm_flavor').vcpu_count
= flavor_info
['vcpus']
765 getattr(flavor
, 'vm_flavor').memory_mb
= flavor_info
['ram']
766 getattr(flavor
, 'vm_flavor').storage_gb
= flavor_info
['disk']
768 ### If extra_specs in flavor_info
769 if not 'extra_specs' in flavor_info
:
772 for attr
in flavor_info
['extra_specs']:
773 if attr
== 'hw:cpu_policy':
774 cpu_pinning_policy
= espec_utils
.guest
.extra_spec_to_mano_cpu_pinning_policy(flavor_info
['extra_specs']['hw:cpu_policy'])
775 if cpu_pinning_policy
is not None:
776 getattr(flavor
, 'guest_epa').cpu_pinning_policy
= cpu_pinning_policy
778 elif attr
== 'hw:cpu_threads_policy':
779 cpu_thread_pinning_policy
= espec_utils
.guest
.extra_spec_to_mano_cpu_thread_pinning_policy(flavor_info
['extra_specs']['hw:cpu_threads_policy'])
780 if cpu_thread_pinning_policy
is not None:
781 getattr(flavor
, 'guest_epa').cpu_thread_pinning_policy
= cpu_thread_pinning_policy
783 elif attr
== 'hw:mem_page_size':
784 mempage_size
= espec_utils
.guest
.extra_spec_to_mano_mempage_size(flavor_info
['extra_specs']['hw:mem_page_size'])
785 if mempage_size
is not None:
786 getattr(flavor
, 'guest_epa').mempage_size
= mempage_size
789 elif attr
== 'hw:numa_nodes':
790 numa_node_count
= espec_utils
.guest
.extra_specs_to_mano_numa_node_count(flavor_info
['extra_specs']['hw:numa_nodes'])
791 if numa_node_count
is not None:
792 getattr(flavor
,'guest_epa').numa_node_policy
.node_cnt
= numa_node_count
794 elif attr
.startswith('hw:numa_cpus.'):
795 node_id
= attr
.split('.')[1]
796 nodes
= [ n
for n
in flavor
.guest_epa
.numa_node_policy
.node
if n
.id == int(node_id
) ]
800 numa_node
= getattr(flavor
,'guest_epa').numa_node_policy
.node
.add()
801 numa_node
.id = int(node_id
)
803 for x
in flavor_info
['extra_specs'][attr
].split(','):
804 numa_node_vcpu
= numa_node
.vcpu
.add()
805 numa_node_vcpu
.id = int(x
)
807 elif attr
.startswith('hw:numa_mem.'):
808 node_id
= attr
.split('.')[1]
809 nodes
= [ n
for n
in flavor
.guest_epa
.numa_node_policy
.node
if n
.id == int(node_id
) ]
813 numa_node
= getattr(flavor
,'guest_epa').numa_node_policy
.node
.add()
814 numa_node
.id = int(node_id
)
816 numa_node
.memory_mb
= int(flavor_info
['extra_specs'][attr
])
818 elif attr
== 'hw:numa_mempolicy':
819 numa_memory_policy
= espec_utils
.guest
.extra_to_mano_spec_numa_memory_policy(flavor_info
['extra_specs']['hw:numa_mempolicy'])
820 if numa_memory_policy
is not None:
821 getattr(flavor
,'guest_epa').numa_node_policy
.mem_policy
= numa_memory_policy
823 elif attr
== 'trust:trusted_host':
824 trusted_execution
= espec_utils
.guest
.extra_spec_to_mano_trusted_execution(flavor_info
['extra_specs']['trust:trusted_host'])
825 if trusted_execution
is not None:
826 getattr(flavor
,'guest_epa').trusted_execution
= trusted_execution
828 elif attr
== 'pci_passthrough:alias':
829 device_types
= flavor_info
['extra_specs']['pci_passthrough:alias']
830 for device
in device_types
.split(','):
831 dev
= getattr(flavor
,'guest_epa').pcie_device
.add()
832 dev
.device_id
= device
.split(':')[0]
833 dev
.count
= int(device
.split(':')[1])
835 elif attr
== 'capabilities:cpu_info:model':
836 cpu_model
= espec_utils
.host
.extra_specs_to_mano_cpu_model(flavor_info
['extra_specs']['capabilities:cpu_info:model'])
837 if cpu_model
is not None:
838 getattr(flavor
, 'host_epa').cpu_model
= cpu_model
840 elif attr
== 'capabilities:cpu_info:arch':
841 cpu_arch
= espec_utils
.host
.extra_specs_to_mano_cpu_arch(flavor_info
['extra_specs']['capabilities:cpu_info:arch'])
842 if cpu_arch
is not None:
843 getattr(flavor
, 'host_epa').cpu_arch
= cpu_arch
845 elif attr
== 'capabilities:cpu_info:vendor':
846 cpu_vendor
= espec_utils
.host
.extra_spec_to_mano_cpu_vendor(flavor_info
['extra_specs']['capabilities:cpu_info:vendor'])
847 if cpu_vendor
is not None:
848 getattr(flavor
, 'host_epa').cpu_vendor
= cpu_vendor
850 elif attr
== 'capabilities:cpu_info:topology:sockets':
851 cpu_sockets
= espec_utils
.host
.extra_spec_to_mano_cpu_socket_count(flavor_info
['extra_specs']['capabilities:cpu_info:topology:sockets'])
852 if cpu_sockets
is not None:
853 getattr(flavor
, 'host_epa').cpu_socket_count
= cpu_sockets
855 elif attr
== 'capabilities:cpu_info:topology:cores':
856 cpu_cores
= espec_utils
.host
.extra_spec_to_mano_cpu_core_count(flavor_info
['extra_specs']['capabilities:cpu_info:topology:cores'])
857 if cpu_cores
is not None:
858 getattr(flavor
, 'host_epa').cpu_core_count
= cpu_cores
860 elif attr
== 'capabilities:cpu_info:topology:threads':
861 cpu_threads
= espec_utils
.host
.extra_spec_to_mano_cpu_core_thread_count(flavor_info
['extra_specs']['capabilities:cpu_info:topology:threads'])
862 if cpu_threads
is not None:
863 getattr(flavor
, 'host_epa').cpu_core_thread_count
= cpu_threads
865 elif attr
== 'capabilities:cpu_info:features':
866 cpu_features
= espec_utils
.host
.extra_spec_to_mano_cpu_features(flavor_info
['extra_specs']['capabilities:cpu_info:features'])
867 if cpu_features
is not None:
868 for feature
in cpu_features
:
869 getattr(flavor
, 'host_epa').cpu_feature
.append(feature
)
870 elif attr
.startswith('aggregate_instance_extra_specs:'):
871 aggregate
= getattr(flavor
, 'host_aggregate').add()
872 aggregate
.metadata_key
= ":".join(attr
.split(':')[1::])
873 aggregate
.metadata_value
= flavor_info
['extra_specs'][attr
]
876 def _fill_flavor_info(flavor_info
):
877 """Create a GI object from flavor info dictionary
879 Converts Flavor information dictionary object returned by openstack
880 driver into Protobuf Gi Object
883 flavor_info: Flavor information from openstack
886 Object of class FlavorInfoItem
888 flavor
= RwcalYang
.FlavorInfoItem()
889 flavor
.name
= flavor_info
['name']
890 flavor
.id = flavor_info
['id']
891 RwcalOpenstackPlugin
._fill
_epa
_attributes
(flavor
, flavor_info
)
895 @rwstatus(ret_on_failure
=[[]])
896 def do_get_flavor_list(self
, account
):
897 """Return flavor information.
900 account - a cloud account
905 response
= RwcalYang
.VimResources()
906 with self
._use
_driver
(account
) as drv
:
907 flavors
= drv
.nova_flavor_list()
909 response
.flavorinfo_list
.append(RwcalOpenstackPlugin
._fill
_flavor
_info
(flv
))
912 @rwstatus(ret_on_failure
=[None])
913 def do_get_flavor(self
, account
, id):
914 """Return flavor information.
917 account - a cloud account
918 id - an id for the flavor
923 with self
._use
_driver
(account
) as drv
:
924 flavor
= drv
.nova_flavor_get(id)
925 return RwcalOpenstackPlugin
._fill
_flavor
_info
(flavor
)
928 def _fill_network_info(self
, network_info
, account
):
929 """Create a GI object from network info dictionary
931 Converts Network information dictionary object returned by openstack
932 driver into Protobuf Gi Object
935 network_info - Network information from openstack
936 account - a cloud account
941 network
= RwcalYang
.NetworkInfoItem()
942 network
.network_name
= network_info
['name']
943 network
.network_id
= network_info
['id']
944 if ('provider:network_type' in network_info
) and (network_info
['provider:network_type'] != None):
945 network
.provider_network
.overlay_type
= network_info
['provider:network_type'].upper()
946 if ('provider:segmentation_id' in network_info
) and (network_info
['provider:segmentation_id']):
947 network
.provider_network
.segmentation_id
= network_info
['provider:segmentation_id']
948 if ('provider:physical_network' in network_info
) and (network_info
['provider:physical_network']):
949 network
.provider_network
.physical_network
= network_info
['provider:physical_network'].upper()
951 if 'subnets' in network_info
and network_info
['subnets']:
952 subnet_id
= network_info
['subnets'][0]
953 with self
._use
_driver
(account
) as drv
:
954 subnet
= drv
.neutron_subnet_get(subnet_id
)
955 network
.subnet
= subnet
['cidr']
958 @rwstatus(ret_on_failure
=[[]])
959 def do_get_network_list(self
, account
):
960 """Return a list of networks
963 account - a cloud account
968 response
= RwcalYang
.VimResources()
969 with self
._use
_driver
(account
) as drv
:
970 networks
= drv
.neutron_network_list()
971 for network
in networks
:
972 response
.networkinfo_list
.append(self
._fill
_network
_info
(network
, account
))
975 @rwstatus(ret_on_failure
=[None])
976 def do_get_network(self
, account
, id):
980 account - a cloud account
981 id - an id for the network
986 with self
._use
_driver
(account
) as drv
:
987 network
= drv
.neutron_network_get(id)
988 return self
._fill
_network
_info
(network
, account
)
990 @rwstatus(ret_on_failure
=[""])
991 def do_create_network(self
, account
, network
):
992 """Create a new network
995 account - a cloud account
996 network - Network object
1002 kwargs
['name'] = network
.network_name
1003 kwargs
['admin_state_up'] = True
1004 kwargs
['external_router'] = False
1005 kwargs
['shared'] = False
1007 if network
.has_field('provider_network'):
1008 if network
.provider_network
.has_field('physical_network'):
1009 kwargs
['physical_network'] = network
.provider_network
.physical_network
1010 if network
.provider_network
.has_field('overlay_type'):
1011 kwargs
['network_type'] = network
.provider_network
.overlay_type
.lower()
1012 if network
.provider_network
.has_field('segmentation_id'):
1013 kwargs
['segmentation_id'] = network
.provider_network
.segmentation_id
1015 with self
._use
_driver
(account
) as drv
:
1016 network_id
= drv
.neutron_network_create(**kwargs
)
1017 drv
.neutron_subnet_create(network_id
= network_id
,
1018 cidr
= network
.subnet
)
1022 def do_delete_network(self
, account
, network_id
):
1026 account - a cloud account
1027 network_id - an id for the network
1029 with self
._use
_driver
(account
) as drv
:
1030 drv
.neutron_network_delete(network_id
)
1033 def _fill_port_info(port_info
):
1034 """Create a GI object from port info dictionary
1036 Converts Port information dictionary object returned by openstack
1037 driver into Protobuf Gi Object
1040 port_info - Port information from openstack
1045 port
= RwcalYang
.PortInfoItem()
1047 port
.port_name
= port_info
['name']
1048 port
.port_id
= port_info
['id']
1049 port
.network_id
= port_info
['network_id']
1050 port
.port_state
= port_info
['status']
1051 if 'device_id' in port_info
:
1052 port
.vm_id
= port_info
['device_id']
1053 if 'fixed_ips' in port_info
:
1054 port
.ip_address
= port_info
['fixed_ips'][0]['ip_address']
1057 @rwstatus(ret_on_failure
=[None])
1058 def do_get_port(self
, account
, port_id
):
1062 account - a cloud account
1063 port_id - an id for the port
1068 with self
._use
_driver
(account
) as drv
:
1069 port
= drv
.neutron_port_get(port_id
)
1071 return RwcalOpenstackPlugin
._fill
_port
_info
(port
)
1073 @rwstatus(ret_on_failure
=[[]])
1074 def do_get_port_list(self
, account
):
1075 """Return a list of ports
1078 account - a cloud account
1083 response
= RwcalYang
.VimResources()
1084 with self
._use
_driver
(account
) as drv
:
1085 ports
= drv
.neutron_port_list(*{})
1087 response
.portinfo_list
.append(RwcalOpenstackPlugin
._fill
_port
_info
(port
))
1090 @rwstatus(ret_on_failure
=[""])
1091 def do_create_port(self
, account
, port
):
1092 """Create a new port
1095 account - a cloud account
1102 kwargs
['name'] = port
.port_name
1103 kwargs
['network_id'] = port
.network_id
1104 kwargs
['admin_state_up'] = True
1105 if port
.has_field('vm_id'):
1106 kwargs
['vm_id'] = port
.vm_id
1107 if port
.has_field('port_type'):
1108 kwargs
['port_type'] = port
.port_type
1110 kwargs
['port_type'] = "normal"
1112 with self
._use
_driver
(account
) as drv
:
1113 return drv
.neutron_port_create(**kwargs
)
1116 def do_delete_port(self
, account
, port_id
):
1120 account - a cloud account
1121 port_id - an id for port
1123 with self
._use
_driver
(account
) as drv
:
1124 drv
.neutron_port_delete(port_id
)
1126 @rwstatus(ret_on_failure
=[""])
1127 def do_add_host(self
, account
, host
):
1131 account - a cloud account
1132 host - a host object
1137 raise NotImplementedError
1140 def do_remove_host(self
, account
, host_id
):
1144 account - a cloud account
1145 host_id - an id for the host
1147 raise NotImplementedError
1149 @rwstatus(ret_on_failure
=[None])
1150 def do_get_host(self
, account
, host_id
):
1154 account - a cloud account
1155 host_id - an id for host
1160 raise NotImplementedError
1162 @rwstatus(ret_on_failure
=[[]])
1163 def do_get_host_list(self
, account
):
1164 """Return a list of hosts
1167 account - a cloud account
1172 raise NotImplementedError
1175 def _fill_connection_point_info(c_point
, port_info
):
1176 """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints()
1178 Converts Port information dictionary object returned by openstack
1179 driver into Protobuf Gi Object
1182 port_info - Port information from openstack
1184 Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints
1186 c_point
.name
= port_info
['name']
1187 c_point
.connection_point_id
= port_info
['id']
1188 if ('fixed_ips' in port_info
) and (len(port_info
['fixed_ips']) >= 1):
1189 if 'ip_address' in port_info
['fixed_ips'][0]:
1190 c_point
.ip_address
= port_info
['fixed_ips'][0]['ip_address']
1191 if 'mac_address' in port_info
:
1192 c_point
.mac_addr
= port_info
['mac_address']
1193 if port_info
['status'] == 'ACTIVE':
1194 c_point
.state
= 'active'
1196 c_point
.state
= 'inactive'
1197 if 'network_id' in port_info
:
1198 c_point
.virtual_link_id
= port_info
['network_id']
1199 if ('device_id' in port_info
) and (port_info
['device_id']):
1200 c_point
.vdu_id
= port_info
['device_id']
1203 def _fill_virtual_link_info(network_info
, port_list
, subnet
):
1204 """Create a GI object for VirtualLinkInfoParams
1206 Converts Network and Port information dictionary object
1207 returned by openstack driver into Protobuf Gi Object
1210 network_info - Network information from openstack
1211 port_list - A list of port information from openstack
1212 subnet: Subnet information from openstack
1214 Protobuf Gi object for VirtualLinkInfoParams
1216 link
= RwcalYang
.VirtualLinkInfoParams()
1217 link
.name
= network_info
['name']
1218 if network_info
['status'] == 'ACTIVE':
1219 link
.state
= 'active'
1221 link
.state
= 'inactive'
1222 link
.virtual_link_id
= network_info
['id']
1223 for port
in port_list
:
1224 if port
['device_owner'] == 'compute:None':
1225 c_point
= link
.connection_points
.add()
1226 RwcalOpenstackPlugin
._fill
_connection
_point
_info
(c_point
, port
)
1229 link
.subnet
= subnet
['cidr']
1231 if ('provider:network_type' in network_info
) and (network_info
['provider:network_type'] != None):
1232 link
.provider_network
.overlay_type
= network_info
['provider:network_type'].upper()
1233 if ('provider:segmentation_id' in network_info
) and (network_info
['provider:segmentation_id']):
1234 link
.provider_network
.segmentation_id
= network_info
['provider:segmentation_id']
1235 if ('provider:physical_network' in network_info
) and (network_info
['provider:physical_network']):
1236 link
.provider_network
.physical_network
= network_info
['provider:physical_network'].upper()
1241 def _fill_vdu_info(drv
, vm_info
, flavor_info
, mgmt_network
, port_list
, server_group
, volume_list
= None):
1242 """Create a GI object for VDUInfoParams
1244 Converts VM information dictionary object returned by openstack
1245 driver into Protobuf Gi Object
1248 vm_info - VM information from openstack
1249 flavor_info - VM Flavor information from openstack
1250 mgmt_network - Management network
1251 port_list - A list of port information from openstack
1252 server_group - A list (with one element or empty list) of server group to which this VM belongs
1254 Protobuf Gi object for VDUInfoParams
1256 vdu
= RwcalYang
.VDUInfoParams()
1257 vdu
.name
= vm_info
['name']
1258 vdu
.vdu_id
= vm_info
['id']
1259 for network_name
, network_info
in vm_info
['addresses'].items():
1260 if network_info
and network_name
== mgmt_network
:
1261 for interface
in network_info
:
1262 if 'OS-EXT-IPS:type' in interface
:
1263 if interface
['OS-EXT-IPS:type'] == 'fixed':
1264 vdu
.management_ip
= interface
['addr']
1265 elif interface
['OS-EXT-IPS:type'] == 'floating':
1266 vdu
.public_ip
= interface
['addr']
1268 # Look for any metadata
1269 for key
, value
in vm_info
['metadata'].items():
1270 if key
== 'node_id':
1273 custommetadata
= vdu
.custom_boot_data
.custom_meta_data
.add()
1274 custommetadata
.name
= key
1275 custommetadata
.value
= str(value
)
1277 # Look for config_drive
1278 if ('config_drive' in vm_info
):
1279 vdu
.custom_boot_data
.custom_drive
= vm_info
['config_drive']
1280 if ('image' in vm_info
) and ('id' in vm_info
['image']):
1281 vdu
.image_id
= vm_info
['image']['id']
1282 if ('flavor' in vm_info
) and ('id' in vm_info
['flavor']):
1283 vdu
.flavor_id
= vm_info
['flavor']['id']
1285 if vm_info
['status'] == 'ACTIVE':
1286 vdu
.state
= 'active'
1287 elif vm_info
['status'] == 'ERROR':
1288 vdu
.state
= 'failed'
1290 vdu
.state
= 'inactive'
1292 if 'availability_zone' in vm_info
:
1293 vdu
.availability_zone
= vm_info
['availability_zone']
1296 vdu
.server_group
.name
= server_group
[0]
1298 vdu
.cloud_type
= 'openstack'
1299 # Fill the port information
1300 for port
in port_list
:
1301 c_point
= vdu
.connection_points
.add()
1302 RwcalOpenstackPlugin
._fill
_connection
_point
_info
(c_point
, port
)
1304 if flavor_info
is not None:
1305 RwcalOpenstackPlugin
._fill
_epa
_attributes
(vdu
, flavor_info
)
1307 # Fill the volume information
1308 if volume_list
is not None:
1309 for os_volume
in volume_list
:
1310 volr
= vdu
.volumes
.add()
1312 " Device name is of format /dev/vda"
1313 vol_name
= (os_volume
['device']).split('/')[2]
1316 volr
.name
= vol_name
1317 volr
.volume_id
= os_volume
['volumeId']
1319 vol_details
= drv
.cinder_volume_get(volr
.volume_id
)
1322 if vol_details
is None:
1324 for key
, value
in vol_details
.metadata
.items():
1325 volmd
= volr
.custom_meta_data
.add()
1331 @rwcalstatus(ret_on_failure
=[""])
1332 def do_create_virtual_link(self
, account
, link_params
):
1333 """Create a new virtual link
1336 account - a cloud account
1337 link_params - information that defines the type of VDU to create
1343 kwargs
['name'] = link_params
.name
1344 kwargs
['admin_state_up'] = True
1345 kwargs
['external_router'] = False
1346 kwargs
['shared'] = False
1348 if link_params
.has_field('provider_network'):
1349 if link_params
.provider_network
.has_field('physical_network'):
1350 kwargs
['physical_network'] = link_params
.provider_network
.physical_network
1351 if link_params
.provider_network
.has_field('overlay_type'):
1352 kwargs
['network_type'] = link_params
.provider_network
.overlay_type
.lower()
1353 if link_params
.provider_network
.has_field('segmentation_id'):
1354 kwargs
['segmentation_id'] = link_params
.provider_network
.segmentation_id
1357 with self
._use
_driver
(account
) as drv
:
1359 network_id
= drv
.neutron_network_create(**kwargs
)
1360 except Exception as e
:
1361 self
.log
.error("Encountered exceptions during network creation. Exception: %s", str(e
))
1364 kwargs
= {'network_id' : network_id
,
1365 'dhcp_params': {'enable_dhcp': True},
1366 'gateway_ip' : None,}
1368 if link_params
.ip_profile_params
.has_field('ip_version'):
1369 kwargs
['ip_version'] = 6 if link_params
.ip_profile_params
.ip_version
== 'ipv6' else 4
1371 kwargs
['ip_version'] = 4
1373 if link_params
.ip_profile_params
.has_field('subnet_address'):
1374 kwargs
['cidr'] = link_params
.ip_profile_params
.subnet_address
1375 elif link_params
.ip_profile_params
.has_field('subnet_prefix_pool'):
1376 subnet_pool
= drv
.netruon_subnetpool_by_name(link_params
.ip_profile_params
.subnet_prefix_pool
)
1377 if subnet_pool
is None:
1378 self
.log
.error("Could not find subnet pool with name :%s to be used for network: %s",
1379 link_params
.ip_profile_params
.subnet_prefix_pool
,
1381 raise NeutronException
.NotFound("SubnetPool with name %s not found"%(link_params
.ip_profile_params
.subnet_prefix_pool
))
1383 kwargs
['subnetpool_id'] = subnet_pool
['id']
1384 elif link_params
.has_field('subnet'):
1385 kwargs
['cidr'] = link_params
.subnet
1387 assert 0, "No IP Prefix or Pool name specified"
1389 if link_params
.ip_profile_params
.has_field('dhcp_params'):
1390 if link_params
.ip_profile_params
.dhcp_params
.has_field('enabled'):
1391 kwargs
['dhcp_params']['enable_dhcp'] = link_params
.ip_profile_params
.dhcp_params
.enabled
1392 if link_params
.ip_profile_params
.dhcp_params
.has_field('start_address'):
1393 kwargs
['dhcp_params']['start_address'] = link_params
.ip_profile_params
.dhcp_params
.start_address
1394 if link_params
.ip_profile_params
.dhcp_params
.has_field('count'):
1395 kwargs
['dhcp_params']['count'] = link_params
.ip_profile_params
.dhcp_params
.count
1397 if link_params
.ip_profile_params
.has_field('dns_server'):
1398 kwargs
['dns_server'] = []
1399 for server
in link_params
.ip_profile_params
.dns_server
:
1400 kwargs
['dns_server'].append(server
.address
)
1402 if link_params
.ip_profile_params
.has_field('gateway_address'):
1403 kwargs
['gateway_ip'] = link_params
.ip_profile_params
.gateway_address
1405 drv
.neutron_subnet_create(**kwargs
)
1411 def do_delete_virtual_link(self
, account
, link_id
):
1412 """Delete a virtual link
1415 account - a cloud account
1416 link_id - id for the virtual-link to be deleted
1422 self
.log
.error("Empty link_id during the virtual link deletion")
1423 raise Exception("Empty link_id during the virtual link deletion")
1425 with self
._use
_driver
(account
) as drv
:
1426 port_list
= drv
.neutron_port_list(**{'network_id': link_id
})
1428 for port
in port_list
:
1429 if ((port
['device_owner'] == 'compute:None') or (port
['device_owner'] == '')):
1430 self
.do_delete_port(account
, port
['id'], no_rwstatus
=True)
1431 self
.do_delete_network(account
, link_id
, no_rwstatus
=True)
1433 @rwstatus(ret_on_failure
=[None])
1434 def do_get_virtual_link(self
, account
, link_id
):
1435 """Get information about virtual link.
1438 account - a cloud account
1439 link_id - id for the virtual-link
1442 Object of type RwcalYang.VirtualLinkInfoParams
1445 self
.log
.error("Empty link_id during the virtual link get request")
1446 raise Exception("Empty link_id during the virtual link get request")
1448 with self
._use
_driver
(account
) as drv
:
1449 network
= drv
.neutron_network_get(link_id
)
1451 port_list
= drv
.neutron_port_list(**{'network_id': network
['id']})
1452 if 'subnets' in network
:
1453 subnet
= drv
.neutron_subnet_get(network
['subnets'][0])
1456 virtual_link
= RwcalOpenstackPlugin
._fill
_virtual
_link
_info
(network
, port_list
, subnet
)
1461 @rwstatus(ret_on_failure
=[None])
1462 def do_get_virtual_link_list(self
, account
):
1463 """Get information about all the virtual links
1466 account - a cloud account
1469 A list of objects of type RwcalYang.VirtualLinkInfoParams
1471 vnf_resources
= RwcalYang
.VNFResources()
1472 with self
._use
_driver
(account
) as drv
:
1473 networks
= drv
.neutron_network_list()
1474 for network
in networks
:
1475 port_list
= drv
.neutron_port_list(**{'network_id': network
['id']})
1476 if ('subnets' in network
) and (network
['subnets']):
1477 subnet
= drv
.neutron_subnet_get(network
['subnets'][0])
1480 virtual_link
= RwcalOpenstackPlugin
._fill
_virtual
_link
_info
(network
, port_list
, subnet
)
1481 vnf_resources
.virtual_link_info_list
.append(virtual_link
)
1482 return vnf_resources
1484 def _create_connection_point(self
, account
, c_point
):
1486 Create a connection point
1488 account - a cloud account
1489 c_point - connection_points
1492 kwargs
['name'] = c_point
.name
1493 kwargs
['network_id'] = c_point
.virtual_link_id
1494 kwargs
['admin_state_up'] = True
1496 if c_point
.type_yang
== 'VIRTIO' or c_point
.type_yang
== 'E1000':
1497 kwargs
['port_type'] = 'normal'
1498 elif c_point
.type_yang
== 'SR_IOV':
1499 kwargs
['port_type'] = 'direct'
1501 raise NotImplementedError("Port Type: %s not supported" %(c_point
.type_yang
))
1503 # By default port gets created with post_security enaled as True
1504 if c_point
.port_security_enabled
is not None and c_point
.port_security_enabled
== False:
1505 kwargs
['port_security_enabled'] = False
1507 kwargs
['port_security_enabled'] = True
1509 with self
._use
_driver
(account
) as drv
:
1510 if c_point
.has_field('security_group'):
1511 group
= drv
.neutron_security_group_by_name(c_point
.security_group
)
1512 if group
is not None:
1513 kwargs
['security_groups'] = [group
['id']]
1514 return drv
.neutron_port_create(**kwargs
)
1516 def _allocate_floating_ip(self
, drv
, pool_name
):
1518 Allocate a floating_ip. If unused floating_ip exists then its reused.
1520 drv: OpenstackDriver instance
1521 pool_name: Floating IP pool name
1524 An object of floating IP nova class (novaclient.v2.floating_ips.FloatingIP)
1527 # available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ]
1529 # if pool_name is not None:
1530 # ### Filter further based on IP address
1531 # available_ip = [ ip for ip in available_ip if ip.pool == pool_name ]
1533 # if not available_ip:
1534 # floating_ip = drv.nova_floating_ip_create(pool_name)
1536 # floating_ip = available_ip[0]
1538 floating_ip
= drv
.nova_floating_ip_create(pool_name
)
1541 def _match_vm_flavor(self
, required
, available
):
1542 self
.log
.info("Matching VM Flavor attributes")
1543 if available
.vcpu_count
!= required
.vcpu_count
:
1544 self
.log
.debug("VCPU requirement mismatch. Required: %d, Available: %d",
1545 required
.vcpu_count
,
1546 available
.vcpu_count
)
1548 if available
.memory_mb
!= required
.memory_mb
:
1549 self
.log
.debug("Memory requirement mismatch. Required: %d MB, Available: %d MB",
1551 available
.memory_mb
)
1553 if available
.storage_gb
!= required
.storage_gb
:
1554 self
.log
.debug("Storage requirement mismatch. Required: %d GB, Available: %d GB",
1555 required
.storage_gb
,
1556 available
.storage_gb
)
1558 self
.log
.debug("VM Flavor match found")
1561 def _match_guest_epa(self
, required
, available
):
1562 self
.log
.info("Matching Guest EPA attributes")
1563 if required
.has_field('pcie_device'):
1564 self
.log
.debug("Matching pcie_device")
1565 if available
.has_field('pcie_device') == False:
1566 self
.log
.debug("Matching pcie_device failed. Not available in flavor")
1569 for dev
in required
.pcie_device
:
1570 if not [ d
for d
in available
.pcie_device
1571 if ((d
.device_id
== dev
.device_id
) and (d
.count
== dev
.count
)) ]:
1572 self
.log
.debug("Matching pcie_device failed. Required: %s, Available: %s", required
.pcie_device
, available
.pcie_device
)
1574 elif available
.has_field('pcie_device'):
1575 self
.log
.debug("Rejecting available flavor because pcie_device not required but available")
1579 if required
.has_field('mempage_size'):
1580 self
.log
.debug("Matching mempage_size")
1581 if available
.has_field('mempage_size') == False:
1582 self
.log
.debug("Matching mempage_size failed. Not available in flavor")
1585 if required
.mempage_size
!= available
.mempage_size
:
1586 self
.log
.debug("Matching mempage_size failed. Required: %s, Available: %s", required
.mempage_size
, available
.mempage_size
)
1588 elif available
.has_field('mempage_size'):
1589 self
.log
.debug("Rejecting available flavor because mempage_size not required but available")
1592 if required
.has_field('cpu_pinning_policy'):
1593 self
.log
.debug("Matching cpu_pinning_policy")
1594 if required
.cpu_pinning_policy
!= 'ANY':
1595 if available
.has_field('cpu_pinning_policy') == False:
1596 self
.log
.debug("Matching cpu_pinning_policy failed. Not available in flavor")
1599 if required
.cpu_pinning_policy
!= available
.cpu_pinning_policy
:
1600 self
.log
.debug("Matching cpu_pinning_policy failed. Required: %s, Available: %s", required
.cpu_pinning_policy
, available
.cpu_pinning_policy
)
1602 elif available
.has_field('cpu_pinning_policy'):
1603 self
.log
.debug("Rejecting available flavor because cpu_pinning_policy not required but available")
1606 if required
.has_field('cpu_thread_pinning_policy'):
1607 self
.log
.debug("Matching cpu_thread_pinning_policy")
1608 if available
.has_field('cpu_thread_pinning_policy') == False:
1609 self
.log
.debug("Matching cpu_thread_pinning_policy failed. Not available in flavor")
1612 if required
.cpu_thread_pinning_policy
!= available
.cpu_thread_pinning_policy
:
1613 self
.log
.debug("Matching cpu_thread_pinning_policy failed. Required: %s, Available: %s", required
.cpu_thread_pinning_policy
, available
.cpu_thread_pinning_policy
)
1615 elif available
.has_field('cpu_thread_pinning_policy'):
1616 self
.log
.debug("Rejecting available flavor because cpu_thread_pinning_policy not required but available")
1619 if required
.has_field('trusted_execution'):
1620 self
.log
.debug("Matching trusted_execution")
1621 if required
.trusted_execution
== True:
1622 if available
.has_field('trusted_execution') == False:
1623 self
.log
.debug("Matching trusted_execution failed. Not available in flavor")
1626 if required
.trusted_execution
!= available
.trusted_execution
:
1627 self
.log
.debug("Matching trusted_execution failed. Required: %s, Available: %s", required
.trusted_execution
, available
.trusted_execution
)
1629 elif available
.has_field('trusted_execution'):
1630 self
.log
.debug("Rejecting available flavor because trusted_execution not required but available")
1633 if required
.has_field('numa_node_policy'):
1634 self
.log
.debug("Matching numa_node_policy")
1635 if available
.has_field('numa_node_policy') == False:
1636 self
.log
.debug("Matching numa_node_policy failed. Not available in flavor")
1639 if required
.numa_node_policy
.has_field('node_cnt'):
1640 self
.log
.debug("Matching numa_node_policy node_cnt")
1641 if available
.numa_node_policy
.has_field('node_cnt') == False:
1642 self
.log
.debug("Matching numa_node_policy node_cnt failed. Not available in flavor")
1645 if required
.numa_node_policy
.node_cnt
!= available
.numa_node_policy
.node_cnt
:
1646 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
)
1648 elif available
.numa_node_policy
.has_field('node_cnt'):
1649 self
.log
.debug("Rejecting available flavor because numa node count not required but available")
1652 if required
.numa_node_policy
.has_field('mem_policy'):
1653 self
.log
.debug("Matching numa_node_policy mem_policy")
1654 if available
.numa_node_policy
.has_field('mem_policy') == False:
1655 self
.log
.debug("Matching numa_node_policy mem_policy failed. Not available in flavor")
1658 if required
.numa_node_policy
.mem_policy
!= available
.numa_node_policy
.mem_policy
:
1659 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
)
1661 elif available
.numa_node_policy
.has_field('mem_policy'):
1662 self
.log
.debug("Rejecting available flavor because num node mem_policy not required but available")
1665 if required
.numa_node_policy
.has_field('node'):
1666 self
.log
.debug("Matching numa_node_policy nodes configuration")
1667 if available
.numa_node_policy
.has_field('node') == False:
1668 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Not available in flavor")
1670 for required_node
in required
.numa_node_policy
.node
:
1671 self
.log
.debug("Matching numa_node_policy nodes configuration for node %s", required_node
)
1673 for available_node
in available
.numa_node_policy
.node
:
1674 if required_node
.id != available_node
.id:
1675 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node
, available_node
)
1677 if required_node
.vcpu
!= available_node
.vcpu
:
1678 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node
, available_node
)
1680 if required_node
.memory_mb
!= available_node
.memory_mb
:
1681 self
.log
.debug("Matching numa_node_policy nodes configuration failed. Required: %s, Available: %s", required_node
, available_node
)
1684 if numa_match
== False:
1686 elif available
.numa_node_policy
.has_field('node'):
1687 self
.log
.debug("Rejecting available flavor because numa nodes not required but available")
1689 elif available
.has_field('numa_node_policy'):
1690 self
.log
.debug("Rejecting available flavor because numa_node_policy not required but available")
1692 self
.log
.info("Successful match for Guest EPA attributes")
1695 def _match_vswitch_epa(self
, required
, available
):
1696 self
.log
.debug("VSwitch EPA match found")
1699 def _match_hypervisor_epa(self
, required
, available
):
1700 self
.log
.debug("Hypervisor EPA match found")
1703 def _match_host_epa(self
, required
, available
):
1704 self
.log
.info("Matching Host EPA attributes")
1705 if required
.has_field('cpu_model'):
1706 self
.log
.debug("Matching CPU model")
1707 if available
.has_field('cpu_model') == False:
1708 self
.log
.debug("Matching CPU model failed. Not available in flavor")
1711 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1712 if required
.cpu_model
.replace('PREFER', 'REQUIRE') != available
.cpu_model
:
1713 self
.log
.debug("Matching CPU model failed. Required: %s, Available: %s", required
.cpu_model
, available
.cpu_model
)
1715 elif available
.has_field('cpu_model'):
1716 self
.log
.debug("Rejecting available flavor because cpu_model not required but available")
1719 if required
.has_field('cpu_arch'):
1720 self
.log
.debug("Matching CPU architecture")
1721 if available
.has_field('cpu_arch') == False:
1722 self
.log
.debug("Matching CPU architecture failed. Not available in flavor")
1725 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1726 if required
.cpu_arch
.replace('PREFER', 'REQUIRE') != available
.cpu_arch
:
1727 self
.log
.debug("Matching CPU architecture failed. Required: %s, Available: %s", required
.cpu_arch
, available
.cpu_arch
)
1729 elif available
.has_field('cpu_arch'):
1730 self
.log
.debug("Rejecting available flavor because cpu_arch not required but available")
1733 if required
.has_field('cpu_vendor'):
1734 self
.log
.debug("Matching CPU vendor")
1735 if available
.has_field('cpu_vendor') == False:
1736 self
.log
.debug("Matching CPU vendor failed. Not available in flavor")
1739 #### Convert all PREFER to REQUIRE since flavor will only have REQUIRE attributes
1740 if required
.cpu_vendor
.replace('PREFER', 'REQUIRE') != available
.cpu_vendor
:
1741 self
.log
.debug("Matching CPU vendor failed. Required: %s, Available: %s", required
.cpu_vendor
, available
.cpu_vendor
)
1743 elif available
.has_field('cpu_vendor'):
1744 self
.log
.debug("Rejecting available flavor because cpu_vendor not required but available")
1747 if required
.has_field('cpu_socket_count'):
1748 self
.log
.debug("Matching CPU socket count")
1749 if available
.has_field('cpu_socket_count') == False:
1750 self
.log
.debug("Matching CPU socket count failed. Not available in flavor")
1753 if required
.cpu_socket_count
!= available
.cpu_socket_count
:
1754 self
.log
.debug("Matching CPU socket count failed. Required: %s, Available: %s", required
.cpu_socket_count
, available
.cpu_socket_count
)
1756 elif available
.has_field('cpu_socket_count'):
1757 self
.log
.debug("Rejecting available flavor because cpu_socket_count not required but available")
1760 if required
.has_field('cpu_core_count'):
1761 self
.log
.debug("Matching CPU core count")
1762 if available
.has_field('cpu_core_count') == False:
1763 self
.log
.debug("Matching CPU core count failed. Not available in flavor")
1766 if required
.cpu_core_count
!= available
.cpu_core_count
:
1767 self
.log
.debug("Matching CPU core count failed. Required: %s, Available: %s", required
.cpu_core_count
, available
.cpu_core_count
)
1769 elif available
.has_field('cpu_core_count'):
1770 self
.log
.debug("Rejecting available flavor because cpu_core_count not required but available")
1773 if required
.has_field('cpu_core_thread_count'):
1774 self
.log
.debug("Matching CPU core thread count")
1775 if available
.has_field('cpu_core_thread_count') == False:
1776 self
.log
.debug("Matching CPU core thread count failed. Not available in flavor")
1779 if required
.cpu_core_thread_count
!= available
.cpu_core_thread_count
:
1780 self
.log
.debug("Matching CPU core thread count failed. Required: %s, Available: %s", required
.cpu_core_thread_count
, available
.cpu_core_thread_count
)
1782 elif available
.has_field('cpu_core_thread_count'):
1783 self
.log
.debug("Rejecting available flavor because cpu_core_thread_count not required but available")
1786 if required
.has_field('cpu_feature'):
1787 self
.log
.debug("Matching CPU feature list")
1788 if available
.has_field('cpu_feature') == False:
1789 self
.log
.debug("Matching CPU feature list failed. Not available in flavor")
1792 for feature
in required
.cpu_feature
:
1793 if feature
not in available
.cpu_feature
:
1794 self
.log
.debug("Matching CPU feature list failed. Required feature: %s is not present. Available features: %s", feature
, available
.cpu_feature
)
1796 elif available
.has_field('cpu_feature'):
1797 self
.log
.debug("Rejecting available flavor because cpu_feature not required but available")
1799 self
.log
.info("Successful match for Host EPA attributes")
1803 def _match_placement_group_inputs(self
, required
, available
):
1804 self
.log
.info("Matching Host aggregate attributes")
1806 if not required
and not available
:
1807 # Host aggregate not required and not available => success
1808 self
.log
.info("Successful match for Host Aggregate attributes")
1810 if required
and available
:
1811 # Host aggregate requested and available => Do a match and decide
1812 xx
= [ x
.as_dict() for x
in required
]
1813 yy
= [ y
.as_dict() for y
in available
]
1816 self
.log
.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required
, available
)
1818 self
.log
.info("Successful match for Host Aggregate attributes")
1821 # Either of following conditions => Failure
1822 # - Host aggregate required but not available
1823 # - Host aggregate not required but available
1824 self
.log
.debug("Rejecting available flavor because host Aggregate mismatch. Required: %s, Available: %s ", required
, available
)
1827 def match_epa_params(self
, resource_info
, request_params
):
1828 result
= self
._match
_vm
_flavor
(getattr(request_params
, 'vm_flavor'),
1829 getattr(resource_info
, 'vm_flavor'))
1831 self
.log
.debug("VM Flavor mismatched")
1834 result
= self
._match
_guest
_epa
(getattr(request_params
, 'guest_epa'),
1835 getattr(resource_info
, 'guest_epa'))
1837 self
.log
.debug("Guest EPA mismatched")
1840 result
= self
._match
_vswitch
_epa
(getattr(request_params
, 'vswitch_epa'),
1841 getattr(resource_info
, 'vswitch_epa'))
1843 self
.log
.debug("Vswitch EPA mismatched")
1846 result
= self
._match
_hypervisor
_epa
(getattr(request_params
, 'hypervisor_epa'),
1847 getattr(resource_info
, 'hypervisor_epa'))
1849 self
.log
.debug("Hypervisor EPA mismatched")
1852 result
= self
._match
_host
_epa
(getattr(request_params
, 'host_epa'),
1853 getattr(resource_info
, 'host_epa'))
1855 self
.log
.debug("Host EPA mismatched")
1858 result
= self
._match
_placement
_group
_inputs
(getattr(request_params
, 'host_aggregate'),
1859 getattr(resource_info
, 'host_aggregate'))
1862 self
.log
.debug("Host Aggregate mismatched")
1867 def _select_resource_flavor(self
, account
, vdu_init
):
1869 Select a existing flavor if it matches the request or create new flavor
1871 flavor
= RwcalYang
.FlavorInfoItem()
1872 flavor
.name
= str(uuid
.uuid4())
1873 epa_types
= ['vm_flavor', 'guest_epa', 'host_epa', 'host_aggregate', 'hypervisor_epa', 'vswitch_epa']
1874 epa_dict
= {k
: v
for k
, v
in vdu_init
.as_dict().items() if k
in epa_types
}
1875 flavor
.from_dict(epa_dict
)
1877 rc
, response
= self
.do_get_flavor_list(account
)
1878 if rc
!= RwTypes
.RwStatus
.SUCCESS
:
1879 self
.log
.error("Get-flavor-info-list operation failed for cloud account: %s",
1881 raise OpenstackCALOperationFailure("Get-flavor-info-list operation failed for cloud account: %s" %(account
.name
))
1884 flavor_list
= response
.flavorinfo_list
1885 self
.log
.debug("Received %d flavor information from RW.CAL", len(flavor_list
))
1886 for flv
in flavor_list
:
1887 self
.log
.info("Attempting to match compute requirement for VDU: %s with flavor %s",
1889 if self
.match_epa_params(flv
, vdu_init
):
1890 self
.log
.info("Flavor match found for compute requirements for VDU: %s with flavor name: %s, flavor-id: %s",
1891 vdu_init
.name
, flv
.name
, flv
.id)
1894 if account
.openstack
.dynamic_flavor_support
is False:
1895 self
.log
.error("Unable to create flavor for compute requirement for VDU: %s. VDU instantiation failed", vdu_init
.name
)
1896 raise OpenstackCALOperationFailure("No resource available with matching EPA attributes")
1898 rc
,flavor_id
= self
.do_create_flavor(account
,flavor
)
1899 if rc
!= RwTypes
.RwStatus
.SUCCESS
:
1900 self
.log
.error("Create-flavor operation failed for cloud account: %s",
1902 raise OpenstackCALOperationFailure("Create-flavor operation failed for cloud account: %s" %(account
.name
))
1905 def _create_vm(self
, account
, vduinfo
, pci_assignement
=None, server_group
=None, port_list
=None, network_list
=None, imageinfo_list
=None):
1906 """Create a new virtual machine.
1909 account - a cloud account
1910 vminfo - information that defines the type of VM to create
1916 kwargs
['name'] = vduinfo
.name
1917 kwargs
['flavor_id'] = vduinfo
.flavor_id
1918 if vduinfo
.has_field('image_id'):
1919 kwargs
['image_id'] = vduinfo
.image_id
1921 kwargs
['image_id'] = ""
1923 with self
._use
_driver
(account
) as drv
:
1924 ### If floating_ip is required and we don't have one, better fail before any further allocation
1925 if vduinfo
.has_field('allocate_public_address') and vduinfo
.allocate_public_address
:
1926 if account
.openstack
.has_field('floating_ip_pool'):
1927 pool_name
= account
.openstack
.floating_ip_pool
1930 floating_ip
= self
._allocate
_floating
_ip
(drv
, pool_name
)
1934 if vduinfo
.has_field('vdu_init') and vduinfo
.vdu_init
.has_field('userdata'):
1935 kwargs
['userdata'] = vduinfo
.vdu_init
.userdata
1937 kwargs
['userdata'] = ''
1939 if account
.openstack
.security_groups
:
1940 kwargs
['security_groups'] = account
.openstack
.security_groups
1942 kwargs
['port_list'] = port_list
1943 kwargs
['network_list'] = network_list
1947 config_drive
= False
1948 # Add all metadata related fields
1949 if vduinfo
.has_field('node_id'):
1950 metadata
['node_id'] = vduinfo
.node_id
1951 if pci_assignement
is not None:
1952 metadata
['pci_assignement'] = pci_assignement
1953 if vduinfo
.has_field('custom_boot_data'):
1954 if vduinfo
.custom_boot_data
.has_field('custom_meta_data'):
1955 for custom_meta_item
in vduinfo
.custom_boot_data
.custom_meta_data
:
1956 if custom_meta_item
.data_type
== "STRING":
1957 metadata
[custom_meta_item
.name
] = custom_meta_item
.value
1958 elif custom_meta_item
.data_type
== "JSON":
1959 metadata
[custom_meta_item
.name
] = tornado
.escape
.json_decode(custom_meta_item
.value
)
1961 raise OpenstackCALOperationFailure("Create-vdu operation failed. Unsupported data-type {} for custom-meta-data name {} ".format(custom_meta_item
.data_type
, custom_meta_item
.name
))
1962 if vduinfo
.custom_boot_data
.has_field('custom_config_files'):
1963 for custom_config_file
in vduinfo
.custom_boot_data
.custom_config_files
:
1964 files
[custom_config_file
.dest
] = custom_config_file
.source
1966 if vduinfo
.custom_boot_data
.has_field('custom_drive'):
1967 if vduinfo
.custom_boot_data
.custom_drive
is True:
1970 kwargs
['metadata'] = metadata
1971 kwargs
['files'] = files
1972 kwargs
['config_drive'] = config_drive
1974 if vduinfo
.has_field('availability_zone') and vduinfo
.availability_zone
.has_field('name'):
1975 kwargs
['availability_zone'] = vduinfo
.availability_zone
1977 kwargs
['availability_zone'] = None
1979 if server_group
is not None:
1980 kwargs
['scheduler_hints'] = {'group': server_group
}
1982 kwargs
['scheduler_hints'] = None
1984 kwargs
['block_device_mapping_v2'] = None
1985 vol_metadata
= False
1986 if vduinfo
.has_field('volumes') :
1987 kwargs
['block_device_mapping_v2'] = []
1988 with self
._use
_driver
(account
) as drv
:
1989 # Only support image->volume
1990 for volume
in vduinfo
.volumes
:
1992 block_map
['boot_index'] = volume
.boot_params
.boot_priority
1993 if "image" in volume
:
1994 # Support image->volume
1995 # Match retrived image info with volume based image name and checksum
1996 if volume
.image
is not None:
1997 matching_images
= [img
for img
in imageinfo_list
if img
['name'] == volume
.image
]
1998 if volume
.image_checksum
is not None:
1999 matching_images
= [img
for img
in matching_images
if img
['checksum'] == volume
.image_checksum
]
2000 img_id
= matching_images
[0]['id']
2002 raise OpenstackCALOperationFailure("Create-vdu operation failed. Volume image not found for name {} checksum {}".format(volume
.name
, volume
.checksum
))
2003 block_map
['uuid'] = img_id
2004 block_map
['source_type'] = "image"
2006 block_map
['source_type'] = "blank"
2008 block_map
['device_name'] = volume
.name
2009 block_map
['destination_type'] = "volume"
2010 block_map
['volume_size'] = volume
.size
2011 block_map
['delete_on_termination'] = True
2012 if volume
.guest_params
.has_field('device_type') and volume
.guest_params
.device_type
== 'cdrom':
2013 block_map
['device_type'] = 'cdrom'
2014 if volume
.guest_params
.has_field('device_bus') and volume
.guest_params
.device_bus
== 'ide':
2015 block_map
['disk_bus'] = 'ide'
2016 kwargs
['block_device_mapping_v2'].append(block_map
)
2019 with self
._use
_driver
(account
) as drv
:
2020 vm_id
= drv
.nova_server_create(**kwargs
)
2022 self
.prepare_vdu_on_boot(account
, vm_id
, floating_ip
, vduinfo
.volumes
)
2026 def get_openstack_image_info(self
, account
, image_name
, image_checksum
=None):
2027 self
.log
.debug("Looking up image id for image name %s and checksum %s on cloud account: %s",
2028 image_name
, image_checksum
, account
.name
2032 with self
._use
_driver
(account
) as drv
:
2033 image_list
= drv
.glance_image_list()
2034 matching_images
= [img
for img
in image_list
if img
['name'] == image_name
]
2036 # If the image checksum was filled in then further filter the images by the checksum
2037 if image_checksum
is not None:
2038 matching_images
= [img
for img
in matching_images
if img
['checksum'] == image_checksum
]
2040 self
.log
.warning("Image checksum not provided. Lookup using image name (%s) only.",
2043 if len(matching_images
) == 0:
2044 raise ResMgrCALOperationFailure("Could not find image name {} (using checksum: {}) for cloud account: {}".format(
2045 image_name
, image_checksum
, account
.name
2048 elif len(matching_images
) > 1:
2049 unique_checksums
= {i
.checksum
for i
in matching_images
}
2050 if len(unique_checksums
) > 1:
2051 msg
= ("Too many images with different checksums matched "
2052 "image name of %s for cloud account: %s" % (image_name
, account
.name
))
2053 raise ResMgrCALOperationFailure(msg
)
2055 return matching_images
[0]
2057 @rwcalstatus(ret_on_failure
=[""])
2058 def do_create_vdu(self
, account
, vdu_init
):
2059 """Create a new virtual deployment unit
2062 account - a cloud account
2063 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
2068 ### First create required number of ports aka connection points
2069 # Add the mgmt_ntwk by default.
2070 mgmt_network_id
= None
2071 with self
._use
_driver
(account
) as drv
:
2072 mgmt_network_id
= drv
._mgmt
_network
_id
2073 ### If floating_ip is required and we don't have one, better fail before any further allocation
2074 if vdu_init
.has_field('allocate_public_address') and vdu_init
.allocate_public_address
:
2075 if account
.openstack
.has_field('floating_ip_pool'):
2076 pool_name
= account
.openstack
.floating_ip_pool
2079 floating_ip
= self
._allocate
_floating
_ip
(drv
, pool_name
)
2086 is_explicit_mgmt_defined
= False
2087 for c_point
in vdu_init
.connection_points
:
2088 # if the user has specified explicit mgmt_network connection point
2089 # then remove the mgmt_network from the VM list
2090 if c_point
.virtual_link_id
== mgmt_network_id
:
2091 is_explicit_mgmt_defined
= True
2092 if c_point
.virtual_link_id
in network_list
:
2093 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"
2095 network_list
.append(c_point
.virtual_link_id
)
2096 port_id
= self
._create
_connection
_point
(account
, c_point
)
2097 port_list
.append(port_id
)
2099 if not vdu_init
.has_field('flavor_id'):
2100 vdu_init
.flavor_id
= self
._select
_resource
_flavor
(account
,vdu_init
)
2102 ### Obtain all images for volumes and perform validations
2103 if vdu_init
.has_field('volumes'):
2104 for volume
in vdu_init
.volumes
:
2105 if "image" in volume
:
2106 image_checksum
= volume
.image_checksum
if volume
.has_field("image_checksum") else None
2107 image_info
= self
.get_openstack_image_info(account
, volume
.image
, image_checksum
)
2108 imageinfo_list
.append(image_info
)
2109 elif vdu_init
.has_field('image_id'):
2110 with self
._use
_driver
(account
) as drv
:
2111 image_info
= drv
.glance_image_get(vdu_init
.image_id
)
2112 imageinfo_list
.append(image_info
)
2114 if not imageinfo_list
:
2115 err_str
= ("VDU has no image information")
2116 self
.log
.error(err_str
)
2117 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2119 ### Check VDU Virtual Interface type and make sure VM with property exists
2120 if vdu_init
.connection_points
:
2121 ### All virtual interfaces need to be of the same type for Openstack Accounts
2122 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
)):
2123 ### We have a mix of E1000 & VIRTIO/SR_IPOV virtual interface types in the VDU, abort instantiation.
2124 assert False, "Only one type of Virtual Intefaces supported for Openstack accounts. Found a mix of VIRTIO/SR_IOV & E1000."
2126 ## 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,
2127 ### we shall assume that all images need to have similar properties
2128 for img_info
in imageinfo_list
:
2130 virt_intf_type
= vdu_init
.connection_points
[0].type_yang
2131 if virt_intf_type
== 'E1000':
2132 if 'hw_vif_model' in img_info
and img_info
.hw_vif_model
== 'e1000':
2133 self
.log
.debug("VDU has Virtual Interface E1000, found matching image with property hw_vif_model=e1000")
2135 err_str
= ("VDU has Virtual Interface E1000, but image '%s' does not have property hw_vif_model=e1000" % img_info
.name
)
2136 self
.log
.error(err_str
)
2137 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2138 elif virt_intf_type
== 'VIRTIO' or virt_intf_type
== 'SR_IOV':
2139 if 'hw_vif_model' in img_info
:
2140 err_str
= ("VDU has Virtual Interface %s, but image '%s' has hw_vif_model mismatch" % virt_intf_type
,img_info
.name
)
2141 self
.log
.error(err_str
)
2142 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2144 self
.log
.debug("VDU has Virtual Interface %s, found matching image" % virt_intf_type
)
2146 err_str
= ("VDU Virtual Interface '%s' not supported yet" % virt_intf_type
)
2147 self
.log
.error(err_str
)
2148 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2150 with self
._use
_driver
(account
) as drv
:
2152 vm_network_list
= []
2153 if not is_explicit_mgmt_defined
:
2154 vm_network_list
.append(drv
._mgmt
_network
_id
)
2156 if vdu_init
.has_field('volumes'):
2157 # Only combination supported: Image->Volume
2158 for volume
in vdu_init
.volumes
:
2159 if "volume" in volume
:
2160 err_str
= ("VDU Volume source not supported yet")
2161 self
.log
.error(err_str
)
2162 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2163 if "guest_params" not in volume
:
2164 err_str
= ("VDU Volume destination parameters '%s' not defined")
2165 self
.log
.error(err_str
)
2166 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2167 if not volume
.guest_params
.has_field('device_type'):
2168 err_str
= ("VDU Volume destination type '%s' not defined")
2169 self
.log
.error(err_str
)
2170 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2171 if volume
.guest_params
.device_type
not in ['disk', 'cdrom'] :
2172 err_str
= ("VDU Volume destination type '%s' not supported" % volume
.guest_params
.device_type
)
2173 self
.log
.error(err_str
)
2174 raise OpenstackCALOperationFailure("Create-vdu operation failed. Error- %s" % err_str
)
2178 if vdu_init
.has_field('server_group'):
2179 ### Get list of server group in openstack for name->id mapping
2180 openstack_group_list
= drv
.nova_server_group_list()
2181 group_id
= [ i
['id'] for i
in openstack_group_list
if i
['name'] == vdu_init
.server_group
.name
]
2182 if len(group_id
) != 1:
2183 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
]))
2184 server_group
= group_id
[0]
2186 pci_assignement
= self
.prepare_vpci_metadata(drv
, vdu_init
)
2187 if pci_assignement
!= '':
2188 vm
.user_tags
.pci_assignement
= pci_assignement
2190 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
)
2193 def prepare_vpci_metadata(self
, drv
, vdu_init
):
2194 pci_assignement
= ''
2195 ### TEF specific metadata creation for
2200 ### For MGMT interface
2201 if vdu_init
.has_field('mgmt_vpci'):
2202 xx
= 'u\''+ drv
._mgmt
_network
_id
+ '\' :[[u\'' + vdu_init
.mgmt_vpci
+ '\', ' + '\'\']]'
2203 virtio_vpci
.append(xx
)
2205 for c_point
in vdu_init
.connection_points
:
2206 if c_point
.has_field('vpci'):
2207 if c_point
.has_field('vpci') and c_point
.type_yang
== 'VIRTIO':
2208 xx
= 'u\''+c_point
.virtual_link_id
+ '\' :[[u\'' + c_point
.vpci
+ '\', ' + '\'\']]'
2209 virtio_vpci
.append(xx
)
2210 elif c_point
.has_field('vpci') and c_point
.type_yang
== 'SR_IOV':
2211 xx
= '[u\'' + c_point
.vpci
+ '\', ' + '\'\']'
2212 sriov_vpci
.append(xx
)
2215 virtio_meta
+= ','.join(virtio_vpci
)
2218 sriov_meta
= 'u\'VF\': ['
2219 sriov_meta
+= ','.join(sriov_vpci
)
2222 if virtio_meta
!= '':
2223 pci_assignement
+= virtio_meta
2224 pci_assignement
+= ','
2226 if sriov_meta
!= '':
2227 pci_assignement
+= sriov_meta
2229 if pci_assignement
!= '':
2230 pci_assignement
= '{' + pci_assignement
+ '}'
2232 return pci_assignement
2236 def prepare_vdu_on_boot(self
, account
, server_id
, floating_ip
, volumes
=None):
2237 cmd
= PREPARE_VM_CMD
.format(auth_url
= account
.openstack
.auth_url
,
2238 username
= account
.openstack
.key
,
2239 password
= account
.openstack
.secret
,
2240 tenant_name
= account
.openstack
.tenant
,
2241 mgmt_network
= account
.openstack
.mgmt_network
,
2242 server_id
= server_id
)
2244 if floating_ip
is not None:
2245 cmd
+= (" --floating_ip "+ floating_ip
.ip
)
2247 vol_metadata
= False
2248 if volumes
is not None:
2249 for volume
in volumes
:
2250 if volume
.guest_params
.has_field('custom_meta_data'):
2254 if vol_metadata
is True:
2256 with tempfile
.NamedTemporaryFile(mode
='w', delete
=False) as tmp_file
:
2258 for volume
in volumes
:
2259 vol_dict
= volume
.as_dict()
2260 vol_list
.append(vol_dict
)
2262 yaml
.dump(vol_list
, tmp_file
)
2263 cmd
+= (" --vol_metadata {}").format(tmp_file
.name
)
2265 exec_path
= 'python3 ' + os
.path
.dirname(openstack_drv
.__file
__)
2266 exec_cmd
= exec_path
+'/'+cmd
2267 self
.log
.info("Running command: %s" %(exec_cmd))
2268 subprocess
.call(exec_cmd
, shell
=True)
2271 def do_modify_vdu(self
, account
, vdu_modify
):
2272 """Modify Properties of existing virtual deployment unit
2275 account - a cloud account
2276 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
2278 ### First create required number of ports aka connection points
2281 for c_point
in vdu_modify
.connection_points_add
:
2282 if c_point
.virtual_link_id
in network_list
:
2283 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"
2285 network_list
.append(c_point
.virtual_link_id
)
2286 port_id
= self
._create
_connection
_point
(account
, c_point
)
2287 port_list
.append(port_id
)
2289 ### Now add the ports to VM
2290 for port_id
in port_list
:
2291 with self
._use
_driver
(account
) as drv
:
2292 drv
.nova_server_add_port(vdu_modify
.vdu_id
, port_id
)
2294 ### Delete the requested connection_points
2295 for c_point
in vdu_modify
.connection_points_remove
:
2296 self
.do_delete_port(account
, c_point
.connection_point_id
, no_rwstatus
=True)
2298 if vdu_modify
.has_field('image_id'):
2299 with self
._use
_driver
(account
) as drv
:
2300 drv
.nova_server_rebuild(vdu_modify
.vdu_id
, vdu_modify
.image_id
)
2304 def do_delete_vdu(self
, account
, vdu_id
):
2305 """Delete a virtual deployment unit
2308 account - a cloud account
2309 vdu_id - id for the vdu to be deleted
2315 self
.log
.error("empty vdu_id during the vdu deletion")
2318 with self
._use
_driver
(account
) as drv
:
2319 ### Get list of floating_ips associated with this instance and delete them
2320 floating_ips
= [ f
for f
in drv
.nova_floating_ip_list() if f
.instance_id
== vdu_id
]
2321 for f
in floating_ips
:
2322 drv
.nova_drv
.floating_ip_delete(f
)
2324 ### Get list of port on VM and delete them.
2325 port_list
= drv
.neutron_port_list(**{'device_id': vdu_id
})
2327 for port
in port_list
:
2328 if ((port
['device_owner'] == 'compute:None') or (port
['device_owner'] == '')):
2329 self
.do_delete_port(account
, port
['id'], no_rwstatus
=True)
2331 self
.do_delete_vm(account
, vdu_id
, no_rwstatus
=True)
2334 @rwstatus(ret_on_failure
=[None])
2335 def do_get_vdu(self
, account
, vdu_id
):
2336 """Get information about a virtual deployment unit.
2339 account - a cloud account
2340 vdu_id - id for the vdu
2343 Object of type RwcalYang.VDUInfoParams
2345 with self
._use
_driver
(account
) as drv
:
2346 port_list
= drv
.neutron_port_list(**{'device_id': vdu_id
})
2348 vm
= drv
.nova_server_get(vdu_id
)
2351 if ('flavor' in vm
) and ('id' in vm
['flavor']):
2353 flavor_info
= drv
.nova_flavor_get(vm
['flavor']['id'])
2354 except Exception as e
:
2355 self
.log
.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm
['flavor']['id'], str(e
)))
2357 openstack_group_list
= drv
.nova_server_group_list()
2358 server_group
= [ i
['name'] for i
in openstack_group_list
if vm
['id'] in i
['members']]
2359 openstack_srv_volume_list
= drv
.nova_volume_list(vm
['id'])
2360 vdu_info
= RwcalOpenstackPlugin
._fill
_vdu
_info
(drv
, vm
,
2362 account
.openstack
.mgmt_network
,
2365 volume_list
= openstack_srv_volume_list
)
2366 if vdu_info
.state
== 'active':
2368 console_info
= drv
.nova_server_console(vdu_info
.vdu_id
)
2369 except Exception as e
:
2372 vdu_info
.console_url
= console_info
['console']['url']
2378 @rwstatus(ret_on_failure
=[None])
2379 def do_get_vdu_list(self
, account
):
2380 """Get information about all the virtual deployment units
2383 account - a cloud account
2386 A list of objects of type RwcalYang.VDUInfoParams
2388 vnf_resources
= RwcalYang
.VNFResources()
2389 with self
._use
_driver
(account
) as drv
:
2390 vms
= drv
.nova_server_list()
2392 port_list
= drv
.neutron_port_list(**{'device_id': vm
['id']})
2396 if ('flavor' in vm
) and ('id' in vm
['flavor']):
2398 flavor_info
= drv
.nova_flavor_get(vm
['flavor']['id'])
2399 except Exception as e
:
2400 self
.log
.critical("Exception encountered while attempting to get flavor info for flavor_id: %s. Exception: %s" %(vm
['flavor']['id'], str(e
)))
2405 openstack_group_list
= drv
.nova_server_group_list()
2406 server_group
= [ i
['name'] for i
in openstack_group_list
if vm
['id'] in i
['members']]
2408 openstack_srv_volume_list
= drv
.nova_volume_list(vm
['id'])
2409 vdu
= RwcalOpenstackPlugin
._fill
_vdu
_info
(drv
, vm
,
2411 account
.openstack
.mgmt_network
,
2414 volume_list
= openstack_srv_volume_list
)
2415 if vdu
.state
== 'active':
2417 console_info
= drv
.nova_server_console(vdu
.vdu_id
)
2418 except Exception as e
:
2421 vdu
.console_url
= console_info
['console']['url']
2423 vnf_resources
.vdu_info_list
.append(vdu
)
2424 return vnf_resources