From f04f4a743a921a3227d453e951b8ea5cd0e4a811 Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Wed, 11 Apr 2018 12:45:20 -0700 Subject: [PATCH] Support to create new volume Change-Id: I909820d590a1f95fe4d3cd4a53ac021ac5dbc6d4 --- .../resources/v2_3/storage_service/volume.py | 56 ++++++++++ .../v2_3/storage_service/volume_schemas.py | 66 ++++++++++++ .../v2_3/storage_service/test_volume.py | 101 ++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 rsd_lib/resources/v2_3/storage_service/volume_schemas.py diff --git a/rsd_lib/resources/v2_3/storage_service/volume.py b/rsd_lib/resources/v2_3/storage_service/volume.py index d4d6b7c..f92ecfe 100644 --- a/rsd_lib/resources/v2_3/storage_service/volume.py +++ b/rsd_lib/resources/v2_3/storage_service/volume.py @@ -13,12 +13,14 @@ # 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__) @@ -207,3 +209,57 @@ class VolumeCollection(base.ResourceCollectionBase): """ 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): + """Compose a node from RackScale hardware + + :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):] diff --git a/rsd_lib/resources/v2_3/storage_service/volume_schemas.py b/rsd_lib/resources/v2_3/storage_service/volume_schemas.py new file mode 100644 index 0000000..83b4894 --- /dev/null +++ b/rsd_lib/resources/v2_3/storage_service/volume_schemas.py @@ -0,0 +1,66 @@ +# Copyright (c) 2018 Intel, Corp. +# +# 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. + +capacity_req_schema = { + 'type': 'number' +} + +access_capabilities_req_schema = { + 'type': 'array', + 'items': { + 'type': 'string', + 'enum': ['Read', 'Write', 'WriteOnce', 'Append', 'Streaming'] + } +} + +capacity_sources_req_schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'ProvidingPools': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + '@odata.id': {'type': 'string'} + }, + 'additionalProperties': False + } + } + }, + 'additionalProperties': False + } +} + +replica_infos_req_schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'ReplicaType': {'type': 'string'}, + 'Replica': { + 'type': 'object', + 'properties': { + '@odata.id': {'type': 'string'} + } + } + }, + 'additionalProperties': False + } +} + +bootable_req_schema = { + 'type': 'boolean' +} diff --git a/rsd_lib/tests/unit/resources/v2_3/storage_service/test_volume.py b/rsd_lib/tests/unit/resources/v2_3/storage_service/test_volume.py index e789dc6..5fa69b1 100644 --- a/rsd_lib/tests/unit/resources/v2_3/storage_service/test_volume.py +++ b/rsd_lib/tests/unit/resources/v2_3/storage_service/test_volume.py @@ -14,12 +14,14 @@ # under the License. import json +import jsonschema import mock import testtools from sushy import exceptions from rsd_lib.resources.v2_3.storage_service import volume +from rsd_lib.tests.unit.fakes import request_fakes class StorageServiceTestCase(testtools.TestCase): @@ -165,6 +167,10 @@ class VolumeCollectionTestCase(testtools.TestCase): with open('rsd_lib/tests/unit/json_samples/v2_3/' 'volume_collection.json', 'r') as f: self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.conn.post.return_value = request_fakes.fake_request_post( + None, headers={"Location": "https://localhost:8443/redfish/v1/" + "StorageServices/NVMeoE1/Volumes/2"}) + self.volume_col = volume.VolumeCollection( self.conn, '/redfish/v1/StorageServices/NVMeoE1/Volumes', redfish_version='1.0.2') @@ -195,3 +201,98 @@ class VolumeCollectionTestCase(testtools.TestCase): redfish_version=self.volume_col.redfish_version) self.assertIsInstance(members, list) self.assertEqual(1, len(members)) + + def test_create_volume(self): + reqs = { + "AccessCapabilities": [ + "Read", + "Write" + ], + "CapacityBytes": 10737418240, + "CapacitySources": [ + { + "ProvidingPools": [ + { + "@odata.id": "/redfish/v1/StorageServices/1/" + "StoragePools/2" + } + ] + } + ], + "ReplicaInfos": [ + { + "ReplicaType": "Clone", + "Replica": { + "@odata.id": "/redfish/v1/StorageServices/NVMeoE1/" + "Volumes/1" + } + } + ], + "Oem": { + "Intel_RackScale": { + "Bootable": True + } + } + } + result = self.volume_col.create_volume( + capacity=10737418240, + access_capabilities=["Read", "Write"], + capacity_sources=[{ + "ProvidingPools": [{ + "@odata.id": "/redfish/v1/StorageServices/1/StoragePools/2" + }] + }], + replica_infos=[{ + "ReplicaType": "Clone", + "Replica": { + "@odata.id": "/redfish/v1/StorageServices/NVMeoE1/" + "Volumes/1" + } + }], + bootable=True) + self.volume_col._conn.post.assert_called_once_with( + '/redfish/v1/StorageServices/NVMeoE1/Volumes', data=reqs) + self.assertEqual(result, + '/redfish/v1/StorageServices/NVMeoE1/Volumes/2') + + def test_create_volume_with_invalid_reqs(self): + self.assertRaises(jsonschema.exceptions.ValidationError, + self.volume_col.create_volume, + capacity='invalid_capacity') + + result = self.volume_col.create_volume(capacity=1024) + self.assertEqual(result, + '/redfish/v1/StorageServices/NVMeoE1/Volumes/2') + + invalid_access_capabilities = ["Write", "invalid"] + self.assertRaises(jsonschema.exceptions.ValidationError, + self.volume_col.create_volume, + capacity=1024, + access_capabilities=invalid_access_capabilities) + + invalid_capacity_sources = [{ + "ProvidingPools": { + "@odata.id": "/redfish/v1/StorageServices/1/StoragePools/2" + } + }] + self.assertRaises(jsonschema.exceptions.ValidationError, + self.volume_col.create_volume, + capacity=1024, + capacity_sources=invalid_capacity_sources) + + invalid_replica_infos = [{ + "Invalid": "Clone", + "Replica": { + "@odata.id": "/redfish/v1/StorageServices/NVMeoE1/" + "Volumes/1" + } + }] + self.assertRaises(jsonschema.exceptions.ValidationError, + self.volume_col.create_volume, + capacity=1024, + replica_infos=invalid_replica_infos) + + self.assertRaises(jsonschema.exceptions.ValidationError, + self.volume_col.create_volume, + capacity=1024, + bootable="True")