Add Storage and Storage Controllers resource support

This change adds basic Redfish Storage and Storage Controllers
support to the dynamic Redfish emulator. As of this patch, the
only backend for the Redfish Storage implemented is static
config (Flask) file.

Change-Id: I250d84e6edd3c867809068a1a6b9c28e6756cbaf
This commit is contained in:
Varsha 2019-08-02 15:42:37 +05:30
parent 3d65187c0c
commit 5491098fdb
13 changed files with 351 additions and 2 deletions

View File

@ -35,7 +35,7 @@ SUSHY_EMULATOR_BOOT_LOADER_MAP = {
# This map contains statically configured Redfish Manager(s) linked
# up with the Systems each Manager pretends to manage.
#
# The first managerc in the list will pretend to manage all other
# The first manager in the list will pretend to manage all other
# resources.
#
# If this map is not present in the configuration, a single default
@ -101,3 +101,21 @@ SUSHY_EMULATOR_VMEDIA_DEVICES = {
]
}
}
# This map contains statically configured Redfish Storage resource linked
# up with the Systems resource, keyed by the UUIDs of the Systems.
SUSHY_EMULATOR_STORAGE = {
"da69abcc-dae0-4913-9a7b-d344043097c0": [
{
"Id": "1",
"Name": "Local Storage Controller",
"StorageControllers": [
{
"MemberId": "0",
"Name": "Contoso Integrated RAID",
"SpeedGbps": 12
}
]
}
]
}

View File

@ -617,3 +617,52 @@ By this point the system will boot off the virtual CD drive when powering it on:
ISO files to boot from must be UEFI-bootable, libvirtd should be running on the same
machine with sushy-emulator.
Storage resource
----------------
For emulating *Storage* resource for a System of choice, the
user can statically configure one or more imaginary storage
instances along with the corresponding storage controllers which
are also imaginary.
The *Storage* instances are keyed by the UUIDs of the System they
belong to.
.. code-block:: python
SUSHY_EMULATOR_STORAGE = {
"da69abcc-dae0-4913-9a7b-d344043097c0": [
{
"Id": "1",
"Name": "Local Storage Controller",
"StorageControllers": [
{
"MemberId": "0",
"Name": "Contoso Integrated RAID",
"SpeedGbps": 12
}
]
}
]
}
The Storage resources can be revealed by querying Storage resource
for the corresponding System directly.
.. code-block:: bash
curl http://localhost:8000/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage
{
"@odata.type": "#StorageCollection.StorageCollection",
"Name": "Storage Collection",
"Members@odata.count": 1,
"Members": [
{
"@odata.id": "/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage/1"
}
],
"Oem": {},
"@odata.context": "/redfish/v1/$metadata#StorageCollection.StorageCollection",
"@odata.id": "/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage"
}

View File

@ -0,0 +1,5 @@
---
features:
- |
Adds Storage and Storage Controllers resource support to the dynamic
Redfish emulator based on static user configuration.

View File

