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:
parent
3d65187c0c
commit
5491098fdb
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds Storage and Storage Controllers resource support to the dynamic
|
||||
Redfish emulator based on static user configuration.
|
@ -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',
|
||||
|
0
sushy_tools/emulator/resources/storage/__init__.py
Normal file
0
sushy_tools/emulator/resources/storage/__init__.py
Normal file
64
sushy_tools/emulator/resources/storage/staticdriver.py
Normal file
64
sushy_tools/emulator/resources/storage/staticdriver.py
Normal 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]]
|
@ -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 %}
|
||||
{
|
||||
|
43
sushy_tools/emulator/templates/storage.json
Normal file
43
sushy_tools/emulator/templates/storage.json
Normal 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 }}
|
||||
}
|
16
sushy_tools/emulator/templates/storage_collection.json
Normal file
16
sushy_tools/emulator/templates/storage_collection.json
Normal 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 }}
|
||||
}
|
||||
|
@ -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 %}
|
||||
|
@ -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)
|
@ -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'])
|
||||
|
Loading…
Reference in New Issue
Block a user