Merge "Add PATCH support for Redfish DateTime fields in Manager resource"

This commit is contained in:
Zuul
2025-07-07 15:30:50 +00:00
committed by Gerrit Code Review
5 changed files with 128 additions and 48 deletions

View File

@@ -0,0 +1,9 @@
---
features:
- |
Adds PATCH support for the Redfish Manager resource's ``DateTime`` and
``DateTimeLocalOffset`` fields in the dynamic emulator. Implements
``get_datetime`` and ``set_datetime`` methods in the fake driver and
adds test support for mocking datetime values. Disables cache reset
to persist updates across requests.

View File

@@ -85,10 +85,6 @@ class Application(flask.Flask):
# This is needed for WSGI since it cannot process argv
self.configure(config_file=os.environ.get('SUSHY_EMULATOR_CONFIG'))
@self.before_request
def reset_cache():
self._cache = {}
def configure(self, config_file=None, extra_config=None):
if config_file:
self.config.from_pyfile(os.path.abspath(config_file))
@@ -346,59 +342,79 @@ def jsonify(obj_type, obj_version, obj):
return flask.jsonify(obj)
@app.route('/redfish/v1/Managers/<identity>', methods=['GET'])
@app.route('/redfish/v1/Managers/<identity>', methods=['GET', 'PATCH'])
@api_utils.returns_json
def manager_resource(identity):
if app.feature_set == "minimum":
raise error.FeatureNotAvailable("Managers")
app.logger.debug('Serving resources for manager "%s"', identity)
manager = app.managers.get_manager(identity)
systems = app.managers.get_managed_systems(manager)
chassis = app.managers.get_managed_chassis(manager)
uuid = manager['UUID']
result = {
"Id": manager['Id'],
"Name": manager.get('Name'),
"UUID": uuid,
"ManagerType": "BMC",
"VirtualMedia": {
"@odata.id": "/redfish/v1/Systems/%s/VirtualMedia" % systems[0],
},
"Links": {
"ManagerForServers": [
{
"@odata.id": "/redfish/v1/Systems/%s" % system
}
for system in systems
],
"ManagerForChassis": [
{
"@odata.id": "/redfish/v1/Chassis/%s" % ch
}
for ch in chassis if app.feature_set == "full"
]
},
"@odata.id": "/redfish/v1/Managers/%s" % uuid,
}
if app.feature_set == "full":
result.update({
"ServiceEntryPointUUID": manager.get('ServiceEntryPointUUID'),
"Description": "Contoso BMC",
"Model": "Joo Janta 200",
"DateTime": datetime.now().strftime('%Y-%M-%dT%H:%M:%S+00:00'),
"DateTimeLocalOffset": "+00:00",
"Status": {
"State": "Enabled",
"Health": "OK"
},
"PowerState": "On",
"FirmwareVersion": "1.00",
})
return jsonify('Manager', 'v1_3_1', result)
if flask.request.method == "GET":
app.logger.debug('Serving resources for manager "%s"', identity)
result = {
"Id": manager['Id'],
"Name": manager.get('Name'),
"UUID": uuid,
"ManagerType": "BMC",
"VirtualMedia": {
"@odata.id": "/redfish/v1/Systems/%s/VirtualMedia" % systems[0]
},
"Links": {
"ManagerForServers": [
{
"@odata.id": "/redfish/v1/Systems/%s" % system
}
for system in systems
],
"ManagerForChassis": [
{
"@odata.id": "/redfish/v1/Chassis/%s" % ch
}
for ch in chassis if app.feature_set == "full"
]
},
"@odata.id": "/redfish/v1/Managers/%s" % uuid,
}
if app.feature_set == "full":
dt_info = app.managers.get_datetime()
result.update({
"ServiceEntryPointUUID": manager.get('ServiceEntryPointUUID'),
"Description": "Contoso BMC",
"Model": "Joo Janta 200",
"DateTime": dt_info.get(
"DateTime",
datetime.now().strftime('%Y-%m-%dT%H:%M:%S+00:00')),
"DateTimeLocalOffset": dt_info.get(
"DateTimeLocalOffset", "+00:00"),
"Status": {
"State": "Enabled",
"Health": "OK"
},
"PowerState": "On",
"FirmwareVersion": "1.00",
})
return jsonify('Manager', 'v1_3_1', result)
elif flask.request.method == "PATCH":
if app.feature_set != "full":
raise error.MethodNotAllowed("PATCH not supported in minimum mode")
data = flask.request.get_json(force=True)
new_datetime = data.get("DateTime")
new_offset = data.get("DateTimeLocalOffset")
app.managers.set_datetime(new_datetime, new_offset)
app.logger.debug("Updated DateTime for manager %s", identity)
return '', 204
@app.route('/redfish/v1/Systems')

