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:
Varsha 2019-08-07 16:02:18 +05:30
parent 5491098fdb
commit f18831163e
12 changed files with 293 additions and 2 deletions

View File

@ -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"
}
]
}

View File

@ -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",
...
}

View File

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

View File

@ -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',

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 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]]

View File

@ -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 %}
{

View 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."
}

View File

@ -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 }}
}

View File

@ -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))

View File

@ -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'])