# Copyright 2018 Intel, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import jsonschema import logging from sushy import exceptions from sushy.resources import base from sushy import utils from rsd_lib.resources.v2_3.storage_service import volume_schemas from rsd_lib import utils as rsd_lib_utils LOG = logging.getLogger(__name__) class StatusField(base.CompositeField): state = base.Field('State') health = base.Field('Health') health_rollup = base.Field('HealthRollup') class CapacitySourcesField(base.ListField): providing_pools = base.Field('ProvidingPools', adapter=utils.get_members_identities) allocated_Bytes = base.Field( ['ProvidedCapacity', 'Data', 'AllocatedBytes'], adapter=rsd_lib_utils.num_or_none) class LinksField(base.CompositeField): endpoints = base.Field(['Oem', 'Intel_RackScale', 'Endpoints'], default=(), adapter=utils.get_members_identities) """Link to related endpoints of this volume""" metrics = base.Field(['Oem', 'Intel_RackScale', 'Metrics'], adapter=rsd_lib_utils.get_resource_identity) """Link to telemetry metrics of this volume""" class IdentifiersField(base.ListField): durable_name = base.Field('DurableName') durable_name_format = base.Field('DurableNameFormat') class ReplicaInfosField(base.ListField): replica_readonly_access = base.Field('ReplicaReadOnlyAccess') replica_type = base.Field('ReplicaType') replica_role = base.Field('ReplicaRole') replica = base.Field('Replica', adapter=rsd_lib_utils.get_resource_identity) class InitializeActionField(base.CompositeField): target_uri = base.Field('target', required=True) class VolumeActionsField(base.CompositeField): initialize = InitializeActionField('#Volume.Initialize') class Volume(base.ResourceBase): identity = base.Field('Id', required=True) """The volume identity string""" description = base.Field('Description') """The volume description string""" name = base.Field('Name') """The volume name string""" model = base.Field('Model') """The volume model""" manufacturer = base.Field('Manufacturer') """The volume manufacturer""" access_capabilities = base.Field('AccessCapabilities') """The access capabilities of volume""" capacity_bytes = base.Field('CapacityBytes', adapter=rsd_lib_utils.num_or_none) """The capacity of volume in bytes""" allocated_Bytes = base.Field(['Capacity', 'Data', 'AllocatedBytes'], adapter=rsd_lib_utils.num_or_none) """The allocated capacity of volume in bytes""" capacity_sources = CapacitySourcesField('CapacitySources') """The logical drive status""" identifiers = IdentifiersField('Identifiers') """These identifiers list of this volume""" links = LinksField('Links') """These links to related components of this volume""" replica_infos = ReplicaInfosField('ReplicaInfos') """These replica related info of this volume""" status = StatusField('Status') """The volume status""" bootable = base.Field(['Oem', 'Intel_RackScale', 'Bootable'], adapter=bool) """The bootable info of this volume""" erased = base.Field(['Oem', 'Intel_RackScale', 'Erased']) """The erased info of this volume""" erase_on_detach = base.Field(['Oem', 'Intel_RackScale', 'EraseOnDetach'], adapter=bool) """The rrase on detach info of this volume""" _actions = VolumeActionsField('Actions', required=True) def __init__(self, connector, identity, redfish_version=None): """A class representing a Volume :param connector: A Connector instance :param identity: The identity of the Volume resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. """ super(Volume, self).__init__(connector, identity, redfish_version) def update(self, bootable=None, erased=None): """Update volume properties :param bootable: Change bootable ability of the volume :param erased: Provide information if the drive was erased :raises: BadRequestError if at least one param isn't specified """ if bootable is None and erased is None: raise ValueError('At least "bootable" or "erased" parameter has ' 'to be specified') if bootable and not isinstance(bootable, bool): raise exceptions.InvalidParameterValueError( parameter='bootable', value=bootable, valid_values=[True, False]) if erased and not isinstance(erased, bool): raise exceptions.InvalidParameterValueError( parameter='erased', value=erased, valid_values=[True, False]) data = {'Oem': {'Intel_RackScale': {}}} if bootable is not None: data['Oem']['Intel_RackScale']['Bootable'] = bootable if erased is not None: data['Oem']['Intel_RackScale']['Erased'] = erased self._conn.patch(self.path, data=data) def _get_initialize_action_element(self): initialize_action = self._actions.initialize if not initialize_action: raise exceptions.MissingActionError( action='#Volume.Initialize', resource=self._path) return initialize_action def initialize(self, init_type): """Change initialize type of this volume :param type: volume initialize type :raises: InvalidParameterValueError if invalid "type" parameter """ allowed_init_type_values = ['Fast', 'Slow'] if init_type not in allowed_init_type_values: raise exceptions.InvalidParameterValueError( parameter='init_type', value=init_type, valid_values=allowed_init_type_values) data = {"InitializeType": init_type} target_uri = self._get_initialize_action_element().target_uri self._conn.post(target_uri, data=data) def delete(self): """Delete this volume""" self._conn.delete(self.path) class VolumeCollection(base.ResourceCollectionBase): @property def _resource_type(self): return Volume def __init__(self, connector, path, redfish_version=None): """A class representing a VolumeCollection :param connector: A Connector instance :param path: The canonical path to the Volume collection resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. """ super(VolumeCollection, self).__init__(connector, path, redfish_version) def _create_volume_request(self, capacity, access_capabilities=None, capacity_sources=None, replica_infos=None, bootable=None): request = {} jsonschema.validate(capacity, volume_schemas.capacity_req_schema) request['CapacityBytes'] = capacity if access_capabilities is not None: jsonschema.validate( access_capabilities, volume_schemas.access_capabilities_req_schema) request['AccessCapabilities'] = access_capabilities if capacity_sources is not None: jsonschema.validate(capacity_sources, volume_schemas.capacity_sources_req_schema) request['CapacitySources'] = capacity_sources if replica_infos is not None: jsonschema.validate(replica_infos, volume_schemas.replica_infos_req_schema) request['ReplicaInfos'] = replica_infos if bootable is not None: jsonschema.validate(bootable, volume_schemas.bootable_req_schema) request['Oem'] = {"Intel_RackScale": {"Bootable": bootable}} return request def create_volume(self, capacity, access_capabilities=None, capacity_sources=None, replica_infos=None, bootable=None): """Create a new volume :param capacity: Requested volume capacity in bytes :param access_capabilities: List of volume access capabilities :param capacity_sources: JSON for volume providing source :param replica_infos: JSON for volume replica infos :param bootable: Determines if the volume should be bootable :returns: The uri of the new volume """ properties = self._create_volume_request( capacity=capacity, access_capabilities=access_capabilities, capacity_sources=capacity_sources, replica_infos=replica_infos, bootable=bootable) resp = self._conn.post(self._path, data=properties) LOG.info("Volume created at %s", resp.headers['Location']) volume_url = resp.headers['Location'] return volume_url[volume_url.find(self._path):]