Add Drive resource support
This change adds basic Redfish Drive resource support to the dynamic Redfish emulator. As of this patch, the only backend for the Redfish Drives implemented is static config (Flask) file. Change-Id: I867cea6263a8a934dd90169b783893be46d75d36
This commit is contained in:
parent
5491098fdb
commit
f18831163e
@ -115,7 +115,25 @@ SUSHY_EMULATOR_STORAGE = {
|
||||
"Name": "Contoso Integrated RAID",
|
||||
"SpeedGbps": 12
|
||||
}
|
||||
],
|
||||
"Drives": [
|
||||
"32ADF365C6C1B7BD"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# This map contains statically configured Redfish Drives resource. The Drive
|
||||
# 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 drive belongs.
|
||||
SUSHY_EMULATOR_DRIVES = {
|
||||
("da69abcc-dae0-4913-9a7b-d344043097c0", "1"): [
|
||||
{
|
||||
"Id": "32ADF365C6C1B7BD",
|
||||
"Name": "Drive Sample",
|
||||
"CapacityBytes": 899527000000,
|
||||
"Protocol": "SAS"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -335,7 +335,7 @@ Chassis resource
|
||||
|
||||
For emulating *Chassis* resource, the user can statically configure
|
||||
one or more imaginary chassis. All existing resources (e.g. *Systems*,
|
||||
*Managers*) will pretend to reside in the first chassis.
|
||||
*Managers*, *Drives*) will pretend to reside in the first chassis.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -626,6 +626,9 @@ user can statically configure one or more imaginary storage
|
||||
instances along with the corresponding storage controllers which
|
||||
are also imaginary.
|
||||
|
||||
The IDs of the imaginary drives associated to a *Storage* resource
|
||||
can be provided as a list under *Drives*.
|
||||
|
||||
The *Storage* instances are keyed by the UUIDs of the System they
|
||||
belong to.
|
||||
|
||||
@ -642,6 +645,9 @@ belong to.
|
||||
"Name": "Contoso Integrated RAID",
|
||||
"SpeedGbps": 12
|
||||
}
|
||||
],
|
||||
"Drives": [
|
||||
"32ADF365C6C1B7BD"
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -666,3 +672,48 @@ for the corresponding System directly.
|
||||
"@odata.context": "/redfish/v1/$metadata#StorageCollection.StorageCollection",
|
||||
"@odata.id": "/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage"
|
||||
}
|
||||
|
||||
Drive resource
|
||||
++++++++++++++
|
||||
|
||||
For emulating the *Drive* resource, the user can statically configure
|
||||
one or more drives.
|
||||
|
||||
The *Drive* instances are keyed in a composite manner using
|
||||
(System_UUID, Storage_ID) where System_UUID is the UUID of the System
|
||||
and Storage_ID is the ID of the Storage resource to which that particular
|
||||
drive belongs.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
SUSHY_EMULATOR_DRIVES = {
|
||||
("da69abcc-dae0-4913-9a7b-d344043097c0", "1"): [
|
||||
{
|
||||
"Id": "32ADF365C6C1B7BD",
|
||||
"Name": "Drive Sample",
|
||||
"CapacityBytes": 899527000000,
|
||||
"Protocol": "SAS"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
The *Drive* resource can be revealed by querying it via the System and the
|
||||
Storage resource it belongs to.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:8000/redfish/v1/Systems/da69abcc-dae0-4913-9a7b-d344043097c0/Storage/1/Drives/32ADF365C6C1B7BD
|
||||
{
|
||||
...
|
||||
"Id": "32ADF365C6C1B7BD",
|
||||
"Name": "Drive Sample",
|
||||
"Model": "C123",
|
||||
"Revision": "100A",
|
||||
"CapacityBytes": 899527000000,
|
||||
"FailurePredicted": false,
|
||||
"Protocol": "SAS",
|
||||
"MediaType": "HDD",
|
||||
"Manufacturer": "Contoso",
|
||||
"SerialNumber": "1234570",
|
||||
...
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds Drive resource support to the dynamic Redfish emulator based on
|
||||
static user configuration.
|
@ -24,6 +24,7 @@ import sys
|
||||
import flask
|
||||
|
||||
from sushy_tools.emulator.resources.chassis import staticdriver as chsdriver
|
||||
from sushy_tools.emulator.resources.drives import staticdriver as drvdriver
|
||||
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
|
||||
@ -46,6 +47,7 @@ class Resources(object):
|
||||
INDICATORS = None
|
||||
VMEDIA = None
|
||||
STORAGE = None
|
||||
DRIVES = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
|
||||
@ -120,6 +122,13 @@ class Resources(object):
|
||||
'Initialized storage resource backed by %s '
|
||||
'driver', cls.STORAGE().driver)
|
||||
|
||||
if cls.DRIVES is None:
|
||||
cls.DRIVES = drvdriver.StaticDriver.initialize(app.config)
|
||||
|
||||
app.logger.debug(
|
||||
'Initialized drive resource backed by %s '
|
||||
'driver', cls.DRIVES().driver)
|
||||
|
||||
return super(Resources, cls).__new__(cls, *args, **kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
@ -129,6 +138,7 @@ class Resources(object):
|
||||
self.indicators = self.INDICATORS()
|
||||
self.vmedia = self.VMEDIA()
|
||||
self.storage = self.STORAGE()
|
||||
self.drives = self.DRIVES()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
@ -138,6 +148,7 @@ class Resources(object):
|
||||
del self.indicators
|
||||
del self.vmedia
|
||||
del self.storage
|
||||
del self.drives
|
||||
|
||||
|
||||
def instance_denied(**kwargs):
|
||||
@ -231,11 +242,13 @@ def chassis_resource(identity):
|
||||
systems = resources.systems.systems
|
||||
managers = resources.managers.managers
|
||||
storage = resources.storage.get_all_storage()
|
||||
drives = resources.drives.get_all_drives()
|
||||
|
||||
else:
|
||||
systems = []
|
||||
managers = []
|
||||
storage = []
|
||||
drives = []
|
||||
|
||||
return flask.render_template(
|
||||
'chassis.json',
|
||||
@ -249,7 +262,8 @@ def chassis_resource(identity):
|
||||
managers=managers[:1],
|
||||
indicator_led=resources.indicators.get_indicator_state(
|
||||
uuid),
|
||||
storage=storage
|
||||
storage=storage,
|
||||
drives=drives
|
||||
)
|
||||
|
||||
elif flask.request.method == 'PATCH':
|
||||
@ -703,6 +717,23 @@ def storage(identity, storage_id):
|
||||
return 'Not found', 404
|
||||
|
||||
|
||||
@app.route('/redfish/v1/Systems/<identity>/Storage/<stg_id>/Drives/<drv_id>',
|
||||
methods=['GET'])
|
||||
@ensure_instance_access
|
||||
@returns_json
|
||||
def drive_resource(identity, stg_id, drv_id):
|
||||
with Resources() as resources:
|
||||
uuid = resources.systems.uuid(identity)
|
||||
drives = resources.drives.get_drives(uuid, stg_id)
|
||||
|
||||
for drv in drives:
|
||||
if drv['Id'] == drv_id:
|
||||
return flask.render_template(
|
||||
'drive.json', identity=identity, storage_id=stg_id, drive=drv)
|
||||
|
||||
return 'Not found', 404
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser('sushy-emulator')
|
||||
parser.add_argument('--config',
|
||||
|
0
sushy_tools/emulator/resources/drives/__init__.py
Normal file
0
sushy_tools/emulator/resources/drives/__init__.py
Normal file
64
sushy_tools/emulator/resources/drives/staticdriver.py
Normal file
64
sushy_tools/emulator/resources/drives/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 drives backed by configuration file"""
|
||||
|
||||
@classmethod
|
||||
def initialize(cls, config):
|
||||
cls._config = config
|
||||
cls._drives = cls._config.get('SUSHY_EMULATOR_DRIVES')
|
||||
return cls
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
"""Return human-friendly driver information
|
||||
|
||||
:returns: driver information as `str`
|
||||
"""
|
||||
return '<static-drives>'
|
||||
|
||||
def get_drives(self, identity, storage_id):
|
||||
try:
|
||||
uu_identity = str(uuid.UUID(identity))
|
||||
|
||||
return self._drives[(uu_identity, storage_id)]
|
||||
|
||||
except (ValueError, KeyError):
|
||||
msg = ('Error finding drive for System UUID "%s" and Storage ID '
|
||||
'"%s"', identity, storage_id)
|
||||
|
||||
logger.debug(msg)
|
||||
|
||||
raise error.FishyError(msg)
|
||||
|
||||
def get_all_drives(self):
|
||||
"""Return all drives represented as tuples in the following format:
|
||||
|
||||
(System_UUID, Storage_ID, Drive_ID)
|
||||
|
||||
:returns: list of tuples representing the drives
|
||||
"""
|
||||
return [k + (d['Id'],) for k in self._drives.keys()
|
||||
for d in self._drives[k]]
|
@ -71,6 +71,15 @@
|
||||
{% endfor -%}
|
||||
],
|
||||
{% endif -%}
|
||||
{% if drives %}
|
||||
"Drives": [
|
||||
{%- for drive in drives %}
|
||||
{
|
||||
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s/Drives/%s"|format(drive[0], drive[1], drive[2])|tojson }}
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor -%}
|
||||
],
|
||||
{% endif -%}
|
||||
"ManagedBy": [
|
||||
{%- for manager in managers %}
|
||||
{
|
||||
|
30
sushy_tools/emulator/templates/drive.json
Normal file
30
sushy_tools/emulator/templates/drive.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"@odata.type": "#Drive.v1_4_0.Drive",
|
||||
"Id": {{ drive['Id']|string|tojson }},
|
||||
"Name": {{ drive['Name']|string|tojson }},
|
||||
"IndicatorLED": "Lit",
|
||||
"Model": "C123",
|
||||
"Revision": "100A",
|
||||
"Status": {
|
||||
"@odata.type": "#Resource.Status",
|
||||
"State": "Enabled",
|
||||
"Health": "OK"
|
||||
},
|
||||
"CapacityBytes": {{ drive['CapacityBytes'] }},
|
||||
"FailurePredicted": false,
|
||||
"Protocol": {{ drive['Protocol']|string|tojson }},
|
||||
"MediaType": "HDD",
|
||||
"Manufacturer": "Contoso",
|
||||
"SerialNumber": "1234570",
|
||||
"PartNumber": "C123-1111",
|
||||
"HotspareType": "Global",
|
||||
"EncryptionAbility": "SelfEncryptingDrive",
|
||||
"EncryptionStatus": "Unlocked",
|
||||
"RotationSpeedRPM": 15000,
|
||||
"BlockSizeBytes": 512,
|
||||
"CapableSpeedGbs": 12,
|
||||
"NegotiatedSpeedGbs": 12,
|
||||
"@odata.context": "/redfish/v1/$metadata#Drive.Drive",
|
||||
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s/Drives/%s"|format(identity, storage_id, drive['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."
|
||||
}
|
@ -38,6 +38,15 @@
|
||||
{% endfor -%}
|
||||
],
|
||||
{% endif -%}
|
||||
{% if storage['Drives'] %}
|
||||
"Drives": [
|
||||
{%- for drive in storage['Drives'] %}
|
||||
{
|
||||
"@odata.id": {{ "/redfish/v1/Systems/%s/Storage/%s/Drives/%s"|format(identity, storage['Id'], drive)|tojson }}
|
||||
}{% 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 }}
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
# 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.drives.staticdriver import StaticDriver
|
||||
|
||||
from oslotest import base
|
||||
|
||||
|
||||
class StaticDriverTestCase(base.BaseTestCase):
|
||||
SYSTEM_UUID = "da69abcc-dae0-4913-9a7b-d344043097c0"
|
||||
STORAGE_ID = "1"
|
||||
DRIVE_COL = [
|
||||
{
|
||||
"Id": "32ADF365C6C1B7BD",
|
||||
"Name": "Drive Sample",
|
||||
"CapacityBytes": 899527000000,
|
||||
"Protocol": "SAS"
|
||||
},
|
||||
{
|
||||
"Id": "58CFF987G8J2V9KL",
|
||||
"Name": "Drive2",
|
||||
"CapacityBytes": 12345670000,
|
||||
"Protocol": "SATA"
|
||||
}
|
||||
]
|
||||
|
||||
CONFIG = {
|
||||
'SUSHY_EMULATOR_DRIVES': {
|
||||
(SYSTEM_UUID, STORAGE_ID): DRIVE_COL
|
||||
}
|
||||
}
|
||||
|
||||
def test_get_drives(self):
|
||||
test_driver = StaticDriver.initialize(self.CONFIG)()
|
||||
drv_col = test_driver.get_drives(self.SYSTEM_UUID, self.STORAGE_ID)
|
||||
self.assertEqual(self.DRIVE_COL, drv_col)
|
||||
|
||||
def test_get_all_drives(self):
|
||||
test_driver = StaticDriver.initialize(self.CONFIG)()
|
||||
drives = test_driver.get_all_drives()
|
||||
self.assertEqual({('da69abcc-dae0-4913-9a7b-d344043097c0', '1',
|
||||
'32ADF365C6C1B7BD'),
|
||||
('da69abcc-dae0-4913-9a7b-d344043097c0', '1',
|
||||
'58CFF987G8J2V9KL')}, set(drives))
|
@ -764,3 +764,22 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
self.assertEqual("0", stg_ctl['MemberId'])
|
||||
self.assertEqual("Contoso Integrated RAID", stg_ctl['Name'])
|
||||
self.assertEqual(12, stg_ctl['SpeedGbps'])
|
||||
|
||||
def test_drive_resource_get(self, resources_mock):
|
||||
resources_mock = resources_mock.return_value.__enter__.return_value
|
||||
resources_mock.drives.get_drives.return_value = [
|
||||
{
|
||||
"Id": "32ADF365C6C1B7BD",
|
||||
"Name": "Drive Sample",
|
||||
"CapacityBytes": 899527000000,
|
||||
"Protocol": "SAS"
|
||||
}
|
||||
]
|
||||
response = self.app.get('/redfish/v1/Systems/vbmc-node/Storage/1'
|
||||
'/Drives/32ADF365C6C1B7BD')
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual('32ADF365C6C1B7BD', response.json['Id'])
|
||||
self.assertEqual('Drive Sample', response.json['Name'])
|
||||
self.assertEqual(899527000000, response.json['CapacityBytes'])
|
||||
self.assertEqual('SAS', response.json['Protocol'])
|
||||
|
Loading…
x
Reference in New Issue
Block a user