Merge "Add PATCH support for Redfish DateTime fields in Manager resource"
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||
@@ -85,10 +85,6 @@ class Application(flask.Flask):
|
|||||||
# This is needed for WSGI since it cannot process argv
|
# This is needed for WSGI since it cannot process argv
|
||||||
self.configure(config_file=os.environ.get('SUSHY_EMULATOR_CONFIG'))
|
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):
|
def configure(self, config_file=None, extra_config=None):
|
||||||
if config_file:
|
if config_file:
|
||||||
self.config.from_pyfile(os.path.abspath(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)
|
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
|
@api_utils.returns_json
|
||||||
def manager_resource(identity):
|
def manager_resource(identity):
|
||||||
if app.feature_set == "minimum":
|
if app.feature_set == "minimum":
|
||||||
raise error.FeatureNotAvailable("Managers")
|
raise error.FeatureNotAvailable("Managers")
|
||||||
|
|
||||||
app.logger.debug('Serving resources for manager "%s"', identity)
|
|
||||||
|
|
||||||
manager = app.managers.get_manager(identity)
|
manager = app.managers.get_manager(identity)
|
||||||
systems = app.managers.get_managed_systems(manager)
|
systems = app.managers.get_managed_systems(manager)
|
||||||
chassis = app.managers.get_managed_chassis(manager)
|
chassis = app.managers.get_managed_chassis(manager)
|
||||||
|
|
||||||
uuid = manager['UUID']
|
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')
|
@app.route('/redfish/v1/Systems')
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ class FakeDriver(base.DriverBase):
|
|||||||
super().__init__(config, logger)
|
super().__init__(config, logger)
|
||||||
self._systems = systems
|
self._systems = systems
|
||||||
self._chassis = chassis
|
self._chassis = chassis
|
||||||
|
self._datetime = None
|
||||||
|
self._datetimelocaloffset = None
|
||||||
|
|
||||||
def get_manager(self, identity):
|
def get_manager(self, identity):
|
||||||
"""Get a manager by its identity
|
"""Get a manager by its identity
|
||||||
@@ -42,10 +44,36 @@ class FakeDriver(base.DriverBase):
|
|||||||
result = {'Id': system_uuid,
|
result = {'Id': system_uuid,
|
||||||
'UUID': system_uuid,
|
'UUID': system_uuid,
|
||||||
'Name': '%s-Manager' % system_name}
|
'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
|
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
|
@property
|
||||||
def driver(self):
|
def driver(self):
|
||||||
"""Return human-friendly driver information
|
"""Return human-friendly driver information
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ class FakeDriverTestCase(base.BaseTestCase):
|
|||||||
self.test_driver = managers.FakeDriver({}, mock.Mock(),
|
self.test_driver = managers.FakeDriver({}, mock.Mock(),
|
||||||
self.systems, self.chassis)
|
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):
|
def test_get_manager_not_found(self):
|
||||||
self.systems.uuid.side_effect = error.FishyError('boom')
|
self.systems.uuid.side_effect = error.FishyError('boom')
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
@@ -52,3 +55,24 @@ class FakeDriverTestCase(base.BaseTestCase):
|
|||||||
def test_managed_systems(self):
|
def test_managed_systems(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
['xxx'], self.test_driver.get_managed_systems(self.manager))
|
['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, {})
|
||||||
|
|||||||
@@ -221,6 +221,9 @@ class ManagersTestCase(EmulatorTestCase):
|
|||||||
}
|
}
|
||||||
managers_mock.get_managed_systems.return_value = ['xxx']
|
managers_mock.get_managed_systems.return_value = ['xxx']
|
||||||
managers_mock.get_managed_chassis.return_value = ['chassis0']
|
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')
|
response = self.app.get('/redfish/v1/Managers/xxxx-yyyy-zzzz')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user