diff --git a/cloudcafe/blockstorage/volumes_api/common/config.py b/cloudcafe/blockstorage/volumes_api/common/config.py index a8d6f99d..39e71306 100644 --- a/cloudcafe/blockstorage/volumes_api/common/config.py +++ b/cloudcafe/blockstorage/volumes_api/common/config.py @@ -382,3 +382,9 @@ class VolumesAPIConfig(ConfigSectionInterface): def allow_snapshot_restore_to_different_type(self): return self.get_boolean( "allow_snapshot_restore_to_different_type", False) + + + @property + def primary_bootable_volume(self): + """Introducing this property to handle VIRT-3099- SKS 11-JUL-2018.""" + return self.get("primary_bootable_volume") diff --git a/cloudcafe/compute/extensions/volumes_boot_api/client.py b/cloudcafe/compute/extensions/volumes_boot_api/client.py index 37cedac7..1fff6bba 100644 --- a/cloudcafe/compute/extensions/volumes_boot_api/client.py +++ b/cloudcafe/compute/extensions/volumes_boot_api/client.py @@ -17,7 +17,7 @@ limitations under the License. from cafe.engine.http.client import AutoMarshallingHTTPClient from cloudcafe.compute.extensions.volumes_boot_api.models.request import \ - CreateServerFromVolume + CreateServerFromVolume, CreateServerFromVolumeVirt2837 from cloudcafe.compute.servers_api.models.servers import Server @@ -118,3 +118,74 @@ class VolumesBootClient(AutoMarshallingHTTPClient): request_entity=server_request_object, requestslib_kwargs=requestslib_kwargs) return resp + + + def create_server_virt2837(self, name, flavor_ref, block_device_mapping_v1, + max_count=None, min_count=None, networks=None, + image_ref=None, personality=None, user_data=None, + metadata=None, accessIPv4=None, accessIPv6=None, + disk_config=None, admin_pass=None, key_name=None, + config_drive=None, scheduler_hints=None, + security_groups=None, requestslib_kwargs=None): + """ + @summary: Creates an instance of a block Version 2 server given the + provided parameters + @param name: Name of the server + @type name: String + @param flavor_ref: Identifier for the flavor used to build the server + @type flavor_ref: String + @param block_device_mapping_v2: A list of dictionaries needed for boot + from volume V2 feature + @type block_device_mapping: List + @param max_count: max_count parameter for the server. + @type max_count: String + @param min_count: min_count parameter for the server. + @type min_count: String + @param networks: Networks for the server. + @type networks: List + @param image_ref: Will be used for negative testing + @type image_ref: String + @param personality: A list of dictionaries for files to be + injected into the server. + @type personality: List + @param user_data: Config Init User data + @type user_data: String + @param metadata: A dictionary of values to be used as server metadata + @type meta: Dictionary + @param accessIPv4: IPv4 address for the server. + @type accessIPv4: String + @param accessIPv6: IPv6 address for the server. + @type accessIPv6: String + @param disk_config: MANUAL/AUTO/None + @type disk_config: String + @param admin_pass: Injected Pass + @type admin_pass: String + @param key_name: Key name + @type key_name: String + @param config_drive: false/true + @type config_drive: String + @param scheduler_hints: Target compute + @type scheduler_hints: String + @param security_groups: List of security groups for the server + @type security_groups: List of dict + @return: Response Object containing response code and + the server domain object + @rtype: Requests.response + """ + + server_request_object = CreateServerFromVolumeVirt2837( + name=name, flavor_ref=flavor_ref, + block_device_mapping=block_device_mapping_v1, + max_count=max_count, min_count=min_count, networks=networks, + image_ref=image_ref, personality=personality, user_data=user_data, + metadata=metadata, accessIPv4=accessIPv4, accessIPv6=accessIPv6, + disk_config=disk_config, admin_pass=admin_pass, key_name=key_name, + config_drive=config_drive, scheduler_hints=scheduler_hints, + security_groups=security_groups) + + url = '{base_url}/os-volumes_boot'.format(base_url=self.url) + resp = self.request('POST', url, + response_entity_type=Server, + request_entity=server_request_object, + requestslib_kwargs=requestslib_kwargs) + return resp diff --git a/cloudcafe/compute/extensions/volumes_boot_api/models/request.py b/cloudcafe/compute/extensions/volumes_boot_api/models/request.py index 534f8e04..06021a62 100644 --- a/cloudcafe/compute/extensions/volumes_boot_api/models/request.py +++ b/cloudcafe/compute/extensions/volumes_boot_api/models/request.py @@ -127,6 +127,146 @@ class CreateServerFromVolume(AutoMarshallingModel): return ''.join([Constants.XML_HEADER, ET.tostring(element)]) + +class CreateServerFromVolumeVirt2837(AutoMarshallingModel): + + def __init__(self, name, flavor_ref, block_device_mapping, + max_count=None, min_count=None, networks=None, + image_ref=None, personality=None, user_data=None, + metadata=None, accessIPv4=None, accessIPv6=None, + disk_config=None, admin_pass=None, key_name=None, + config_drive=None, scheduler_hints=None, + security_groups=None): + + super(CreateServerFromVolumeVirt2837, self).__init__() + self.name = name + self.flavor_ref = flavor_ref + self.block_device_mapping = block_device_mapping + self.max_count = max_count + self.min_count = min_count + self.networks = networks + self.image_ref = image_ref + self.personality = personality + self.user_data = user_data + self.metadata = metadata + self.accessIPv4 = accessIPv4 + self.accessIPv6 = accessIPv6 + self.disk_config = disk_config + self.admin_pass = admin_pass + self.key_name = key_name + self.config_drive = config_drive + self.scheduler_hints = scheduler_hints + self.security_groups = security_groups + + def _obj_to_json(self): + body = { + 'name': self.name, + 'flavorRef': self.flavor_ref, + 'block_device_mapping': self.block_device_mapping, + 'max_count': self.max_count, + 'min_count': self.min_count, + 'networks': self.networks, + 'imageRef': self.image_ref, + 'personality': self.personality, + 'user_data': self.user_data, + 'metadata': self.metadata, + 'accessIPv4': self.accessIPv4, + 'accessIPv6': self.accessIPv6, + 'OS-DCF:diskConfig': self.disk_config, + 'adminPass': self.admin_pass, + 'key_name': self.key_name, + 'config_drive': self.config_drive, + 'security_groups': self.security_groups + } + + body = self._remove_empty_values(body) + main_body = {'server': body} + + if self.scheduler_hints: + main_body['os:scheduler_hints'] = self.scheduler_hints + + return json.dumps(main_body) + + def _obj_to_xml(self): + elements_dict = { + 'name': self.name, + 'flavorRef': self.flavor_ref, + 'max_count': self.max_count, + 'min_count': self.min_count, + 'image_ref': self.image_ref, + 'user_data': self.user_data, + 'accessIPv4': self.accessIPv4, + 'accessIPv6': self.accessIPv6, + 'adminPass': self.admin_pass, + 'key_name': self.key_name, + 'config_drive': self.config_drive, + 'security_groups': self.security_groups + } + element = ET.Element('server') + element.set('xmlns', Constants.XML_API_NAMESPACE) + block_device_ele = ET.Element('block_device_mapping') + block_device_ele.append(BlockDeviceMappingV1VIRT2837._obj_to_xml( + self.block_device_mapping)) + element.append(block_device_ele) + if self.networks is not None: + networks_ele = ET.Element('networks') + for network_id in self.networks: + network = ET.Element('network') + network.set('uuid', network_id['uuid']) + networks_ele.append(network) + element.append(networks_ele) + if self.personality is not None: + personality_ele = ET.Element('personality') + personality_ele.append(Personality._obj_to_xml(self.personality)) + element.append(personality_ele) + if self.metadata is not None: + meta_ele = ET.Element('metadata') + for key, value in self.metadata.items(): + meta_ele.append(Metadata._dict_to_xml(key, value)) + element.append(meta_ele) + if self.disk_config is not None: + element.set('xmlns:OS-DCF', + Constants.XML_API_DISK_CONFIG_NAMESPACE) + element.set('OS-DCF:diskConfig', self.disk_config) + element = self._set_xml_etree_element(element, elements_dict) + return ''.join([Constants.XML_HEADER, ET.tostring(element)]) + +class BlockDeviceMappingV1VIRT2837(AutoMarshallingModel): + """ + @summary: Block Device Mapping Request Object for Version 1 + of boot from volume extension + """ + ROOT_TAG = 'block_device_mapping' + + def __init__(self, volume_id, device_name, delete_on_termination): + super(BlockDeviceMappingV1VIRT2837, self).__init__() + self.volume_id = volume_id + self.device_name = device_name + self.delete_on_termination = delete_on_termination + + @classmethod + def _obj_to_json(self): + body = { + 'volume_id': self.volume_id, + 'device_name': self.device_name, + 'delete_on_termination': self.delete_on_termination + } + + body = self._remove_empty_values(body) + return json.dumps({'block_device_mapping': body}) + + @classmethod + def _obj_to_xml(self, list_dicts): + device_dict = None + for device_dict in list_dicts: + device_element = ET.Element('block_device_mapping') + device_element.set('volume_id', device_dict.get('volume_id')) + device_element.set('device_name', device_dict.get('device_name')) + device_element.set('delete_on_termination', + device_dict.get('delete_on_termination')) + return device_element + + class BlockDeviceMappingV2(AutoMarshallingModel): """ @summary: Block Device Mapping Request Object for Version 2 Havana diff --git a/cloudcafe/compute/servers_api/behaviors.py b/cloudcafe/compute/servers_api/behaviors.py index a9a7d4fa..aa2faed0 100644 --- a/cloudcafe/compute/servers_api/behaviors.py +++ b/cloudcafe/compute/servers_api/behaviors.py @@ -687,6 +687,32 @@ class ServerBehaviors(BaseComputeBehavior): "type": type}] return block_device_mapping_matrix + + def create_block_device_mapping_v1_virt2837(self, device_name, volume_id, delete_on_termination): + """ + @summary: Creates Block Device mapping on the fly + @param volume_id: The uuid of the volume + @type volume_id: String + @param delete_on_termination: True or False also 0 or 1 + @type delete_on_termination: Boolean + @param device_name: Device name + @type device_name: String + @param size: Volume Size in GB + @type size: Int + @param type: snap or blank, from where the volume was created + @type type: String + @return: The Block Device Mapping + @rtype: List of dicts + """ + + # Creating block device mapping + block_device_mapping_matrix = [{ + "device_name": device_name, + "volume_id": volume_id, + "delete_on_termination": delete_on_termination}] + return block_device_mapping_matrix + + def create_block_device_mapping_v2(self, boot_index, uuid, volume_size, source_type, destination_type, delete_on_termination):