Merge "Fix missing ETag when patching Redfish resource"

This commit is contained in:
Zuul 2023-11-14 09:21:59 +00:00 committed by Gerrit Code Review
commit b87bc5448d
17 changed files with 81 additions and 25 deletions

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Fixes missing ETag in PATCH operation against Redfish resources with
backward compatibility for Redfish implementation which doesn't work
with ETag in header.

View File

@ -214,9 +214,10 @@ class Chassis(base.ResourceBase):
parameter='state', value=state, parameter='state', value=state,
valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED)) valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED))
etag = self._get_etag()
data = {'IndicatorLED': state} data = {'IndicatorLED': state}
self._conn.patch(self.path, data=data) self._conn.patch(self.path, data=data, etag=etag)
self.invalidate() self.invalidate()
@property @property

View File

@ -248,8 +248,10 @@ class VirtualMedia(base.ResourceBase):
parameter='verify_certificate', value=verify_certificate, parameter='verify_certificate', value=verify_certificate,
valid_values='boolean (True, False)') valid_values='boolean (True, False)')
etag = self._get_etag()
self._conn.patch(self.path, self._conn.patch(self.path,
data={'VerifyCertificate': verify_certificate}) data={'VerifyCertificate': verify_certificate},
etag=etag)
self.invalidate() self.invalidate()
@property @property

View File

@ -170,7 +170,7 @@ class SettingsField(base.CompositeField):
:returns: Response object :returns: Response object
""" """
return connector.patch(self.resource_uri, data=value) return connector.patch(self.resource_uri, data=value, etag=self._etag)
@property @property
def resource_uri(self): def resource_uri(self):

View File

@ -149,6 +149,10 @@ class Bios(base.ResourceBase):
payload = utils.process_apply_time_input( payload = utils.process_apply_time_input(
payload, apply_time, maint_window_start_time, payload, apply_time, maint_window_start_time,
maint_window_duration) maint_window_duration)
# NOTE(vanou): To retrieve current ETag value of @Redfish.Settings
# but not update cached _pending_settings_resource, because cached
# property is only this one and re-cache is not required
self.refresh(force=False)
self._settings.commit(self._conn, self._settings.commit(self._conn,
payload) payload)
utils.cache_clear(self, force_refresh=False, utils.cache_clear(self, force_refresh=False,

View File

@ -144,4 +144,6 @@ class SecureBoot(base.ResourceBase):
raise exceptions.InvalidParameterValueError( raise exceptions.InvalidParameterValueError(
"Expected a boolean for 'enabled', got %r" % enabled) "Expected a boolean for 'enabled', got %r" % enabled)
self._conn.patch(self.path, data={'SecureBootEnable': enabled}) etag = self._get_etag()
self._conn.patch(self.path, data={'SecureBootEnable': enabled},
etag=etag)

View File

@ -97,6 +97,10 @@ class StorageController(base.ResourceBase):
payload = utils.process_apply_time_input( payload = utils.process_apply_time_input(
payload, apply_time, maint_window_start_time, payload, apply_time, maint_window_start_time,
maint_window_duration) maint_window_duration)
# NOTE(vanou): To retrieve current ETag value of @Redfish.Settings
# but not update cached pending_settings, because cached property is
# only this one and re-cache this is not required
self.refresh(force=False)
r = self._settings.commit(self._conn, payload) r = self._settings.commit(self._conn, payload)
utils.cache_clear(self, force_refresh=False, utils.cache_clear(self, force_refresh=False,
only_these=['pending_settings']) only_these=['pending_settings'])

View File

@ -102,7 +102,8 @@ class Drive(base.ResourceBase):
parameter='state', value=state, parameter='state', value=state,
valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED)) valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED))
etag = self._get_etag()
data = {'IndicatorLED': state} data = {'IndicatorLED': state}
self._conn.patch(self.path, data=data) self._conn.patch(self.path, data=data, etag=etag)
self.invalidate() self.invalidate()

View File

@ -342,9 +342,10 @@ class System(base.ResourceBase):
parameter='state', value=state, parameter='state', value=state,
valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED)) valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED))
etag = self._get_etag()
data = {'IndicatorLED': state} data = {'IndicatorLED': state}
self._conn.patch(self.path, data=data) self._conn.patch(self.path, data=data, etag=etag)
self.invalidate() self.invalidate()
def _get_processor_collection_path(self): def _get_processor_collection_path(self):

View File

