diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index b9958e198..f245feb93 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -24,6 +24,7 @@ import re import shlex import shutil import stat +import string import time from ironic_lib import disk_utils @@ -1447,23 +1448,32 @@ class GenericHardwareManager(HardwareManager): return 'linux_raid_member' in out - def _is_read_only_device(self, block_device): + def _is_read_only_device(self, block_device, partition=False): """Check if a block device is read-only. Checks the device read-only flag in order to identify virtual and firmware driven devices that block write device access. :param block_device: a BlockDevice object + :param partition: if True, this device is a partition :returns: True if the device is read-only. """ try: - dev_name = str(block_device.name)[5:] + dev_name = os.path.basename(block_device.name) + if partition: + # Check the base device + dev_name = dev_name.rstrip(string.digits) with open('/sys/block/%s/ro' % dev_name, 'r') as f: flag = f.read().strip() if flag == '1': return True except IOError as e: + # Check underlying device as the file may exist there + if (not partition and dev_name[-1].isdigit() + and 'nvme' not in dev_name): + return self._is_read_only_device(block_device, partition=True) + LOG.warning("Could not determine if %(name)s is a" "read-only device. Error: %(err)s", {'name': block_device.name, 'err': e}) diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index 646cf01b8..086080be7 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -2184,6 +2184,26 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock_open.assert_called_once_with( '/sys/block/sdfake/ro', 'r') + def test__is_read_only_device_partition_error(self): + device = hardware.BlockDevice('/dev/sdfake1', 'fake', 1024, False) + with mock.patch( + 'builtins.open', side_effect=IOError, + autospec=True) as mock_open: + self.assertFalse(self.hardware._is_read_only_device(device)) + mock_open.assert_has_calls([ + mock.call('/sys/block/sdfake1/ro', 'r'), + mock.call('/sys/block/sdfake/ro', 'r')]) + + def test__is_read_only_device_partition_ok(self): + fileobj = mock.mock_open(read_data='1\n') + device = hardware.BlockDevice('/dev/sdfake1', 'fake', 1024, False) + reads = [IOError, '1'] + with mock.patch( + 'builtins.open', fileobj, create=True) as mock_open: + mock_dev_file = mock_open.return_value.read + mock_dev_file.side_effect = reads + self.assertTrue(self.hardware._is_read_only_device(device)) + @mock.patch.object(il_utils, 'try_execute', autospec=True) @mock.patch.object(utils, 'execute', autospec=True) def test_get_bmc_address(self, mocked_execute, mte): diff --git a/releasenotes/notes/partition_check_read_only_base_device-5bc15ac2f034aca9.yaml b/releasenotes/notes/partition_check_read_only_base_device-5bc15ac2f034aca9.yaml new file mode 100644 index 000000000..b1451eb1c --- /dev/null +++ b/releasenotes/notes/partition_check_read_only_base_device-5bc15ac2f034aca9.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixes an issue where metadata erasure cleaning fails for partitions + because the read-only file isn't found, while it is available at the + base device. Adds a check for the base device file on failure. See + `story 2008696 `_.