diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index e3d408e0..4f6695ba 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -437,6 +437,13 @@ def md_restart(raid_device): """ try: LOG.debug('Restarting software RAID device %s', raid_device) + # NOTE(mnasiadka): If the image is LVM based we need to clean + # dmsetup devices, because mdadm --stop will fail. + dmsetup_stdout, _ = il_utils.execute('dmsetup', 'table', + use_standard_locale=True) + if 'No devices found' not in dmsetup_stdout: + LOG.debug('Deactivating LVM') + il_utils.execute('dmsetup', 'remove_all') component_devices = get_component_devices(raid_device) il_utils.execute('mdadm', '--stop', raid_device) il_utils.execute('mdadm', '--assemble', raid_device, @@ -550,6 +557,7 @@ def _is_known_device(existing, new_device_name): report_json = json.loads(report) except json.decoder.JSONDecodeError as ex: LOG.error("Unable to decode lsblk output, invalid JSON: %s", ex) + raise context = pyudev.Context() devices_raw = report_json['blockdevices'] @@ -605,11 +613,11 @@ def _is_known_device(existing, new_device_name): {'device_raw': device_raw}) elif (devtype == 'md' and (block_type == 'part' - or block_type == 'md')): - # NOTE(dszumski): Partitions on software RAID devices have type - # 'md'. This may also contain RAID devices in a broken state in - # rare occasions. See https://review.opendev.org/#/c/670807 for - # more detail. + or block_type == 'lvm')): + # NOTE(dszumski): Partitions and LVM volumes on software RAID + # devices have type 'md'. This may also contain RAID devices in + # a broken state in rare occasions. See + # https://review.opendev.org/#/c/670807 for more detail. LOG.debug( "TYPE detected to contain 'md', signifying a " "RAID partition. Found: %(device_raw)s", diff --git a/ironic_python_agent/tests/unit/samples/hardware_samples.py b/ironic_python_agent/tests/unit/samples/hardware_samples.py index 6b1018d3..1bd6995b 100644 --- a/ironic_python_agent/tests/unit/samples/hardware_samples.py +++ b/ironic_python_agent/tests/unit/samples/hardware_samples.py @@ -187,7 +187,9 @@ {"kname":"md0", "model":"RAID", "size":1765517033470, "rota":false, "type":"raid1", "serial":null, "uuid":null, "partuuid":null}, {"kname":"md1", "model":"RAID", "size":0, "rota":false, "type":"raid1", - "serial":null, "uuid":null, "partuuid":null} + "serial":null, "uuid":null, "partuuid":null}, + {"kname":"dm-0", "model":"RAID", "size":1765517033470, "rota":false, + "type":"md", "uuid":null, "partuuid":null} ] } """) diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index fdb5c8df..c9bab63e 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -1701,9 +1701,9 @@ def test_list_block_devices_check_skip_list_with_complete_skip_list( @mock.patch.object(hardware, '_get_device_info', autospec=True) @mock.patch.object(pyudev.Devices, 'from_device_file', autospec=False) @mock.patch.object(il_utils, 'execute', autospec=True) - def test_list_all_block_device(self, mocked_execute, mocked_udev, - mocked_dev_vendor, mock_listdir, - mock_readlink): + def test_list_all_block_devices(self, mocked_execute, mocked_udev, + mocked_dev_vendor, mock_listdir, + mock_readlink): by_path_map = { '/dev/disk/by-path/1:0:0:0': '../../dev/sda', '/dev/disk/by-path/1:0:0:1': '../../dev/sdb', @@ -5602,6 +5602,66 @@ def test_list_all_block_devices_success_raid(self, mocked_fromdevfile, @mock.patch.object(hardware, '_get_device_info', lambda x, y, z: 'FooTastic') @mock.patch.object(hardware, '_udev_settle', autospec=True) + @mock.patch.object(hardware.pyudev.Devices, "from_device_file", + autospec=False) + def test_list_all_block_devices_success_raid_lvm(self, mocked_fromdevfile, + mocked_udev, + mocked_readlink, + mocked_mpath, + mocked_execute): + mocked_readlink.return_value = '../../sda' + mocked_fromdevfile.return_value = {} + mocked_mpath.return_value = True + mocked_execute.side_effect = [ + (hws.RAID_BLK_DEVICE_TEMPLATE, ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda1'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb1'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # md0p1 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # md0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # md0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # md1 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-0 + ] + expected_calls = [ + mock.call('lsblk', '-bia', '--json', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID', + check_exit_code=[0]), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sda1'), + mock.call('multipath', '-c', '/dev/sdb'), + mock.call('multipath', '-c', '/dev/sdb'), + mock.call('multipath', '-c', '/dev/sdb1'), + mock.call('multipath', '-c', '/dev/md0p1'), + mock.call('multipath', '-c', '/dev/md0'), + mock.call('multipath', '-c', '/dev/md0'), + mock.call('multipath', '-c', '/dev/md1'), + mock.call('multipath', '-c', '/dev/dm-0'), + ] + result = hardware.list_all_block_devices(block_type='lvm', + ignore_empty=False) + mocked_execute.assert_has_calls(expected_calls) + hardware.LOG.error(result[0].__dict__) + self.assertEqual(RAID_BLK_DEVICE_TEMPLATE_DEVICES[3:4], result) + mocked_udev.assert_called_once_with() + + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) + @mock.patch.object(os, 'readlink', autospec=True) + @mock.patch.object(hardware, '_get_device_info', + lambda x, y, z: 'FooTastic') + @mock.patch.object(disk_utils, 'udev_settle', autospec=True) @mock.patch.object(hardware.pyudev.Devices, "from_device_file", autospec=False) def test_list_all_block_devices_partuuid_success( diff --git a/releasenotes/notes/lvm-md-322bdd582b407606.yaml b/releasenotes/notes/lvm-md-322bdd582b407606.yaml new file mode 100644 index 00000000..fd09e4bb --- /dev/null +++ b/releasenotes/notes/lvm-md-322bdd582b407606.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for LVM based images in MD (software RAID) scenario.