@ -34,6 +34,7 @@ class ChassisTestCase(base.TestCase):
self.json_doc = json.load(f) self.json_doc = json.load(f)
self.conn.get.return_value.json.return_value = self.json_doc self.conn.get.return_value.json.return_value = self.json_doc
self.conn.get.return_value.headers = {'ETag': 'd37f7bcd528e4d59'}
self.chassis = chassis.Chassis(self.conn, '/redfish/v1/Chassis/Blade1', self.chassis = chassis.Chassis(self.conn, '/redfish/v1/Chassis/Blade1',
redfish_version='1.8.0') redfish_version='1.8.0')
@ -144,7 +145,8 @@ class ChassisTestCase(base.TestCase):
self.chassis.set_indicator_led(sushy.IndicatorLED.BLINKING) self.chassis.set_indicator_led(sushy.IndicatorLED.BLINKING)
self.chassis._conn.patch.assert_called_once_with( self.chassis._conn.patch.assert_called_once_with(
'/redfish/v1/Chassis/Blade1', '/redfish/v1/Chassis/Blade1',
data={'IndicatorLED': 'Blinking'}) data={'IndicatorLED': 'Blinking'},
etag='d37f7bcd528e4d59')
invalidate_mock.assert_called_once_with() invalidate_mock.assert_called_once_with()

View File

@ -294,13 +294,16 @@ class VirtualMediaTestCase(base.TestCase):
self.assertTrue(self.sys_virtual_media._is_stale) self.assertTrue(self.sys_virtual_media._is_stale)
def test_set_verify_certificate(self): def test_set_verify_certificate(self):
self.conn.get.return_value.headers = {'Allow': 'GET,HEAD',
'ETag': '3d7b8a7360bf2941d'}
with mock.patch.object( with mock.patch.object(
self.sys_virtual_media, 'invalidate', self.sys_virtual_media, 'invalidate',
autospec=True) as invalidate_mock: autospec=True) as invalidate_mock:
self.sys_virtual_media.set_verify_certificate(True) self.sys_virtual_media.set_verify_certificate(True)
self.sys_virtual_media._conn.patch.assert_called_once_with( self.sys_virtual_media._conn.patch.assert_called_once_with(
"/redfish/v1/Managers/BMC/VirtualMedia/Floppy1", "/redfish/v1/Managers/BMC/VirtualMedia/Floppy1",
data={'VerifyCertificate': True}) data={'VerifyCertificate': True},
etag='3d7b8a7360bf2941d')
invalidate_mock.assert_called_once_with() invalidate_mock.assert_called_once_with()

View File

@ -65,6 +65,8 @@ class ControllerTestCase(base.TestCase):
self.controller.supported_apply_times) self.controller.supported_apply_times)
def test_update(self): def test_update(self):
self.conn.get.return_value.json.side_effect = [
self.json_doc, self.json_doc]
mock_response = mock.Mock() mock_response = mock.Mock()
mock_response.status_code = http_client.ACCEPTED mock_response.status_code = http_client.ACCEPTED
mock_response.headers = {'Content-Length': 42, mock_response.headers = {'Content-Length': 42,
@ -81,7 +83,8 @@ class ControllerTestCase(base.TestCase):
data={'ControllerRates': {'ConsistencyCheckRatePercent': 30}, data={'ControllerRates': {'ConsistencyCheckRatePercent': 30},
'@Redfish.SettingsApplyTime': { '@Redfish.SettingsApplyTime': {
'@odata.type': '#Settings.v1_0_0.PreferredApplyTime', '@odata.type': '#Settings.v1_0_0.PreferredApplyTime',
'ApplyTime': 'OnReset'}}) 'ApplyTime': 'OnReset'}},
etag=None)
self.assertIsInstance(tm, taskmonitor.TaskMonitor) self.assertIsInstance(tm, taskmonitor.TaskMonitor)
self.assertEqual('/Task/545', tm.task_monitor_uri) self.assertEqual('/Task/545', tm.task_monitor_uri)

View File

@ -83,13 +83,15 @@ class DriveTestCase(base.TestCase):
self.assertEqual('3', volumes[1].identity) self.assertEqual('3', volumes[1].identity)
def test_set_indicator_led(self): def test_set_indicator_led(self):
self.conn.get.return_value.headers = {'ETag': 'a3b01b63f80a4913'}
with mock.patch.object( with mock.patch.object(
self.stor_drive, 'invalidate', self.stor_drive, 'invalidate',
autospec=True) as invalidate_mock: autospec=True) as invalidate_mock:
self.stor_drive.set_indicator_led(sushy.IndicatorLED.BLINKING) self.stor_drive.set_indicator_led(sushy.IndicatorLED.BLINKING)
self.stor_drive._conn.patch.assert_called_once_with( self.stor_drive._conn.patch.assert_called_once_with(
'/redfish/v1/Systems/437XR1138/Storage/1/Drives/' '/redfish/v1/Systems/437XR1138/Storage/1/Drives/'
'32ADF365C6C1B7BD', data={'IndicatorLED': 'Blinking'}) '32ADF365C6C1B7BD', data={'IndicatorLED': 'Blinking'},
etag='a3b01b63f80a4913')
invalidate_mock.assert_called_once_with() invalidate_mock.assert_called_once_with()

