diff --git a/doc/source/admin/emulator.conf b/doc/source/admin/emulator.conf index 7a6b37e9..c6d22eff 100644 --- a/doc/source/admin/emulator.conf +++ b/doc/source/admin/emulator.conf @@ -136,4 +136,39 @@ SUSHY_EMULATOR_DRIVES = { "Protocol": "SAS" } ] -} +} + +# This map contains dynamically configured Redfish Volume resource backed +# by the libvirt virtualization backend of the dynamic Redfish emulator. +# The Volume objects are keyed in a composite fashion using a tuple of the +# form (System_UUID, Storage_ID) referring to the UUID of the System and ID +# of the Storage resource, respectively, to which the Volume belongs. +# +# Only the volumes specified in the map or created via a POST request are +# allowed to be emulated upon by the emulator. Volumes other than these can +# neither be listed nor deleted. +# +# The Volumes from map missing in the libvirt backend will be created +# dynamically in the pool name specified (provided the pool exists in the +# backend). If the pool name is not specified, the volume will be created +# automatically in pool named 'default'. +SUSHY_EMULATOR_VOLUMES = { + ('da69abcc-dae0-4913-9a7b-d344043097c0', '1'): [ + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol", + "Id": "1", + "Name": "Sample Volume 1", + "VolumeType": "Mirrored", + "CapacityBytes": 23748 + }, + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol1", + "Id": "2", + "Name": "Sample Volume 2", + "VolumeType": "StripedWithParity", + "CapacityBytes": 48395 + } + ] +} diff --git a/doc/source/user/dynamic-emulator.rst b/doc/source/user/dynamic-emulator.rst index c38d5402..714ace28 100644 --- a/doc/source/user/dynamic-emulator.rst +++ b/doc/source/user/dynamic-emulator.rst @@ -716,3 +716,76 @@ Storage resource it belongs to. "SerialNumber": "1234570", ... } + +Storage Volume resource ++++++++++++++++++++++++ + +The *Volume* resource is emulated as a persistent emulator database +record, backed by the libvirt virtualization backend of the dynamic +Redfish emulator. + +Only the volumes specified in the config file or created via a POST request +are allowed to be emulated upon by the emulator and appear as libvirt volumes +in the libvirt virtualization backend. Volumes other than these can neither be +listed nor deleted. + +To allow libvirt volumes to be emulated upon, they need to be specified +in the configuration file in the following format (keyed compositely by +the System UUID and the Storage ID): + +.. code-block:: python + + SUSHY_EMULATOR_VOLUMES = { + ('da69abcc-dae0-4913-9a7b-d344043097c0', '1'): [ + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol", + "Id": "1", + "Name": "Sample Volume 1", + "VolumeType": "Mirrored", + "CapacityBytes": 23748 + }, + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol1", + "Id": "2", + "Name": "Sample Volume 2", + "VolumeType": "StripedWithParity", + "CapacityBytes": 48395 + } + ] + } + +The Volume resources can be revealed by querying Volumes resource +for the corresponding System and the Storage. + +.. code-block:: bash + + curl http://localhost:8000/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage/1/Volumes + { + "@odata.type": "#VolumeCollection.VolumeCollection", + "Name": "Storage Volume Collection", + "Members@odata.count": 2, + "Members": [ + { + "@odata.id": "/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage/1/Volumes/1" + }, + { + "@odata.id": "/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage/1/Volumes/2" + } + ], + "@odata.context": "/redfish/v1/$metadata#VolumeCollection.VolumeCollection", + "@odata.id": "/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage/1/Volumes", + } + +A new volume can also be created in the libvirt backend via a POST request +on a Volume Collection: + +.. code-block:: bash + + curl -d '{"Name": "SampleVol",\ + "VolumeType": "Mirrored",\ + "CapacityBytes": 74859}' \ + -H "Content-Type: application/json" \ + -X POST \ + http://localhost:8000/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage/1/Volumes diff --git a/releasenotes/notes/add-volume-resource-db795af928e41e5c.yaml b/releasenotes/notes/add-volume-resource-db795af928e41e5c.yaml new file mode 100644 index 00000000..9aa13b6b --- /dev/null +++ b/releasenotes/notes/add-volume-resource-db795af928e41e5c.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Adds Volume resource emulation support. + + As of this release, a user can configure a collection of Volumes including + the VolumeType and Capacity. The configured volumes will appear as libvirt + volumes in the libvirt virtualization backend of the dynamic Redfish + emulator (provided the libvirt pool specified for the volume exists). + + Volume creation via POST request is also supported. + + In case the Openstack backend is used, the NotSupportedError is raised. diff --git a/sushy_tools/emulator/main.py b/sushy_tools/emulator/main.py index 38beb60b..d4cc915f 100755 --- a/sushy_tools/emulator/main.py +++ b/sushy_tools/emulator/main.py @@ -31,6 +31,7 @@ from sushy_tools.emulator.resources.storage import staticdriver as stgdriver from sushy_tools.emulator.resources.systems import libvirtdriver from sushy_tools.emulator.resources.systems import novadriver from sushy_tools.emulator.resources.vmedia import staticdriver as vmddriver +from sushy_tools.emulator.resources.volumes import staticdriver as voldriver from sushy_tools import error from sushy_tools.error import FishyError @@ -48,6 +49,7 @@ class Resources(object): VMEDIA = None STORAGE = None DRIVES = None + VOLUMES = None def __new__(cls, *args, **kwargs): @@ -129,6 +131,13 @@ class Resources(object): 'Initialized drive resource backed by %s ' 'driver', cls.DRIVES().driver) + if cls.VOLUMES is None: + cls.VOLUMES = voldriver.StaticDriver.initialize(app.config) + + app.logger.debug( + 'Initialized volumes resource backed by %s ' + 'driver', cls.VOLUMES().driver) + return super(Resources, cls).__new__(cls, *args, **kwargs) def __enter__(self): @@ -139,6 +148,7 @@ class Resources(object): self.vmedia = self.VMEDIA() self.storage = self.STORAGE() self.drives = self.DRIVES() + self.volumes = self.VOLUMES() return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -149,6 +159,7 @@ class Resources(object): del self.vmedia del self.storage del self.drives + del self.volumes def instance_denied(**kwargs): @@ -734,6 +745,72 @@ def drive_resource(identity, stg_id, drv_id): return 'Not found', 404 +@app.route('/redfish/v1/Systems//Storage//Volumes', + methods=['GET', 'POST']) +@ensure_instance_access +@returns_json +def volumes_collection(identity, storage_id): + with Resources() as resources: + + uuid = resources.systems.uuid(identity) + + if flask.request.method == 'GET': + + vol_col = resources.volumes.get_volumes_col(uuid, storage_id) + + vol_ids = [] + for vol in vol_col: + vol_id = resources.systems.find_or_create_storage_volume(vol) + if not vol_id: + resources.volumes.delete_volume(uuid, storage_id, vol) + else: + vol_ids.append(vol_id) + + return flask.render_template( + 'volume_collection.json', identity=identity, + storage_id=storage_id, volume_col=vol_ids) + + elif flask.request.method == 'POST': + data = { + "Name": flask.request.json.get('Name'), + "VolumeType": flask.request.json.get('VolumeType'), + "CapacityBytes": flask.request.json.get('CapacityBytes'), + "Id": str(os.getpid()) + datetime.now().strftime("%H%M%S") + } + data['libvirtVolName'] = data['Id'] + new_id = resources.systems.find_or_create_storage_volume(data) + if new_id: + resources.volumes.add_volume(uuid, storage_id, data) + app.logger.debug('New storage volume created with ID "%s"', + new_id) + vol_url = ("/redfish/v1/Systems/%s/Storage/%s/" + "Volumes/%s" % (identity, storage_id, new_id)) + return flask.Response(status=201, + headers={'Location': vol_url}) + + +@app.route('/redfish/v1/Systems//Storage//Volumes/', + methods=['GET']) +@ensure_instance_access +@returns_json +def volume(identity, stg_id, vol_id): + with Resources() as resources: + uuid = resources.systems.uuid(identity) + vol_col = resources.volumes.get_volumes_col(uuid, stg_id) + + for vol in vol_col: + if vol['Id'] == vol_id: + vol_id = resources.systems.find_or_create_storage_volume(vol) + if not vol_id: + resources.volumes.delete_volume(uuid, stg_id, vol) + else: + return flask.render_template( + 'volume.json', identity=identity, storage_id=stg_id, + volume=vol) + + return 'Not Found', 404 + + def parse_args(): parser = argparse.ArgumentParser('sushy-emulator') parser.add_argument('--config', diff --git a/sushy_tools/emulator/resources/systems/base.py b/sushy_tools/emulator/resources/systems/base.py index 56e2e858..536d96c5 100644 --- a/sushy_tools/emulator/resources/systems/base.py +++ b/sushy_tools/emulator/resources/systems/base.py @@ -199,3 +199,13 @@ class AbstractSystemsDriver(DriverBase): :returns: dict of Simple Storage Controllers and their atributes """ + + def find_or_create_storage_volume(self, data): + """Find/create volume based on existence in the virtualization backend + + :param data: data about the volume in dict form with values for `Id`, + `Name`, `CapacityBytes`, `VolumeType`, `libvirtPoolName` + and `libvirtVolName` + + :returns: Id of the volume if successfully found/created else None + """ diff --git a/sushy_tools/emulator/resources/systems/libvirtdriver.py b/sushy_tools/emulator/resources/systems/libvirtdriver.py index 07b4fd4b..0e59ac56 100644 --- a/sushy_tools/emulator/resources/systems/libvirtdriver.py +++ b/sushy_tools/emulator/resources/systems/libvirtdriver.py @@ -125,7 +125,7 @@ class LibvirtDriver(AbstractSystemsDriver): STORAGE_VOLUME_XML = """ %(name)s - %(name)s + %(path)s %(size)i %(size)i @@ -933,3 +933,60 @@ class LibvirtDriver(AbstractSystemsDriver): simple_storage[ctl_type]['Name'] = ctl_type simple_storage[ctl_type]['DeviceList'].append(disk_device) return simple_storage + + def find_or_create_storage_volume(self, data): + """Find/create volume based on existence in the virtualization backend + + :param data: data about the volume in dict form with values for `Id`, + `Name`, `CapacityBytes`, `VolumeType`, `libvirtPoolName` + and `libvirtVolName` + + :returns: Id of the volume if successfully found/created else None + """ + with libvirt_open(self._uri) as conn: + try: + poolName = data['libvirtPoolName'] + except KeyError: + poolName = self.STORAGE_POOL + try: + pool = conn.storagePoolLookupByName(poolName) + except libvirt.libvirtError as ex: + msg = ('Error finding Storage Pool by name "%(name)s" at ' + 'libvirt URI "%(uri)s": %(err)s' % + {'name': poolName, 'uri': self._uri, 'err': ex}) + logger.debug(msg) + return + try: + vol = pool.storageVolLookupByName(data['libvirtVolName']) + except libvirt.libvirtError as ex: + + msg = ('Creating storage volume with name: "%s"', + data['libvirtVolName']) + logger.debug(msg) + + pool_tree = ET.fromstring(pool.XMLDesc()) + + # Find out path to the volume + pool_path_element = pool_tree.find('target/path') + if pool_path_element is None: + msg = ('Missing "target/path" tag in the libvirt ' + 'storage pool "%(pool)s"' + '' % {'pool': poolName}) + logger.debug(msg) + return + + vol_path = os.path.join( + pool_path_element.text, data['libvirtVolName']) + + # Create a new volume + vol = pool.createXML( + self.STORAGE_VOLUME_XML % { + 'name': data['libvirtVolName'], 'path': vol_path, + 'size': data['CapacityBytes']}) + + if not vol: + msg = ('Error creating "%s" storage volume in "%s" pool', + data['libvirtVolName'], poolName) + logger.debug(msg) + return + return data['Id'] diff --git a/sushy_tools/emulator/resources/systems/novadriver.py b/sushy_tools/emulator/resources/systems/novadriver.py index 9094820c..4095b296 100644 --- a/sushy_tools/emulator/resources/systems/novadriver.py +++ b/sushy_tools/emulator/resources/systems/novadriver.py @@ -374,3 +374,14 @@ class OpenStackDriver(AbstractSystemsDriver): def get_simple_storage_collection(self, identity): raise error.NotSupportedError('Not implemented') + + def find_or_create_storage_volume(self, data): + """Find/create volume based on existence in the virtualization backend + + :param data: data about the volume in dict form with values for `Id`, + `Name`, `CapacityBytes`, `VolumeType`, `libvirtPoolName` + and `libvirtVolName` + + :returns: Id of the volume if successfully found/created else None + """ + raise error.NotSupportedError('Not implemented') diff --git a/sushy_tools/emulator/resources/volumes/__init__.py b/sushy_tools/emulator/resources/volumes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/emulator/resources/volumes/staticdriver.py b/sushy_tools/emulator/resources/volumes/staticdriver.py new file mode 100644 index 00000000..2846471e --- /dev/null +++ b/sushy_tools/emulator/resources/volumes/staticdriver.py @@ -0,0 +1,81 @@ +# Copyright 2019 Red Hat, 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 logging + +from sushy_tools.emulator import memoize +from sushy_tools.emulator.resources.base import DriverBase +import uuid + +logger = logging.getLogger(__name__) + + +class StaticDriver(DriverBase): + """Redfish Volumes emulated in libvirt backed by the config file + + Maintains the libvirt volumes in memory. + """ + @classmethod + def initialize(cls, config): + cls._config = config + + cls._volumes = memoize.PersistentDict() + + if hasattr(cls._volumes, 'make_permanent'): + cls._volumes.make_permanent( + config.get('SUSHY_EMULATOR_STATE_DIR'), 'volumes') + + cls._volumes.update( + config.get('SUSHY_EMULATOR_VOLUMES', {})) + + return cls + + @property + def driver(self): + """Return human-friendly driver information + + :returns: driver information as `str` + """ + return '' + + def get_volumes_col(self, identity, storage_id): + try: + uu_identity = str(uuid.UUID(identity)) + + return self._volumes[(uu_identity, storage_id)] + + except (KeyError, ValueError): + msg = ('Error finding volume collection by System UUID %s ' + 'and Storage ID %s' % (uu_identity, storage_id)) + logger.debug(msg) + + def add_volume(self, uu_identity, storage_id, vol): + if not self._volumes[(uu_identity, storage_id)]: + self._volumes[(uu_identity, storage_id)] = [] + + vol_col = self._volumes[(uu_identity, storage_id)] + vol_col.append(vol) + self._volumes.update({(uu_identity, storage_id): vol_col}) + + def delete_volume(self, uu_identity, storage_id, vol): + try: + vol_col = self._volumes[(uu_identity, storage_id)] + except KeyError: + msg = ('Error finding volume collection by System UUID %s ' + 'and Storage ID %s' % (uu_identity, storage_id)) + logger.debug(msg) + else: + vol_col.remove(vol) + self._volumes.update({(uu_identity, storage_id): vol_col}) diff --git a/sushy_tools/emulator/templates/volume.json b/sushy_tools/emulator/templates/volume.json new file mode 100644 index 00000000..0b575a12 --- /dev/null +++ b/sushy_tools/emulator/templates/volume.json @@ -0,0 +1,16 @@ +{ + "@odata.type": "#Volume.v1_0_3.Volume", + "Id": {{ volume['Id']|string|tojson }}, + "Name": {{ volume['Name']|string|tojson }}, + "Status": { + "@odata.type": "#Resource.Status", + "State": "Enabled", + "Health": "OK" + }, + "Encrypted": false, + "VolumeType": {{ volume['VolumeType']|string|tojson }}, + "CapacityBytes": {{ volume['CapacityBytes'] }}, + "@odata.context": "/redfish/v1/$metadata#Volume.Volume", + "@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s/Volumes/%s"|format(identity, storage_id, volume['Id'])|tojson }}, + "@Redfish.Copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." +} \ No newline at end of file diff --git a/sushy_tools/emulator/templates/volume_collection.json b/sushy_tools/emulator/templates/volume_collection.json new file mode 100644 index 00000000..3ca7e85d --- /dev/null +++ b/sushy_tools/emulator/templates/volume_collection.json @@ -0,0 +1,15 @@ +{ + "@odata.type": "#VolumeCollection.VolumeCollection", + "Name": "Storage Volume Collection", + "Members@odata.count": {{ volume_col|length }}, + "Members": [ + {% for volume in volume_col %} + { + "@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s/Volumes/%s"|format(identity, storage_id, volume)|tojson }} + }{% if not loop.last %},{% endif %} + {% endfor %} + ], + "@odata.context": "/redfish/v1/$metadata#VolumeCollection.VolumeCollection", + "@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s/Volumes"|format(identity, storage_id)|tojson }}, + "@Redfish.Copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." +} \ No newline at end of file diff --git a/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py b/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py index 24ff7622..778575e5 100644 --- a/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py +++ b/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py @@ -645,3 +645,24 @@ class LibvirtDriverTestCase(base.BaseTestCase): .get_simple_storage_collection(self.uuid)) self.assertEqual({}, simple_storage_response) + + @mock.patch('libvirt.open', autospec=True) + def test_find_or_create_storage_volume(self, libvirt_mock): + conn_mock = libvirt_mock.return_value + vol_data = { + "libvirtVolName": "123456", + "Id": "1", + "Name": "Sample Vol", + "CapacityBytes": 12345, + "VolumeType": "Mirrored" + } + + pool_mock = conn_mock.storagePoolLookupByName.return_value + with open('sushy_tools/tests/unit/emulator/pool.xml', 'r') as f: + data = f.read() + pool_mock.storageVolLookupByName.side_effect = libvirt.libvirtError( + 'Storage volume not found') + pool_mock.XMLDesc.return_value = data + + self.test_driver.find_or_create_storage_volume(vol_data) + pool_mock.createXML.assert_called_once_with(mock.ANY) diff --git a/sushy_tools/tests/unit/emulator/resources/volumes/__init__.py b/sushy_tools/tests/unit/emulator/resources/volumes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/tests/unit/emulator/resources/volumes/test_static.py b/sushy_tools/tests/unit/emulator/resources/volumes/test_static.py new file mode 100644 index 00000000..501065bb --- /dev/null +++ b/sushy_tools/tests/unit/emulator/resources/volumes/test_static.py @@ -0,0 +1,86 @@ +# Copyright 2019 Red Hat, 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. +from oslotest import base +from six.moves import mock + +from sushy_tools.emulator.resources.volumes.staticdriver import StaticDriver + + +@mock.patch('sushy_tools.emulator.resources.volumes' + '.staticdriver.memoize.PersistentDict', new=dict) +class StaticDriverTestCase(base.BaseTestCase): + + SYSTEM_UUID = "da69abcc-dae0-4913-9a7b-d344043097c0" + STORAGE_ID = "1" + VOLUMES_COL = [ + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol", + "Id": "1", + "Name": "Sample Volume 1", + "VolumeType": "Mirrored", + "CapacityBytes": 23748 + }, + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol1", + "Id": "2", + "Name": "Sample Volume 2", + "VolumeType": "StripedWithParity", + "CapacityBytes": 48395 + } + ] + + CONFIG = { + 'SUSHY_EMULATOR_VOLUMES': { + (SYSTEM_UUID, STORAGE_ID): VOLUMES_COL + } + } + + def test_get_volumes_col(self): + test_driver = StaticDriver.initialize(self.CONFIG)() + vol_col = test_driver.get_volumes_col(self.SYSTEM_UUID, + self.STORAGE_ID) + self.assertEqual(self.VOLUMES_COL, vol_col) + + def test_add_volume(self): + test_driver = StaticDriver.initialize(self.CONFIG)() + vol = { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol2", + "Id": "3", + "Name": "Sample Volume 3", + "VolumeType": "Mirrored", + "CapacityBytes": 76584 + } + test_driver.add_volume(self.SYSTEM_UUID, self.STORAGE_ID, vol) + vol_col = test_driver.get_volumes_col(self.SYSTEM_UUID, + self.STORAGE_ID) + self.assertTrue(vol in vol_col) + + def test_delete_volume(self): + test_driver = StaticDriver.initialize(self.CONFIG)() + vol = { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol", + "Id": "1", + "Name": "Sample Volume 1", + "VolumeType": "Mirrored", + "CapacityBytes": 23748 + } + test_driver.delete_volume(self.SYSTEM_UUID, self.STORAGE_ID, vol) + vol_col = test_driver.get_volumes_col(self.SYSTEM_UUID, + self.STORAGE_ID) + self.assertFalse(vol in vol_col) diff --git a/sushy_tools/tests/unit/emulator/test_main.py b/sushy_tools/tests/unit/emulator/test_main.py index fd40d052..fae74ae6 100644 --- a/sushy_tools/tests/unit/emulator/test_main.py +++ b/sushy_tools/tests/unit/emulator/test_main.py @@ -783,3 +783,63 @@ class EmulatorTestCase(base.BaseTestCase): self.assertEqual('Drive Sample', response.json['Name']) self.assertEqual(899527000000, response.json['CapacityBytes']) self.assertEqual('SAS', response.json['Protocol']) + + def test_volume_collection_get(self, resources_mock): + resources_mock = resources_mock.return_value.__enter__.return_value + resources_mock.volumes.get_volumes_col.return_value = [ + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol", + "Id": "1", + "Name": "Sample Volume 1", + "VolumeType": "Mirrored", + "CapacityBytes": 23748 + } + ] + resources_mock.systems.find_or_create_storage_volume.return_value = "1" + response = self.app.get('/redfish/v1/Systems/vmc-node/Storage/1/' + 'Volumes') + + self.assertEqual(200, response.status_code) + self.assertEqual({'@odata.id': + '/redfish/v1/Systems/vmc-node/Storage/1/Volumes/1'}, + response.json['Members'][0]) + + def test_create_volume_post(self, resources_mock): + resources_mock = resources_mock.return_value.__enter__.return_value + systems_mock = resources_mock.systems + systems_mock.find_or_create_storage_volume.return_value = "13087010612" + data = { + "Name": "Sample Volume 3", + "VolumeType": "NonRedundant", + "CapacityBytes": 23456 + } + response = self.app.post('/redfish/v1/Systems/vmc-node/Storage/1/' + 'Volumes', json=data) + + self.assertEqual(201, response.status_code) + self.assertEqual('http://localhost/redfish/v1/Systems/vmc-node/' + 'Storage/1/Volumes/13087010612', + response.headers['Location']) + + def test_volume_resource_get(self, resources_mock): + resources_mock = resources_mock.return_value.__enter__.return_value + resources_mock.volumes.get_volumes_col.return_value = [ + { + "libvirtPoolName": "sushyPool", + "libvirtVolName": "testVol", + "Id": "1", + "Name": "Sample Volume 1", + "VolumeType": "Mirrored", + "CapacityBytes": 23748 + } + ] + resources_mock.systems.find_or_create_storage_volume.return_value = "1" + response = self.app.get('/redfish/v1/Systems/vbmc-node/Storage/1/' + 'Volumes/1') + + self.assertEqual(200, response.status_code) + self.assertEqual('1', response.json['Id']) + self.assertEqual('Sample Volume 1', response.json['Name']) + self.assertEqual('Mirrored', response.json['VolumeType']) + self.assertEqual(23748, response.json['CapacityBytes'])