Add Volume resource support
This change adds basic Redfish Volumes resource support to the dynamic Redfish emulator. The Volumes are emulated as persistant mocks backed by the libvirt volumes from the libvirt virtualization backend. Change-Id: If99d438c07f8a37280b1c480dcdc40510283c6d9
This commit is contained in:
parent
f18831163e
commit
fa70fbcb18
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -717,3 +717,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
|
||||
|
|
|
@ -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.
|
|
@ -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/<identity>/Storage/<storage_id>/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/<identity>/Storage/<stg_id>/Volumes/<vol_id>',
|
||||
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',
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -125,7 +125,7 @@ class LibvirtDriver(AbstractSystemsDriver):
|
|||
STORAGE_VOLUME_XML = """
|
||||
<volume type='file'>
|
||||
<name>%(name)s</name>
|
||||
<key>%(name)s</key>
|
||||
<key>%(path)s</key>
|
||||
<capacity unit='bytes'>%(size)i</capacity>
|
||||
<physical unit='bytes'>%(size)i</physical>
|
||||
<target>
|
||||
|
@ -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']
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 '<static-volumes>'
|
||||
|
||||
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})
|
|
@ -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."
|
||||
}
|
|
@ -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."
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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'])
|
||||
|
|
Loading…
Reference in New Issue