View File

@ -119,6 +119,10 @@ class BiosTestCase(base.TestCase):
attributes.get('maintenance_window')) attributes.get('maintenance_window'))
def test_set_attribute_apply_time(self): def test_set_attribute_apply_time(self):
self.conn.get.return_value.json.side_effect = [
self.bios_json,
self.bios_json]
self.sys_bios.set_attribute( self.sys_bios.set_attribute(
'ProcTurboMode', 'Disabled', 'ProcTurboMode', 'Disabled',
res_cons.ApplyTime.IN_MAINTENANCE_WINDOW_ON_RESET, res_cons.ApplyTime.IN_MAINTENANCE_WINDOW_ON_RESET,
@ -131,9 +135,15 @@ class BiosTestCase(base.TestCase):
'@odata.type': '#Settings.v1_0_0.PreferredApplyTime', '@odata.type': '#Settings.v1_0_0.PreferredApplyTime',
'ApplyTime': 'InMaintenanceWindowOnReset', 'ApplyTime': 'InMaintenanceWindowOnReset',
'MaintenanceWindowStartTime': '2020-09-01T04:30:00', 'MaintenanceWindowStartTime': '2020-09-01T04:30:00',
'MaintenanceWindowDurationInSeconds': 600}}) 'MaintenanceWindowDurationInSeconds': 600}},
etag='9234ac83b9700123cc32')
def test_set_attribute_on_refresh(self): def test_set_attribute_on_refresh(self):
self.conn.get.return_value.json.side_effect = [
self.bios_settings_json,
self.bios_json,
self.bios_settings_json]
self.conn.get.reset_mock() self.conn.get.reset_mock()
# make it to instantiate pending attributes # make it to instantiate pending attributes
self.sys_bios.pending_attributes self.sys_bios.pending_attributes
@ -150,6 +160,9 @@ class BiosTestCase(base.TestCase):
self.assertTrue(self.conn.get.called) self.assertTrue(self.conn.get.called)
def test_set_attributes(self): def test_set_attributes(self):
self.conn.get.return_value.json.side_effect = [
self.bios_json]
self.sys_bios.set_attributes( self.sys_bios.set_attributes(
{'ProcTurboMode': 'Disabled', 'UsbControl': 'UsbDisabled'}, {'ProcTurboMode': 'Disabled', 'UsbControl': 'UsbDisabled'},
res_cons.ApplyTime.AT_MAINTENANCE_WINDOW_START, res_cons.ApplyTime.AT_MAINTENANCE_WINDOW_START,
@ -163,9 +176,15 @@ class BiosTestCase(base.TestCase):
'@odata.type': '#Settings.v1_0_0.PreferredApplyTime', '@odata.type': '#Settings.v1_0_0.PreferredApplyTime',
'ApplyTime': 'AtMaintenanceWindowStart', 'ApplyTime': 'AtMaintenanceWindowStart',
'MaintenanceWindowStartTime': '2020-09-01T04:30:00', 'MaintenanceWindowStartTime': '2020-09-01T04:30:00',
'MaintenanceWindowDurationInSeconds': 600}}) 'MaintenanceWindowDurationInSeconds': 600}},
etag='9234ac83b9700123cc32')
def test_set_attributes_on_refresh(self): def test_set_attributes_on_refresh(self):
self.conn.get.return_value.json.side_effect = [
self.bios_settings_json,
self.bios_json,
self.bios_settings_json]
self.conn.get.reset_mock() self.conn.get.reset_mock()
# make it to instantiate pending attributes # make it to instantiate pending attributes
self.sys_bios.pending_attributes self.sys_bios.pending_attributes

View File

