4 # Copyright 2017 RIFT.IO Inc
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
20 gi
.require_version('RwcalYang', '1.0')
21 from gi
.repository
import RwcalYang
24 class ImageValidateError(Exception):
27 class VolumeValidateError(Exception):
30 class AffinityGroupError(Exception):
34 class ComputeUtils(object):
36 Utility class for compute operations
38 epa_types
= ['vm_flavor',
44 def __init__(self
, driver
):
48 driver: object of OpenstackDriver()
57 def search_vdu_flavor(self
, vdu_params
):
59 Function to search a matching flavor for VDU instantiation
60 from already existing flavors
63 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
66 flavor_id(string): Flavor id for VDU instantiation
67 None if no flavor could be found
70 if vdu_params
.vm_flavor
.has_field('vm_flavor_name') and \
71 vdu_params
.vm_flavor
.vm_flavor_name
is not None:
72 nova_flavor_list
= self
.driver
.nova_flavor_list()
73 for flavor
in nova_flavor_list
:
74 self
.log
.debug("Flavor {} ".format(flavor
.get('name', '')))
75 if flavor
.get('name', '') == vdu_params
.vm_flavor
.vm_flavor_name
:
78 kwargs
= { 'vcpus': vdu_params
.vm_flavor
.vcpu_count
,
79 'ram' : vdu_params
.vm_flavor
.memory_mb
,
80 'disk' : vdu_params
.vm_flavor
.storage_gb
,}
82 flavors
= self
.driver
.nova_flavor_find(**kwargs
)
85 flavor_list
.append(self
.driver
.utils
.flavor
.parse_flavor_info(flv
))
87 flavor_id
= self
.driver
.utils
.flavor
.match_resource_flavor(vdu_params
, flavor_list
)
90 def select_vdu_flavor(self
, vdu_params
):
92 This function attempts to find a pre-existing flavor matching required
93 parameters for VDU instantiation. If no such flavor is found, a new one
97 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
100 flavor_id(string): Flavor id for VDU instantiation
102 flavor_id
= self
.search_vdu_flavor(vdu_params
)
103 if flavor_id
is not None:
104 self
.log
.info("Found flavor with id: %s matching requirements for VDU: %s",
105 flavor_id
, vdu_params
.name
)
108 flavor
= RwcalYang
.YangData_RwProject_Project_VimResources_FlavorinfoList()
109 flavor
.name
= str(uuid
.uuid4())
111 epa_dict
= { k
: v
for k
, v
in vdu_params
.as_dict().items()
112 if k
in ComputeUtils
.epa_types
}
114 flavor
.from_dict(epa_dict
)
116 flavor_id
= self
.driver
.nova_flavor_create(name
= flavor
.name
,
117 ram
= flavor
.vm_flavor
.memory_mb
,
118 vcpus
= flavor
.vm_flavor
.vcpu_count
,
119 disk
= flavor
.vm_flavor
.storage_gb
,
120 epa_specs
= self
.driver
.utils
.flavor
.get_extra_specs(flavor
))
123 def make_vdu_flavor_args(self
, vdu_params
):
125 Creates flavor related arguments for VDU operation
127 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
130 A dictionary {'flavor_id': <flavor-id>}
132 return {'flavor_id': self
.select_vdu_flavor(vdu_params
)}
135 def make_vdu_image_args(self
, vdu_params
):
137 Creates image related arguments for VDU operation
139 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
142 A dictionary {'image_id': <image-id>}
146 if vdu_params
.has_field('image_name'):
147 kwargs
['image_id'] = self
.resolve_image_n_validate(vdu_params
.image_name
,
148 vdu_params
.image_checksum
)
149 elif vdu_params
.has_field('image_id'):
150 kwargs
['image_id'] = vdu_params
.image_id
154 def resolve_image_n_validate(self
, image_name
, checksum
= None):
156 Resolve the image_name to image-object by matching image_name and checksum
159 image_name (string): Name of image
160 checksums (string): Checksum associated with image
162 Raises ImageValidateError in case of Errors
164 image_info
= [ i
for i
in self
.driver
._glance
_image
_list
if i
['name'] == image_name
]
167 self
.log
.error("No image with name: %s found", image_name
)
168 raise ImageValidateError("No image with name %s found" %(image_name))
170 for image
in image_info
:
171 if 'status' not in image
or image
['status'] != 'active':
172 self
.log
.error("Image %s not in active state. Current state: %s",
173 image_name
, image
['status'])
174 raise ImageValidateError("Image with name %s found in incorrect (%s) state"
175 %(image_name
, image
['status']))
176 if not checksum
or checksum
== image
['checksum']:
179 self
.log
.info("No image found with matching name: %s and checksum: %s",
180 image_name
, checksum
)
181 raise ImageValidateError("No image found with matching name: %s and checksum: %s"
182 %(image_name
, checksum
))
185 def resolve_volume_n_validate(self
, volume_ref
):
187 Resolve the volume reference
190 volume_ref (string): Name of volume reference
192 Raises VolumeValidateError in case of Errors
195 for vol
in self
.driver
._cinder
_volume
_list
:
196 voldict
= vol
.to_dict()
197 if 'display_name' in voldict
and voldict
['display_name'] == volume_ref
:
198 if 'status' in voldict
:
199 if voldict
['status'] == 'available':
202 self
.log
.error("Volume %s not in available state. Current state: %s",
203 volume_ref
, voldict
['status'])
204 raise VolumeValidateError("Volume with name %s found in incorrect (%s) state"
205 %(volume_ref
, voldict
['status']))
207 self
.log
.info("No volume found with matching name: %s ", volume_ref
)
208 raise VolumeValidateError("No volume found with matching name: %s " %(volume_ref))
210 def make_vdu_volume_args(self
, volume
, vdu_params
):
213 volume: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams_Volumes()
214 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
217 A dictionary required to create volume for VDU
219 Raises VolumeValidateError in case of Errors
223 if 'boot_priority' in volume
:
225 kwargs
['boot_index'] = volume
.boot_priority
226 if volume
.has_field("image"):
227 # Support image->volume
228 kwargs
['source_type'] = "image"
229 kwargs
['uuid'] = self
.resolve_image_n_validate(volume
.image
, volume
.image_checksum
)
230 kwargs
['delete_on_termination'] = True
231 elif "volume_ref" in volume
:
232 # Support volume-ref->volume (only ref)
234 kwargs
['source_type'] = "volume"
235 kwargs
['uuid'] = self
.resolve_volume_n_validate(volume
.volume_ref
)
236 kwargs
['delete_on_termination'] = False
238 # Support blank->volume
239 kwargs
['source_type'] = "blank"
240 kwargs
['delete_on_termination'] = True
241 kwargs
['device_name'] = volume
.name
242 kwargs
['destination_type'] = "volume"
243 kwargs
['volume_size'] = volume
.size
245 if volume
.has_field('device_type'):
246 if volume
.device_type
in ['cdrom', 'disk']:
247 kwargs
['device_type'] = volume
.device_type
249 self
.log
.error("Unsupported device_type <%s> found for volume: %s",
250 volume
.device_type
, volume
.name
)
251 raise VolumeValidateError("Unsupported device_type <%s> found for volume: %s"
252 %(volume
.device_type
, volume
.name
))
254 if volume
.has_field('device_bus'):
255 if volume
.device_bus
in ['ide', 'virtio', 'scsi']:
256 kwargs
['disk_bus'] = volume
.device_bus
258 self
.log
.error("Unsupported device_type <%s> found for volume: %s",
259 volume
.device_type
, volume
.name
)
260 raise VolumeValidateError("Unsupported device_type <%s> found for volume: %s"
261 %(volume
.device_type
, volume
.name
))
265 def make_vdu_storage_args(self
, vdu_params
):
267 Creates volume related arguments for VDU operation
270 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
273 A dictionary required for volumes creation for VDU instantiation
276 if vdu_params
.has_field('volumes'):
277 kwargs
['block_device_mapping_v2'] = list()
278 bootvol_list
= list()
279 othervol_list
= list()
280 # Ignore top-level image
281 kwargs
['image_id'] = ""
282 for volume
in vdu_params
.volumes
:
283 if 'boot_priority' in volume
:
284 bootvol_list
.append(self
.make_vdu_volume_args(volume
, vdu_params
))
286 othervol_list
.append(self
.make_vdu_volume_args(volume
, vdu_params
))
287 # Sort block_device_mapping_v2 list by boot index, Openstack does not seem to respecting order by boot index
288 kwargs
['block_device_mapping_v2'] = sorted(bootvol_list
, key
=lambda k
: k
['boot_index']) + othervol_list
291 def make_vdu_network_args(self
, vdu_params
):
293 Creates VDU network related arguments for VDU operation
295 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
298 A dictionary {'port_list' : [ports], 'network_list': [networks]}
302 kwargs
['port_list'], kwargs
['network_list'] = self
.driver
.utils
.network
.setup_vdu_networking(vdu_params
)
307 def make_vdu_boot_config_args(self
, vdu_params
):
309 Creates VDU boot config related arguments for VDU operation
311 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
315 'userdata' : <cloud-init> ,
316 'config_drive': True/False,
317 'files' : [ file name ],
318 'metadata' : <metadata string>
324 if vdu_params
.has_field('node_id'):
325 metadata
['rift_node_id'] = vdu_params
.node_id
326 kwargs
['metadata'] = metadata
328 if vdu_params
.has_field('vdu_init') and vdu_params
.vdu_init
.has_field('userdata'):
329 kwargs
['userdata'] = vdu_params
.vdu_init
.userdata
331 kwargs
['userdata'] = ''
333 if not vdu_params
.has_field('supplemental_boot_data'):
336 if vdu_params
.supplemental_boot_data
.has_field('config_file'):
338 for cf
in vdu_params
.supplemental_boot_data
.config_file
:
339 files
[cf
.dest
] = cf
.source
340 kwargs
['files'] = files
342 if vdu_params
.supplemental_boot_data
.has_field('boot_data_drive'):
343 kwargs
['config_drive'] = vdu_params
.supplemental_boot_data
.boot_data_drive
345 kwargs
['config_drive'] = False
349 if vdu_params
.supplemental_boot_data
.has_field('custom_meta_data'):
350 for cm
in vdu_params
.supplemental_boot_data
.custom_meta_data
:
351 # Adding this condition as the list contains CLOUD_INIT Variables as
352 # well. CloudInit Variables such as password are visible on the OpenStack UI
353 # if not removed from the custom_meta_data list.
354 if cm
.destination
== 'CLOUD_METADATA':
355 metadata
[cm
.name
] = cm
.value
356 kwargs
['metadata'] = metadata
357 except Exception as e
:
362 def _select_affinity_group(self
, group_name
):
364 Selects the affinity group based on name and return its id
366 group_name (string): Name of the Affinity/Anti-Affinity group
368 Id of the matching group
370 Raises exception AffinityGroupError if no matching group is found
372 groups
= [g
['id'] for g
in self
.driver
._nova
_affinity
_group
if g
['name'] == group_name
]
374 self
.log
.error("No affinity/anti-affinity group with name: %s found", group_name
)
375 raise AffinityGroupError("No affinity/anti-affinity group with name: %s found" %(group_name))
379 def make_vdu_server_placement_args(self
, vdu_params
):
381 Function to create kwargs required for nova server placement
384 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
387 A dictionary { 'availability_zone' : < Zone >, 'scheduler_hints': <group-id> }
392 if vdu_params
.has_field('availability_zone') \
393 and vdu_params
.availability_zone
.has_field('name'):
394 kwargs
['availability_zone'] = vdu_params
.availability_zone
396 if vdu_params
.has_field('server_group'):
397 kwargs
['scheduler_hints'] = {
398 'group': self
._select
_affinity
_group
(vdu_params
.server_group
)
402 def make_vdu_server_security_args(self
, vdu_params
, account
):
404 Function to create kwargs required for nova security group
407 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
408 account: Protobuf GI object RwcalYang.YangData_RwProject_Project_CloudAccounts_CloudAccountList()
411 A dictionary {'security_groups' : < group > }
414 if account
.openstack
.security_groups
:
415 kwargs
['security_groups'] = account
.openstack
.security_groups
419 def make_vdu_create_args(self
, vdu_params
, account
):
421 Function to create kwargs required for nova_server_create API
424 vdu_params: Protobuf GI object RwcalYang.YangData_RwProject_Project_VduInitParams()
425 account: Protobuf GI object RwcalYang.YangData_RwProject_Project_CloudAccounts_CloudAccountList()
428 A kwargs dictionary for VDU create operation
432 kwargs
['name'] = vdu_params
.name
434 kwargs
.update(self
.make_vdu_flavor_args(vdu_params
))
435 kwargs
.update(self
.make_vdu_storage_args(vdu_params
))
436 kwargs
.update(self
.make_vdu_image_args(vdu_params
))
437 kwargs
.update(self
.make_vdu_network_args(vdu_params
))
438 kwargs
.update(self
.make_vdu_boot_config_args(vdu_params
))
439 kwargs
.update(self
.make_vdu_server_placement_args(vdu_params
))
440 kwargs
.update(self
.make_vdu_server_security_args(vdu_params
, account
))
444 def _parse_vdu_mgmt_address_info(self
, vm_info
):
446 Get management_ip and public_ip for VDU
449 vm_info : A dictionary object return by novaclient library listing VM attributes
452 A tuple of mgmt_ip (string) and public_ip (string)
456 if 'addresses' in vm_info
:
457 for network_name
, network_info
in vm_info
['addresses'].items():
458 if network_info
and network_name
== self
.driver
.mgmt_network
:
459 for interface
in network_info
:
460 if 'OS-EXT-IPS:type' in interface
:
461 if interface
['OS-EXT-IPS:type'] == 'fixed':
462 mgmt_ip
= interface
['addr']
463 elif interface
['OS-EXT-IPS:type'] == 'floating':
464 public_ip
= interface
['addr']
466 return (mgmt_ip
, public_ip
)
468 def get_vdu_epa_info(self
, vm_info
):
470 Get flavor information (including EPA) for VDU
473 vm_info : A dictionary returned by novaclient library listing VM attributes
475 flavor_info: A dictionary object returned by novaclient library listing flavor attributes
477 if 'flavor' in vm_info
and 'id' in vm_info
['flavor']:
479 flavor_info
= self
.driver
.nova_flavor_get(vm_info
['flavor']['id'])
481 except Exception as e
:
482 self
.log
.exception("Exception %s occured during get-flavor", str(e
))
485 def _parse_vdu_cp_info(self
, vdu_id
):
487 Get connection point information for VDU identified by vdu_id
489 vdu_id (string) : VDU Id (vm_info['id'])
491 A List of object RwcalYang.YangData_RwProject_Project_VnfResources_VduInfoList_ConnectionPoints()
495 # Fill the port information
496 port_list
= self
.driver
.neutron_port_list(**{'device_id': vdu_id
})
497 for port
in port_list
:
498 cp_info
= self
.driver
.utils
.network
._parse
_cp
(port
)
499 cp
= RwcalYang
.YangData_RwProject_Project_VnfResources_VduInfoList_ConnectionPoints()
500 cp
.from_dict(cp_info
.as_dict())
504 def _parse_vdu_state_info(self
, vm_info
):
506 Get VDU state information
509 vm_info : A dictionary returned by novaclient library listing VM attributes
512 state (string): State of the VDU
514 if 'status' in vm_info
:
515 if vm_info
['status'] == 'ACTIVE':
517 elif vm_info
['status'] == 'ERROR':
520 vdu_state
= 'inactive'
522 vdu_state
= 'unknown'
525 def _parse_vdu_server_group_info(self
, vm_info
):
527 Get VDU server group information
529 vm_info : A dictionary returned by novaclient library listing VM attributes
532 server_group_name (string): Name of the server group to which VM belongs, else empty string
535 server_group
= [ v
['name']
536 for v
in self
.driver
.nova_server_group_list()
537 if vm_info
['id'] in v
['members'] ]
539 return server_group
[0]
543 def _parse_vdu_boot_config_data(self
, vm_info
):
545 Parses VDU supplemental boot data
547 vm_info : A dictionary returned by novaclient library listing VM attributes
550 List of RwcalYang.YangData_RwProject_Project_VnfResources_VduInfoList_SupplementalBootData()
552 supplemental_boot_data
= None
554 if 'config_drive' in vm_info
:
555 supplemental_boot_data
= RwcalYang
.YangData_RwProject_Project_VnfResources_VduInfoList_SupplementalBootData()
556 supplemental_boot_data
.boot_data_drive
= vm_info
['config_drive']
557 # Look for any metadata
558 if 'metadata' not in vm_info
:
559 return node_id
, supplemental_boot_data
560 if supplemental_boot_data
is None:
561 supplemental_boot_data
= RwcalYang
.YangData_RwProject_Project_VnfResources_VduInfoList_SupplementalBootData()
562 for key
, value
in vm_info
['metadata'].items():
563 if key
== 'rift_node_id':
568 cm
= supplemental_boot_data
.custom_meta_data
.add()
570 cm
.value
= str(value
)
571 except Exception as e
:
573 return node_id
, supplemental_boot_data
575 def _parse_vdu_volume_info(self
, vm_info
):
577 Get VDU server group information
579 vm_info : A dictionary returned by novaclient library listing VM attributes
582 List of RwcalYang.YangData_RwProject_Project_VnfResources_VduInfoList_Volumes()
587 volume_list
= self
.driver
.nova_volume_list(vm_info
['id'])
588 except Exception as e
:
589 self
.log
.exception("Exception %s occured during nova-volume-list", str(e
))
592 for v
in volume_list
:
593 volume
= RwcalYang
.YangData_RwProject_Project_VnfResources_VduInfoList_Volumes()
595 volume
.name
= (v
['device']).split('/')[2]
596 volume
.volume_id
= v
['volumeId']
597 details
= self
.driver
.cinder_volume_get(volume
.volume_id
)
602 for k
, v
in details
.metadata
.items():
603 vd
= volume
.custom_meta_data
.add()
606 except Exception as e
:
608 except Exception as e
:
609 self
.log
.exception("Exception %s occured during volume list parsing", str(e
))
612 volumes
.append(volume
)
615 def _parse_vdu_console_url(self
, vm_info
):
619 vm_info : A dictionary returned by novaclient library listing VM attributes
622 console_url(string): Console URL for VM
625 if self
._parse
_vdu
_state
_info
(vm_info
) == 'active':
627 serv_console_url
= self
.driver
.nova_server_console(vm_info
['id'])
628 if 'console' in serv_console_url
:
629 console_url
= serv_console_url
['console']['url']
631 self
.log
.error("Error fetching console url. This could be an Openstack issue. Console : " + str(serv_console_url
))
634 except Exception as e
:
635 self
.log
.warning("Exception %s occured during volume list parsing", str(e
))
638 def parse_cloud_vdu_info(self
, vm_info
):
640 Parse vm_info dictionary (return by python-client) and put values in GI object for VDU
643 vm_info : A dictionary object return by novaclient library listing VM attributes
646 Protobuf GI Object of type RwcalYang.YangData_RwProject_Project_VnfResources_VduInfoList()
648 vdu
= RwcalYang
.YangData_RwProject_Project_VnfResources_VduInfoList()
649 vdu
.name
= vm_info
['name']
650 vdu
.vdu_id
= vm_info
['id']
651 vdu
.cloud_type
= 'openstack'
653 if 'image' in vm_info
and 'id' in vm_info
['image']:
654 vdu
.image_id
= vm_info
['image']['id']
656 if 'availability_zone' in vm_info
:
657 vdu
.availability_zone
= vm_info
['availability_zone']
659 vdu
.state
= self
._parse
_vdu
_state
_info
(vm_info
)
660 management_ip
,public_ip
= self
._parse
_vdu
_mgmt
_address
_info
(vm_info
)
663 vdu
.management_ip
= management_ip
666 vdu
.public_ip
= public_ip
668 if 'flavor' in vm_info
and 'id' in vm_info
['flavor']:
669 vdu
.flavor_id
= vm_info
['flavor']['id']
670 flavor_info
= self
.get_vdu_epa_info(vm_info
)
671 if flavor_info
is not None:
672 vm_flavor
= self
.driver
.utils
.flavor
.parse_vm_flavor_epa_info(flavor_info
)
673 guest_epa
= self
.driver
.utils
.flavor
.parse_guest_epa_info(flavor_info
)
674 host_epa
= self
.driver
.utils
.flavor
.parse_host_epa_info(flavor_info
)
675 host_aggregates
= self
.driver
.utils
.flavor
.parse_host_aggregate_epa_info(flavor_info
)
677 vdu
.vm_flavor
.from_dict(vm_flavor
.as_dict())
678 vdu
.guest_epa
.from_dict(guest_epa
.as_dict())
679 vdu
.host_epa
.from_dict(host_epa
.as_dict())
680 for aggr
in host_aggregates
:
681 ha
= vdu
.host_aggregate
.add()
682 ha
.from_dict(aggr
.as_dict())
684 node_id
, boot_data
= self
._parse
_vdu
_boot
_config
_data
(vm_info
)
686 vdu
.node_id
= node_id
688 vdu
.supplemental_boot_data
= boot_data
690 cp_list
= self
._parse
_vdu
_cp
_info
(vdu
.vdu_id
)
692 vdu
.connection_points
.append(cp
)
694 vdu
.server_group
.name
= self
._parse
_vdu
_server
_group
_info
(vm_info
)
696 for v
in self
._parse
_vdu
_volume
_info
(vm_info
):
697 vdu
.volumes
.append(v
)
699 vdu
.console_url
= self
._parse
_vdu
_console
_url
(vm_info
)
703 def perform_vdu_network_cleanup(self
, vdu_id
):
705 This function cleans up networking resources related to VDU
707 vdu_id(string): VDU id
711 ### Get list of floating_ips associated with this instance and delete them
712 floating_ips
= [ f
for f
in self
.driver
.nova_floating_ip_list() if f
.instance_id
== vdu_id
]
713 for f
in floating_ips
:
714 self
.driver
.nova_floating_ip_delete(f
)
716 ### Get list of port on VM and delete them.
717 port_list
= self
.driver
.neutron_port_list(**{'device_id': vdu_id
})
719 for port
in port_list
:
720 self
.driver
.neutron_port_delete(port
['id'])