View File

@@ -21,6 +21,8 @@ class FakeDriver(base.DriverBase):
super().__init__(config, logger)
self._systems = systems
self._chassis = chassis
self._datetime = None
self._datetimelocaloffset = None
def get_manager(self, identity):
"""Get a manager by its identity
@@ -42,10 +44,36 @@ class FakeDriver(base.DriverBase):
result = {'Id': system_uuid,
'UUID': system_uuid,
'Name': '%s-Manager' % system_name}
self._logger.debug('Found manager %(mgr)s by UUID %(id)s',
{'mgr': result, 'id': identity})
self._logger.debug(
'Found manager %(mgr)s by UUID %(id)s',
{'mgr': result, 'id': identity}
)
return result
def set_datetime(self, datetime_value, datetimelocaloffset_value):
"""Set the datetime and offset information for a manager
:param datetime_value: The datetime string to set
:param datetimelocaloffset_value:
The time zone offset to set (e.g., "+00:00")
"""
self._datetime = datetime_value
self._datetimelocaloffset = datetimelocaloffset_value
def get_datetime(self):
"""Retrieve the datetime and offset information for a manager
:returns: A dictionary with 'DateTime' and 'DateTimeLocalOffset' keys,
or an empty dict if no values are set
"""
if self._datetime and self._datetimelocaloffset:
return {
'DateTime': self._datetime,
'DateTimeLocalOffset': self._datetimelocaloffset
}
return {}
@property
def driver(self):
"""Return human-friendly driver information

View File

@@ -36,6 +36,9 @@ class FakeDriverTestCase(base.BaseTestCase):
self.test_driver = managers.FakeDriver({}, mock.Mock(),
self.systems, self.chassis)
self.datetime_value = "2025-06-11T12:00:00+00:00"
self.datetimelocaloffset_value = "+00:00"
def test_get_manager_not_found(self):
self.systems.uuid.side_effect = error.FishyError('boom')
self.assertRaises(
@@ -52,3 +55,24 @@ class FakeDriverTestCase(base.BaseTestCase):
def test_managed_systems(self):
self.assertEqual(
['xxx'], self.test_driver.get_managed_systems(self.manager))
def test_set_datetime(self):
self.test_driver.set_datetime(self.datetime_value,
self.datetimelocaloffset_value)
self.assertEqual(self.test_driver._datetime, self.datetime_value)
self.assertEqual(self.test_driver._datetimelocaloffset,
self.datetimelocaloffset_value)
def test_get_datetime_returns_expected_dict(self):
self.test_driver.set_datetime(self.datetime_value,
self.datetimelocaloffset_value)
result = self.test_driver.get_datetime()
expected = {
"DateTime": self.datetime_value,
"DateTimeLocalOffset": self.datetimelocaloffset_value
}
self.assertEqual(result, expected)
def test_get_datetime_returns_empty_dict_when_not_set(self):
result = self.test_driver.get_datetime()
self.assertEqual(result, {})

View File

@@ -221,6 +221,9 @@ class ManagersTestCase(EmulatorTestCase):
}
managers_mock.get_managed_systems.return_value = ['xxx']
managers_mock.get_managed_chassis.return_value = ['chassis0']
managers_mock.get_datetime.return_value = {
"DateTime": "2025-06-02T12:00:00+00:00",
"DateTimeLocalOffset": "+00:00"}
response = self.app.get('/redfish/v1/Managers/xxxx-yyyy-zzzz')