@ -29,6 +29,7 @@ class SecureBootTestCase(base.TestCase):
self.secure_boot_json = json.load(f) self.secure_boot_json = json.load(f)
self.conn.get.return_value.json.return_value = self.secure_boot_json self.conn.get.return_value.json.return_value = self.secure_boot_json
self.conn.get.return_value.headers = {'ETag': 'b26ae716a2c1f39f'}
self.secure_boot = secure_boot.SecureBoot( self.secure_boot = secure_boot.SecureBoot(
self.conn, '/redfish/v1/Systems/437XR1138R2/SecureBoot', self.conn, '/redfish/v1/Systems/437XR1138R2/SecureBoot',
registries={}, redfish_version='1.1.0') registries={}, redfish_version='1.1.0')
@ -79,7 +80,8 @@ class SecureBootTestCase(base.TestCase):
self.secure_boot.set_enabled(True) self.secure_boot.set_enabled(True)
self.conn.patch.assert_called_once_with( self.conn.patch.assert_called_once_with(
'/redfish/v1/Systems/437XR1138R2/SecureBoot', '/redfish/v1/Systems/437XR1138R2/SecureBoot',
data={'SecureBootEnable': True}) data={'SecureBootEnable': True},
etag='b26ae716a2c1f39f')
def test_set_enabled_wrong_type(self): def test_set_enabled_wrong_type(self):
self.assertRaises(exceptions.InvalidParameterValueError, self.assertRaises(exceptions.InvalidParameterValueError,

View File

@ -46,6 +46,8 @@ class SystemTestCase(base.TestCase):
self.sys_inst = system.System( self.sys_inst = system.System(
self.conn, '/redfish/v1/Systems/437XR1138R2', self.conn, '/redfish/v1/Systems/437XR1138R2',
redfish_version='1.0.2') redfish_version='1.0.2')
self.sys_inst._get_etag = mock.Mock()
self.sys_inst._get_etag.return_value = '81802dbf61beb0bd'
def test__parse_attributes(self): def test__parse_attributes(self):
self.sys_inst._parse_attributes(self.json_doc) self.sys_inst._parse_attributes(self.json_doc)
@ -283,7 +285,7 @@ class SystemTestCase(base.TestCase):
data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
'BootSourceOverrideTarget': 'Pxe', 'BootSourceOverrideTarget': 'Pxe',
'BootSourceOverrideMode': 'UEFI'}}, 'BootSourceOverrideMode': 'UEFI'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_options_no_mode_specified(self): def test_set_system_boot_options_no_mode_specified(self):
self.sys_inst.set_system_boot_options( self.sys_inst.set_system_boot_options(
@ -293,7 +295,7 @@ class SystemTestCase(base.TestCase):
'/redfish/v1/Systems/437XR1138R2', '/redfish/v1/Systems/437XR1138R2',
data={'Boot': {'BootSourceOverrideEnabled': 'Once', data={'Boot': {'BootSourceOverrideEnabled': 'Once',
'BootSourceOverrideTarget': 'Hdd'}}, 'BootSourceOverrideTarget': 'Hdd'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_options_no_target_specified(self): def test_set_system_boot_options_no_target_specified(self):
self.sys_inst.set_system_boot_options( self.sys_inst.set_system_boot_options(
@ -303,7 +305,7 @@ class SystemTestCase(base.TestCase):
'/redfish/v1/Systems/437XR1138R2', '/redfish/v1/Systems/437XR1138R2',
data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
'BootSourceOverrideMode': 'UEFI'}}, 'BootSourceOverrideMode': 'UEFI'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_options_no_freq_specified(self): def test_set_system_boot_options_no_freq_specified(self):
self.sys_inst.set_system_boot_options( self.sys_inst.set_system_boot_options(
@ -313,7 +315,7 @@ class SystemTestCase(base.TestCase):
'/redfish/v1/Systems/437XR1138R2', '/redfish/v1/Systems/437XR1138R2',
data={'Boot': {'BootSourceOverrideTarget': 'Pxe', data={'Boot': {'BootSourceOverrideTarget': 'Pxe',
'BootSourceOverrideMode': 'UEFI'}}, 'BootSourceOverrideMode': 'UEFI'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_options_nothing_specified(self): def test_set_system_boot_options_nothing_specified(self):
self.sys_inst.set_system_boot_options() self.sys_inst.set_system_boot_options()
@ -348,7 +350,7 @@ class SystemTestCase(base.TestCase):
'/redfish/v1/Systems/437XR1138R2', '/redfish/v1/Systems/437XR1138R2',
data={'Boot': {'BootSourceOverrideEnabled': 'Once', data={'Boot': {'BootSourceOverrideEnabled': 'Once',
'BootSourceOverrideTarget': 'UsbCd'}}, 'BootSourceOverrideTarget': 'UsbCd'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_options_supermicro_no_usb_cd_boot(self): def test_set_system_boot_options_supermicro_no_usb_cd_boot(self):
@ -361,7 +363,7 @@ class SystemTestCase(base.TestCase):
'/redfish/v1/Systems/437XR1138R2', '/redfish/v1/Systems/437XR1138R2',
data={'Boot': {'BootSourceOverrideEnabled': 'Once', data={'Boot': {'BootSourceOverrideEnabled': 'Once',
'BootSourceOverrideTarget': 'Cd'}}, 'BootSourceOverrideTarget': 'Cd'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_options_settings_resource_nokia(self): def test_set_system_boot_options_settings_resource_nokia(self):
with open('sushy/tests/unit/json_samples/settings-nokia.json') as f: with open('sushy/tests/unit/json_samples/settings-nokia.json') as f:
@ -487,10 +489,10 @@ class SystemTestCase(base.TestCase):
data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
'BootSourceOverrideTarget': 'Pxe', 'BootSourceOverrideTarget': 'Pxe',
'BootSourceOverrideMode': 'UEFI'}}, 'BootSourceOverrideMode': 'UEFI'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_source_with_etag(self): def test_set_system_boot_source_with_etag(self):
self.conn.get.return_value.headers = {'ETag': '"3d7b838291941d"'} self.conn.get.return_value.headers = {'ETag': '"81802dbf61beb0bd"'}
self.sys_inst.set_system_boot_source( self.sys_inst.set_system_boot_source(
sushy.BOOT_SOURCE_TARGET_PXE, sushy.BOOT_SOURCE_TARGET_PXE,
enabled=sushy.BOOT_SOURCE_ENABLED_CONTINUOUS, enabled=sushy.BOOT_SOURCE_ENABLED_CONTINUOUS,
@ -500,7 +502,7 @@ class SystemTestCase(base.TestCase):
data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
'BootSourceOverrideTarget': 'Pxe', 'BootSourceOverrideTarget': 'Pxe',
'BootSourceOverrideMode': 'UEFI'}}, 'BootSourceOverrideMode': 'UEFI'}},
etag='"3d7b838291941d"') etag="81802dbf61beb0bd")
def test_set_system_boot_source_no_mode_specified(self): def test_set_system_boot_source_no_mode_specified(self):
self.sys_inst.set_system_boot_source( self.sys_inst.set_system_boot_source(
@ -510,7 +512,7 @@ class SystemTestCase(base.TestCase):
'/redfish/v1/Systems/437XR1138R2', '/redfish/v1/Systems/437XR1138R2',
data={'Boot': {'BootSourceOverrideEnabled': 'Once', data={'Boot': {'BootSourceOverrideEnabled': 'Once',
'BootSourceOverrideTarget': 'Hdd'}}, 'BootSourceOverrideTarget': 'Hdd'}},
etag=None) etag='81802dbf61beb0bd')
def test_set_system_boot_source_invalid_target(self): def test_set_system_boot_source_invalid_target(self):
self.assertRaises(exceptions.InvalidParameterValueError, self.assertRaises(exceptions.InvalidParameterValueError,
@ -533,7 +535,8 @@ class SystemTestCase(base.TestCase):
self.sys_inst.set_indicator_led(sushy.IndicatorLED.BLINKING) self.sys_inst.set_indicator_led(sushy.IndicatorLED.BLINKING)
self.sys_inst._conn.patch.assert_called_once_with( self.sys_inst._conn.patch.assert_called_once_with(
'/redfish/v1/Systems/437XR1138R2', '/redfish/v1/Systems/437XR1138R2',
data={'IndicatorLED': 'Blinking'}) data={'IndicatorLED': 'Blinking'},
etag='81802dbf61beb0bd')
invalidate_mock.assert_called_once_with() invalidate_mock.assert_called_once_with()

View File

@ -78,7 +78,8 @@ class SettingsFieldTestCase(base.TestCase):
instance.commit(conn, {'Attributes': {'key': 'value'}}) instance.commit(conn, {'Attributes': {'key': 'value'}})
conn.patch.assert_called_once_with( conn.patch.assert_called_once_with(
'/redfish/v1/Systems/437XR1138R2/BIOS/Settings', '/redfish/v1/Systems/437XR1138R2/BIOS/Settings',
data={'Attributes': {'key': 'value'}}) data={'Attributes': {'key': 'value'}},
etag='9234ac83b9700123cc32')
def test_get_status_failure(self): def test_get_status_failure(self):
instance = self.settings._load(self.json, mock.Mock()) instance = self.settings._load(self.json, mock.Mock())