@ -26,6 +26,7 @@ import flask
from sushy_tools.emulator.resources.chassis import staticdriver as chsdriver
from sushy_tools.emulator.resources.indicators import staticdriver as inddriver
from sushy_tools.emulator.resources.managers import staticdriver as mgrdriver
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
@ -44,6 +45,7 @@ class Resources(object):
CHASSIS = None
INDICATORS = None
VMEDIA = None
STORAGE = None
def __new__(cls, *args, **kwargs):
@ -111,6 +113,13 @@ class Resources(object):
'Initialized virtual media resource backed by %s '
'driver', cls.VMEDIA().driver)
if cls.STORAGE is None:
cls.STORAGE = stgdriver.StaticDriver.initialize(app.config)
app.logger.debug(
'Initialized storage resource backed by %s '
'driver', cls.STORAGE().driver)
return super(Resources, cls).__new__(cls, *args, **kwargs)
def __enter__(self):
@ -119,6 +128,7 @@ class Resources(object):
self.chassis = self.CHASSIS()
self.indicators = self.INDICATORS()
self.vmedia = self.VMEDIA()
self.storage = self.STORAGE()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
@ -127,6 +137,7 @@ class Resources(object):
del self.chassis
del self.indicators
del self.vmedia
del self.storage
def instance_denied(**kwargs):
@ -219,10 +230,12 @@ def chassis_resource(identity):
if uuid == chassis.chassis[0]:
systems = resources.systems.systems
managers = resources.managers.managers
storage = resources.storage.get_all_storage()
else:
systems = []
managers = []
storage = []
return flask.render_template(
'chassis.json',
@ -235,7 +248,8 @@ def chassis_resource(identity):
contained_chassis=[],
managers=managers[:1],
indicator_led=resources.indicators.get_indicator_state(
uuid)
uuid),
storage=storage
)
elif flask.request.method == 'PATCH':
@ -657,6 +671,38 @@ def simple_storage(identity, simple_storage_id):
simple_storage=storage_controller)
@app.route('/redfish/v1/Systems/<identity>/Storage',
methods=['GET'])
@ensure_instance_access
@returns_json
def storage_collection(identity):
with Resources() as resources:
uuid = resources.systems.uuid(identity)
storage_col = resources.storage.get_storage_col(uuid)
return flask.render_template(
'storage_collection.json', identity=identity,
storage_col=storage_col)
@app.route('/redfish/v1/Systems/<identity>/Storage/<storage_id>',
methods=['GET'])
@ensure_instance_access
@returns_json
def storage(identity, storage_id):
with Resources() as resources:
uuid = resources.systems.uuid(identity)
storage_col = resources.storage.get_storage_col(uuid)
for stg in storage_col:
if stg['Id'] == storage_id:
return flask.render_template(
'storage.json', identity=identity, storage=stg)
return 'Not found', 404
def parse_args():
parser = argparse.ArgumentParser('sushy-emulator')
parser.add_argument('--config',

View File

@ -0,0 +1,64 @@
# 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
import uuid
from sushy_tools.emulator.resources.base import DriverBase
from sushy_tools import error
logger = logging.getLogger(__name__)
class StaticDriver(DriverBase):
"""Redfish storage backed by configuration file"""
@classmethod
def initialize(cls, config):
cls._config = config
cls._storage = cls._config.get('SUSHY_EMULATOR_STORAGE')
return cls
@property
def driver(self):
"""Return human-friendly driver information
:returns: driver information as `str`
"""
return '<static-storage>'
def get_storage_col(self, identity):
try:
uu_identity = str(uuid.UUID(identity))
return self._storage[uu_identity]
except KeyError:
msg = ('Error finding storage collection by UUID '
'"%(identity)s"' % {'identity': identity})
logger.debug(msg)
raise error.FishyError(msg)
def get_all_storage(self):
"""Returns all storage instances represented as tuples in the format:
(System_ID, Storage_ID)
:returns: list of tuples representing the storage instances
"""
return [(k, st["Id"]) for k in self._storage.keys()
for st in self._storage[k]]

View File

@ -62,6 +62,15 @@
{% endfor -%}
],
{% endif -%}
{% if storage %}
"Storage": [
{%- for stg in storage %}
{
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s"|format(stg[0], stg[1])|tojson }}
}{% if not loop.last %},{% endif %}
{% endfor -%}
],
{% endif -%}
"ManagedBy": [
{%- for manager in managers %}
{

View File

@ -0,0 +1,43 @@
{
"@odata.type": "##Storage.v1_4_0.Storage",
"Id": {{ storage['Id']|string|tojson }},
"Name": {{ storage['Name']|string|tojson }},
"Status": {
"@odata.type": "#Resource.Status",
"State": "Enabled",
"Health": "OK",
"HealthRollup": "OK"
},
{% if storage['StorageControllers'] %}
"StorageControllers": [
{%- for ctl in storage['StorageControllers'] %}
{
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s#/StorageControllers/%s"|format(identity, storage['Id'], ctl['MemberId'])|tojson }},
"@odata.type": "#Storage.v1_3_0.StorageController",
"MemberId": {{ ctl['MemberId']|string|tojson }},
"Name": {{ ctl['Name']|string|tojson }},
"Status": {
"@odata.type": "#Resource.Status",
"State": "Enabled",
"Health": "OK"
},
"Manufacturer": "Contoso",
"Model": "12Gbs Integrated RAID",
"SerialNumber": "2M220100SL",
"PartNumber": "CT18754",
"SpeedGbps": {{ ctl['SpeedGbps'] }},
"FirmwareVersion": "1.0.0.7",
"SupportedControllerProtocols": [
"PCIe"
],
"SupportedDeviceProtocols": [
"SAS",
"SATA"
]
}{% if not loop.last %},{% endif %}
{% endfor -%}
],
{% endif -%}
"@odata.context": "/redfish/v1/$metadata#Storage.Storage",
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s"|format(identity, storage['Id'])|tojson }}
}

View File

@ -0,0 +1,16 @@
{
"@odata.type": "#StorageCollection.StorageCollection",
"Name": "Storage Collection",
"Members@odata.count": {{ storage_col|length }},
"Members": [
{% for storage in storage_col %}
{
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s"|format(identity, storage.Id)|tojson }}
}{% if not loop.last %},{% endif %}
{% endfor %}
],
"Oem": {},
"@odata.context": "/redfish/v1/$metadata#StorageCollection.StorageCollection",
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage"|format(identity)|tojson }}
}

View File

@ -69,6 +69,9 @@
"SimpleStorage": {
"@odata.id": {{ "/redfish/v1/Systems/%s/SimpleStorage"|format(identity)|tojson }}
},
"Storage": {
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage"|format(identity)|tojson }}
},
{%- if indicator_led %}
"IndicatorLED": {{ indicator_led|string|tojson }},
{%- endif %}

View File

@ -0,0 +1,50 @@
# 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 sushy_tools.emulator.resources.storage.staticdriver import StaticDriver
from oslotest import base
class StaticDriverTestCase(base.BaseTestCase):
UUID = "da69abcc-dae0-4913-9a7b-d344043097c0"
STORAGE_COL = [
{
"Id": "1",
"Name": "Local Storage Controller",
"StorageControllers": [
{
"MemberId": "0",
"Name": "Contoso Integrated RAID",
"SpeedGbps": 12
}
]
}
]
CONFIG = {
'SUSHY_EMULATOR_STORAGE': {
UUID: STORAGE_COL
}
}
def test_get_storage_col(self):
test_driver = StaticDriver.initialize(self.CONFIG)()
stg_col = test_driver.get_storage_col(self.UUID)
self.assertEqual(self.STORAGE_COL, stg_col)
def test_get_all_storage(self):
test_driver = StaticDriver.initialize(self.CONFIG)()
stg = test_driver.get_all_storage()
self.assertEqual([(self.UUID, '1')], stg)

View File

@ -718,3 +718,49 @@ class EmulatorTestCase(base.BaseTestCase):
'/SimpleStorage/scsi')
self.assertEqual(404, response.status_code)
def test_storage_collection_resource(self, resources_mock):
resources_mock = resources_mock.return_value.__enter__.return_value
resources_mock.storage.get_storage_col.return_value = [
{
"Id": "1",
"Name": "Local Storage Controller",
"StorageControllers": [
{
"MemberId": "0",
"Name": "Contoso Integrated RAID",
"SpeedGbps": 12
}
]
}
]
response = self.app.get('redfish/v1/Systems/vbmc-node/Storage')
self.assertEqual(200, response.status_code)
self.assertEqual({'@odata.id':
'/redfish/v1/Systems/vbmc-node/Storage/1'},
response.json['Members'][0])
def test_storage_resource_get(self, resources_mock):
resources_mock = resources_mock.return_value.__enter__.return_value
resources_mock.storage.get_storage_col.return_value = [
{
"Id": "1",
"Name": "Local Storage Controller",
"StorageControllers": [
{
"MemberId": "0",
"Name": "Contoso Integrated RAID",
"SpeedGbps": 12
}
]
}
]
response = self.app.get('/redfish/v1/Systems/vbmc-node/Storage/1')
self.assertEqual(200, response.status_code)
self.assertEqual('1', response.json['Id'])
self.assertEqual('Local Storage Controller', response.json['Name'])
stg_ctl = response.json['StorageControllers'][0]
self.assertEqual("0", stg_ctl['MemberId'])
self.assertEqual("Contoso Integrated RAID", stg_ctl['Name'])
self.assertEqual(12, stg_ctl['SpeedGbps'])