diff --git a/common-powervc/powervc/common/utils.py b/common-powervc/powervc/common/utils.py index f2af53e..a335f72 100644 --- a/common-powervc/powervc/common/utils.py +++ b/common-powervc/powervc/common/utils.py @@ -503,6 +503,12 @@ class Utils(object): return accessible_storage_providers + def get_all_storage_templates(self): + """ + Get all storage templates from PowerVC + """ + return self._cinderclient.volume_types.list_all_storage_templates() + def get_multi_scg_accessible_storage_templates(self, scg_uuid_list, scg_name_list): @@ -587,6 +593,37 @@ class Utils(object): (accessible_storage_templates))) return accessible_storage_templates + def get_scg_accessible_storage_templates_extended(self, + scg_list, + all_storage_templates): + + """ + Get a dict that stores the scgs and the corresponding + accessible storage templates . The key is scg id , + and the value is the storage templates. + + :param: scg_list : All available Storage Connectivity Groups + :param: all_storage_templates : All available storage + templates from PowerVC + :return: The dict to store the scgs and + the corresponding storage templates + """ + if scg_list is not None: + scg_storage_templates = {} + for scg in scg_list: + accessible_storage_templates = [] + volume_types = scg.list_all_volume_types() + volume_type_ids = [] + for vol_type in volume_types: + volume_type_ids.append(vol_type.__dict__.get("id")) + for storage_template in all_storage_templates: + if(storage_template.__dict__.get("id") in volume_type_ids): + accessible_storage_templates.append(storage_template) + scg_storage_templates[scg.id] = accessible_storage_templates + return scg_storage_templates + else: + return {} + def get_multi_scg_accessible_volumes(self, scg_uuid_list, scg_name_list, @@ -770,6 +807,30 @@ class Utils(object): LOG.debug(_('Unable to find staging user: %s'), staginguser) raise exception.StagingUserNotFound(name=staginguser) + def get_image_scgs_dict(self, scg_list): + """ + Get a dict that store the image and the corresponding scg list . + The key is the image UUID , and the value is the corresponding scg list + + :param scg_list : All available Storage Connectivity Groups + :returns The dict to store the image and the corresponding scg list + """ + if scg_list is not None: + image_scgs_dict = {} + for scg in scg_list: + image_id_list = self.get_scg_image_ids(scg.id) + for image_id in image_id_list: + if image_id in image_scgs_dict.keys(): + scg_list = image_scgs_dict[image_id] + scg_list.append(scg) + image_scgs_dict[image_id] = scg_list + else: + scg_list = [scg] + image_scgs_dict[image_id] = scg_list + return image_scgs_dict + else: + return {} + def import_relative_module(relative_import_str, import_str): """ diff --git a/glance-powervc/powervc/glance/manager/manager.py b/glance-powervc/powervc/glance/manager/manager.py index b9d323f..9dfb697 100644 --- a/glance-powervc/powervc/glance/manager/manager.py +++ b/glance-powervc/powervc/glance/manager/manager.py @@ -17,6 +17,7 @@ from powervc.common import config from nova.openstack.common import service from nova.openstack.common import log as logging from nova.openstack.common import timeutils +from nova.openstack.common import jsonutils from glanceclient.v1 import images as v1images from glanceclient.exc import CommunicationError from glanceclient.exc import HTTPNotFound @@ -231,6 +232,11 @@ class PowerVCImageManager(service.Service): local_v2client = self._get_local_v2_client() v2local_images = local_v2client.images + # Get image_scg_dict and scg_storage_templates_dict for + # the special property image_topology format + image_scgs_dict, scg_storage_templates_dict = \ + self._get_dicts_for_extra_image_property() + # When catching exceptions during sync operations we will look for # CommunicationError, and raise those so we don't waste time # trying to process all images when there is a connection failure. @@ -277,6 +283,15 @@ class PowerVCImageManager(service.Service): if uuid in self.ids_dict.keys(): self.ids_dict.pop(uuid) else: + # Add an extra property named image_topology , which allows + # user to select a SCG/Storage Template when booting an VM + pvc_image = pvc_image_dict[uuid] + if pvc_image: + pvc_image = \ + self._insert_extra_property_image_topology( + pvc_image, + image_scgs_dict, + scg_storage_templates_dict) # Update the image if it has changed. (Right now, always # update it, and update all fields). Update using the @@ -326,6 +341,14 @@ class PowerVCImageManager(service.Service): # that are accessible from our Storage Connectivity # Group if status and status == 'active': + # Add an extra property named image_topology , + # which allows user to select a SCG/Storage + # Template when booting an VM + pvc_image = \ + self._insert_extra_property_image_topology( + pvc_image, + image_scgs_dict, + scg_storage_templates_dict) # Add or activate the local image self._add_or_activate_local_image( @@ -463,6 +486,11 @@ class PowerVCImageManager(service.Service): cur_local_image_set = set(local_image_dict) cur_pvc_image_set = set(pvc_image_dict) + # Get image_scg_dict and scg_storage_templates_dict for + # the special property image_topology format + image_scgs_dict, scg_storage_templates_dict = \ + self._get_dicts_for_extra_image_property() + # We only need to update sync images that are in both PowerVC and # the local hosting OS. If an image is missing from either side # it will be added or deleted, so no need to try to update it. @@ -484,6 +512,21 @@ class PowerVCImageManager(service.Service): pvc_image = pvc_image_dict[uuid] local_updated = self._local_image_updated(uuid, local_image) pvc_updated = self._pvc_image_updated(uuid, pvc_image) + + # Add an extra property named image_topology , which allows + # user to select a SCG/Storage Template when booting an VM + pvc_image = \ + self._insert_extra_property_image_topology( + pvc_image, + image_scgs_dict, + scg_storage_templates_dict) + + if 'image_topology' not in local_image.properties or \ + local_image.properties['image_topology'] != \ + pvc_image.properties['image_topology']: + if not pvc_updated: + pvc_updated = True + local_checksum = \ self._get_image_checksum(local_image.to_dict()) pvc_checksum = self._get_image_checksum(pvc_image.to_dict()) @@ -543,11 +586,18 @@ class PowerVCImageManager(service.Service): 'the local hosting OS to PowerVC'), local_image.name) + # To avoid the image property image_topology is + # synced to PowerVC side + local_image = \ + self._filter_out_image_properties(local_image, + ['image_topology']) + # Update sync local image to PowerVC updated_image = self._update_pvc_image(uuid, local_image, pvc_image, v1pvc_images, v2pvc_images) + if updated_image is None: LOG.error(_('PowerVC image \'%s\' with UUID %s was not' ' updated during periodic image ' @@ -767,6 +817,15 @@ class PowerVCImageManager(service.Service): # that are accessible on our Storage Connectivity Group if status and status == 'active': + # Add an extra property named image_topology , + # which allows user to select a SCG/Storage + # Template when booting an VM + pvc_image = \ + self._insert_extra_property_image_topology( + pvc_image, + image_scgs_dict, + scg_storage_templates_dict) + # Add or activate the local image self._add_or_activate_local_image( pvc_image, local_image_owner, @@ -1058,6 +1117,12 @@ class PowerVCImageManager(service.Service): 'merged master image to PowerVC for PowerVC UUID ' '%s'), master_image.name, uuid) + # To avoid the image property image_topology is + # synced to PowerVC side + master_image = \ + self._filter_out_image_properties(master_image, + ['image_topology']) + # Update sync master image to PowerVC LOG.debug(_('Master image for pvc: %s'), str(master_image)) updated_pvc_image = self._update_pvc_image(uuid, master_image, @@ -1238,6 +1303,7 @@ class PowerVCImageManager(service.Service): # Reset the image properties master_image.properties = master_props + master_image._info['properties'] = master_props LOG.debug(_('Master image for merge: %s'), str(master_image)) def _get_image(self, uuid, image_id, image_name, v1images, v2images): @@ -2583,6 +2649,12 @@ class PowerVCImageManager(service.Service): pvc_image.name, pvc_id) return + # To avoid the image property image_topology is + # synced to PowerVC side + local_image = \ + self._filter_out_image_properties(local_image, + ['image_topology']) + # Perform the image update to PowerVC image = self._update_pvc_image(pvc_id, local_image, pvc_image, v1pvc_images, v2pvc_images) @@ -3719,6 +3791,109 @@ class PowerVCImageManager(service.Service): self._unescape(filtered_props) return filtered_props + def _get_extra_property_image_topology(self, + imageUUID, + image_scg_dict, + scg_storage_template_dict): + """ + Get an extra image property , named "image_topology" , + which is used UI to select an available Storage + Connectivity Groups or Storage templates. + """ + if imageUUID is not None: + image_topology = [] + scg_list = image_scg_dict[imageUUID] + for scg in scg_list: + scg_topology = {} + scg_topology['scg_id'] = scg.id + scg_topology['display_name'] = scg.display_name + + scg_storage_templates = scg_storage_template_dict[scg.id] + available_storage_templates = [] + for storage_template in scg_storage_templates: + storage_template_dict = {} + storage_template_dict['id'] = storage_template.id + storage_template_dict['name'] = storage_template.name + available_storage_templates.append(storage_template_dict) + if available_storage_templates: + scg_topology['storage_template_list'] = \ + available_storage_templates + image_topology.append(scg_topology) + + json_image_topology = jsonutils.dumps(image_topology) + return json_image_topology + else: + return [] + + def _insert_extra_property_image_topology(self, + image, + image_scg_dict, + scg_storage_template_dict): + """ + Insert or Update the extra property "image_topology" for + the image object and return the image object. + + :param: image The image object that need to be updated + :param: image_scg_dict: A dict to store image UUID and + the corresponding scg list + :param: scg_storage_template_dict: A dict to store scg UUID and + the corresponding storage template list + :return The image object with the property "image_topology" updated + """ + if image is not None and \ + image_scg_dict is not None and \ + scg_storage_template_dict: + + image_topology_prop = \ + self._get_extra_property_image_topology( + image.id, + image_scg_dict, + scg_storage_template_dict) + image_properties = self._get_image_properties(image.to_dict()) + image_properties[u'image_topology'] = unicode(image_topology_prop) + image.properties = image_properties + image._info['properties'] = image_properties + return image + else: + return image + + def _filter_out_image_properties(self, image, props): + """ + Delete the properties in the props list for the image object and return + the image object without these properties. + + :param: image The image object that need to be filtered out + :param: props The properties need to delete + :return The image object without these specific properties in the props + """ + if image is not None and props is not None: + image_properties = self._get_image_properties(image.to_dict()) + for prop in props: + if prop in image_properties.keys(): + del(image_properties[prop]) + image.properties = image_properties + image._info['properties'] = image_properties + return image + else: + return image + + def _get_dicts_for_extra_image_property(self): + """ + Get two dict to format the extra property "image_topology" , one dict + stores the image UUID and the corresponding scg list , the other stores + the scg UUID and the corresponding storage templates list. + """ + available_scg_list = utils.get_utils().get_our_scg_list() + available_storage_template = \ + utils.get_utils().get_all_storage_templates() + image_scgs_dict = \ + utils.get_utils().get_image_scgs_dict(available_scg_list) + scg_storage_templates_dict = \ + utils.get_utils().\ + get_scg_accessible_storage_templates_extended( + available_scg_list, available_storage_template) + return image_scgs_dict, scg_storage_templates_dict + class ImageSyncController(): """ diff --git a/nova-powervc/powervc/nova/driver/compute/constants.py b/nova-powervc/powervc/nova/driver/compute/constants.py index b0b333f..5083587 100644 --- a/nova-powervc/powervc/nova/driver/compute/constants.py +++ b/nova-powervc/powervc/nova/driver/compute/constants.py @@ -14,6 +14,7 @@ PVM_HYPERVISOR_TYPE = "powervm" # Flavor constants SCG_KEY = "powervm:storage_connectivity_group" +STORAGE_TEMPLATE_KEY = "powervm:boot_volume_type" EXTRA_SPECS = "extra_specs" IS_PUBLIC = "os-flavor-access:is_public" diff --git a/nova-powervc/powervc/nova/driver/virt/powervc/service.py b/nova-powervc/powervc/nova/driver/virt/powervc/service.py index ef4170f..8c6aad6 100644 --- a/nova-powervc/powervc/nova/driver/virt/powervc/service.py +++ b/nova-powervc/powervc/nova/driver/virt/powervc/service.py @@ -653,11 +653,22 @@ class PowerVCService(object): """ createdServer = None - self.validate_update_scg(flavorDict) - # extract activation data from instance meta = instance._metadata key_name = instance.key_name + + extra_specs_key = constants.EXTRA_SPECS + scg_key = constants.SCG_KEY + storage_template_key = constants.STORAGE_TEMPLATE_KEY + + if 'selected-scg' in meta.keys() and \ + 'selected-storage-template' in meta.keys(): + flavorDict[extra_specs_key][scg_key] = meta['selected-scg'] + flavorDict[extra_specs_key][storage_template_key] = \ + meta['selected-storage-template'] + + self.validate_update_scg(flavorDict) + # key_data = instance.key_data config_drive = instance._config_drive userdata = instance.user_data # already base64 encoded by local OS