diff options
Diffstat (limited to 'ironic_python_agent/tests/unit/test_hardware.py')
-rw-r--r-- | ironic_python_agent/tests/unit/test_hardware.py | 523 |
1 files changed, 485 insertions, 38 deletions
diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index d9d40c4e..17d67822 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -751,60 +751,238 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual('eth1.102', interfaces[4].name) self.assertEqual('eth1.103', interfaces[5].name) + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) @mock.patch.object(os, 'readlink', autospec=True) @mock.patch.object(os, 'listdir', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @mock.patch.object(utils, 'execute', autospec=True) def test_get_os_install_device(self, mocked_execute, mock_cached_node, - mocked_listdir, mocked_readlink): + mocked_listdir, mocked_readlink, + mocked_mpath): mocked_readlink.return_value = '../../sda' mocked_listdir.return_value = ['1:0:0:0'] mock_cached_node.return_value = None - mocked_execute.return_value = (hws.BLK_DEVICE_TEMPLATE, '') + mocked_mpath.return_value = False + mocked_execute.side_effect = [ + (hws.BLK_DEVICE_TEMPLATE, ''), + ] self.assertEqual('/dev/sdb', self.hardware.get_os_install_device()) - mocked_execute.assert_called_once_with( - 'lsblk', '-Pbia', '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID') mock_cached_node.assert_called_once_with() + self.assertEqual(1, mocked_mpath.call_count) + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) + @mock.patch.object(os, 'readlink', autospec=True) + @mock.patch.object(os, 'listdir', autospec=True) + @mock.patch.object(hardware, 'get_cached_node', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + def test_get_os_install_device_multipath( + self, mocked_execute, mock_cached_node, + mocked_listdir, mocked_readlink, + mocked_mpath): + mocked_mpath.return_value = True + mocked_readlink.return_value = '../../sda' + mocked_listdir.return_value = ['1:0:0:0'] + mock_cached_node.return_value = None + mocked_execute.side_effect = [ + (hws.MULTIPATH_BLK_DEVICE_TEMPLATE, ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda2', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda3', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda1', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-4 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-2 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-3 + (hws.MULTIPATH_VALID_PATH % '/dev/sdb', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sdb2', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sdb3', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sdb1', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdc'), # sdc + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdc1'), # sdc1 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-1 + ] + expected = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-ll', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sda2'), + mock.call('multipath', '-ll', '/dev/sda2'), + mock.call('multipath', '-c', '/dev/sda3'), + mock.call('multipath', '-ll', '/dev/sda3'), + mock.call('multipath', '-c', '/dev/sda1'), + mock.call('multipath', '-ll', '/dev/sda1'), + mock.call('multipath', '-c', '/dev/dm-0'), + mock.call('multipath', '-c', '/dev/dm-4'), + mock.call('multipath', '-c', '/dev/dm-2'), + mock.call('multipath', '-c', '/dev/dm-3'), + mock.call('multipath', '-c', '/dev/sdb'), + mock.call('multipath', '-ll', '/dev/sdb'), + mock.call('multipath', '-c', '/dev/sdb2'), + mock.call('multipath', '-ll', '/dev/sdb2'), + mock.call('multipath', '-c', '/dev/sdb3'), + mock.call('multipath', '-ll', '/dev/sdb3'), + mock.call('multipath', '-c', '/dev/sdb1'), + mock.call('multipath', '-ll', '/dev/sdb1'), + mock.call('multipath', '-c', '/dev/sdc'), + mock.call('multipath', '-c', '/dev/sdc1'), + mock.call('multipath', '-c', '/dev/dm-1'), + ] + self.assertEqual('/dev/dm-0', self.hardware.get_os_install_device()) + mocked_execute.assert_has_calls(expected) + mock_cached_node.assert_called_once_with() + + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) + @mock.patch.object(os, 'readlink', autospec=True) + @mock.patch.object(os, 'listdir', autospec=True) + @mock.patch.object(hardware, 'get_cached_node', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + def test_get_os_install_device_not_multipath( + self, mocked_execute, mock_cached_node, + mocked_listdir, mocked_readlink, mocked_mpath): + mocked_readlink.return_value = '../../sda' + mocked_listdir.return_value = ['1:0:0:0'] + mocked_mpath.return_value = True + hint = {'size': '>900'} + mock_cached_node.return_value = {'properties': {'root_device': hint}, + 'uuid': 'node1', + 'instance_info': {}} + mocked_execute.side_effect = [ + (hws.MULTIPATH_BLK_DEVICE_TEMPLATE, ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda2', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda3', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sda1', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-4 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-2 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-3 + (hws.MULTIPATH_VALID_PATH % '/dev/sdb', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sdb2', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sdb3', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + (hws.MULTIPATH_VALID_PATH % '/dev/sdb1', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdc'), # sdc + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdc1'), # sdc1 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-1 + ] + expected = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-ll', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sda2'), + mock.call('multipath', '-ll', '/dev/sda2'), + mock.call('multipath', '-c', '/dev/sda3'), + mock.call('multipath', '-ll', '/dev/sda3'), + mock.call('multipath', '-c', '/dev/sda1'), + mock.call('multipath', '-ll', '/dev/sda1'), + mock.call('multipath', '-c', '/dev/dm-0'), + mock.call('multipath', '-c', '/dev/dm-4'), + mock.call('multipath', '-c', '/dev/dm-2'), + mock.call('multipath', '-c', '/dev/dm-3'), + mock.call('multipath', '-c', '/dev/sdb'), + mock.call('multipath', '-ll', '/dev/sdb'), + mock.call('multipath', '-c', '/dev/sdb2'), + mock.call('multipath', '-ll', '/dev/sdb2'), + mock.call('multipath', '-c', '/dev/sdb3'), + mock.call('multipath', '-ll', '/dev/sdb3'), + mock.call('multipath', '-c', '/dev/sdb1'), + mock.call('multipath', '-ll', '/dev/sdb1'), + mock.call('multipath', '-c', '/dev/sdc'), + mock.call('multipath', '-c', '/dev/sdc1'), + mock.call('multipath', '-c', '/dev/dm-1'), + ] + self.assertEqual('/dev/sdc', self.hardware.get_os_install_device()) + mocked_execute.assert_has_calls(expected) + mock_cached_node.assert_called_once_with() + mocked_mpath.assert_called_once_with() + + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) @mock.patch.object(os, 'readlink', autospec=True) @mock.patch.object(os, 'listdir', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @mock.patch.object(utils, 'execute', autospec=True) def test_get_os_install_device_raid(self, mocked_execute, mock_cached_node, mocked_listdir, - mocked_readlink): + mocked_readlink, mocked_mpath): # NOTE(TheJulia): The readlink and listdir mocks are just to satisfy # what is functionally an available path check and that information # is stored in the returned result for use by root device hints. mocked_readlink.side_effect = '../../sda' mocked_listdir.return_value = ['1:0:0:0'] mock_cached_node.return_value = None - mocked_execute.return_value = (hws.RAID_BLK_DEVICE_TEMPLATE, '') + mocked_mpath.return_value = False + mocked_execute.side_effect = [ + (hws.RAID_BLK_DEVICE_TEMPLATE, ''), + ] + # This should ideally select the smallest device and in theory raid # should always be smaller self.assertEqual('/dev/md0', self.hardware.get_os_install_device()) - mocked_execute.assert_called_once_with( - 'lsblk', '-Pbia', '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID') + expected = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + ] + + mocked_execute.assert_has_calls(expected) mock_cached_node.assert_called_once_with() + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) @mock.patch.object(os, 'readlink', autospec=True) @mock.patch.object(os, 'listdir', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @mock.patch.object(utils, 'execute', autospec=True) def test_get_os_install_device_fails(self, mocked_execute, mock_cached_node, - mocked_listdir, mocked_readlink): + mocked_listdir, mocked_readlink, + mocked_mpath): """Fail to find device >=4GB w/o root device hints""" mocked_readlink.return_value = '../../sda' mocked_listdir.return_value = ['1:0:0:0'] + mocked_mpath.return_value = False mock_cached_node.return_value = None mocked_execute.return_value = (hws.BLK_DEVICE_TEMPLATE_SMALL, '') ex = self.assertRaises(errors.DeviceNotFound, self.hardware.get_os_install_device) - mocked_execute.assert_called_once_with( - 'lsblk', '-Pbia', '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID') + expected = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + ] + + mocked_execute.assert_has_calls(expected) self.assertIn(str(4 * units.Gi), ex.details) mock_cached_node.assert_called_once_with() + self.assertEqual(1, mocked_mpath.call_count) @mock.patch.object(hardware, 'list_all_block_devices', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @@ -1211,6 +1389,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): ignore_raid=True)], list_mock.call_args_list) + @mock.patch.object(hardware, 'get_multipath_status', lambda *_: True) @mock.patch.object(os, 'readlink', autospec=True) @mock.patch.object(os, 'listdir', autospec=True) @mock.patch.object(hardware, '_get_device_info', autospec=True) @@ -1228,7 +1407,34 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock_readlink.side_effect = lambda x, m=by_path_map: m[x] mock_listdir.return_value = [os.path.basename(x) for x in sorted(by_path_map)] - mocked_execute.return_value = (hws.BLK_DEVICE_TEMPLATE, '') + mocked_execute.side_effect = [ + (hws.BLK_DEVICE_TEMPLATE, ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdc'), + # Pretend sdd is a multipath device... because why not. + (hws.MULTIPATH_VALID_PATH % '/dev/sdd', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # loop0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # zram0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram1 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram2 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram3 + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdf'), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-0 + ] mocked_udev.side_effect = [pyudev.DeviceNotFoundByFileError(), pyudev.DeviceNotFoundByNumberError('block', 1234), @@ -1258,7 +1464,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): vendor='Super Vendor', hctl='1:0:0:0', by_path='/dev/disk/by-path/1:0:0:2'), - hardware.BlockDevice(name='/dev/sdd', + hardware.BlockDevice(name='/dev/dm-0', model='NWD-BLP4-1600', size=1765517033472, rotational=False, @@ -1274,21 +1480,42 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual(getattr(expected, attr), getattr(device, attr)) expected_calls = [mock.call('/sys/block/%s/device/scsi_device' % dev) - for dev in ('sda', 'sdb', 'sdc', 'sdd')] + for dev in ('sda', 'sdb', 'sdc', 'dm-0')] mock_listdir.assert_has_calls(expected_calls) expected_calls = [mock.call('/dev/disk/by-path/1:0:0:%d' % dev) for dev in range(3)] mock_readlink.assert_has_calls(expected_calls) + expected_calls = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sdb'), + mock.call('multipath', '-c', '/dev/sdc'), + mock.call('multipath', '-c', '/dev/sdd'), + mock.call('multipath', '-ll', '/dev/sdd'), + mock.call('multipath', '-c', '/dev/loop0'), + mock.call('multipath', '-c', '/dev/zram0'), + mock.call('multipath', '-c', '/dev/ram0'), + mock.call('multipath', '-c', '/dev/ram1'), + mock.call('multipath', '-c', '/dev/ram2'), + mock.call('multipath', '-c', '/dev/ram3'), + mock.call('multipath', '-c', '/dev/sdf'), + mock.call('multipath', '-c', '/dev/dm-0') + ] + mocked_execute.assert_has_calls(expected_calls) + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) @mock.patch.object(os, 'listdir', autospec=True) @mock.patch.object(hardware, '_get_device_info', autospec=True) @mock.patch.object(pyudev.Devices, 'from_device_file', autospec=False) @mock.patch.object(utils, 'execute', autospec=True) def test_list_all_block_device_hctl_fail(self, mocked_execute, mocked_udev, mocked_dev_vendor, - mocked_listdir): + mocked_listdir, + mocked_mpath): mocked_listdir.side_effect = (OSError, OSError, IndexError) + mocked_mpath.return_value = False mocked_execute.return_value = (hws.BLK_DEVICE_TEMPLATE_SMALL, '') mocked_dev_vendor.return_value = 'Super Vendor' devices = hardware.list_all_block_devices() @@ -1300,6 +1527,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): ] self.assertEqual(expected_calls, mocked_listdir.call_args_list) + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) @mock.patch.object(os, 'readlink', autospec=True) @mock.patch.object(os, 'listdir', autospec=True) @mock.patch.object(hardware, '_get_device_info', autospec=True) @@ -1307,15 +1535,62 @@ class TestGenericHardwareManager(base.IronicAgentTest): @mock.patch.object(utils, 'execute', autospec=True) def test_list_all_block_device_with_udev(self, mocked_execute, mocked_udev, mocked_dev_vendor, mocked_listdir, - mocked_readlink): + mocked_readlink, mocked_mpath): mocked_readlink.return_value = '../../sda' mocked_listdir.return_value = ['1:0:0:0'] - mocked_execute.return_value = (hws.BLK_DEVICE_TEMPLATE, '') + mocked_execute.side_effect = [ + (hws.BLK_DEVICE_TEMPLATE, ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdc'), + # Pretend sdd is a multipath device... because why not. + (hws.MULTIPATH_VALID_PATH % '/dev/sdd', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # loop0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # zram0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram1 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram2 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram3 + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdf'), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-0 + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ] + + mocked_mpath.return_value = False mocked_udev.side_effect = iter([ {'ID_WWN': 'wwn%d' % i, 'ID_SERIAL_SHORT': 'serial%d' % i, 'ID_WWN_WITH_EXTENSION': 'wwn-ext%d' % i, 'ID_WWN_VENDOR_EXTENSION': 'wwn-vendor-ext%d' % i} - for i in range(4) + for i in range(5) ]) mocked_dev_vendor.return_value = 'Super Vendor' devices = hardware.list_all_block_devices() @@ -1373,6 +1648,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): expected_calls = [mock.call('/sys/block/%s/device/scsi_device' % dev) for dev in ('sda', 'sdb', 'sdc', 'sdd')] mocked_listdir.assert_has_calls(expected_calls) + mocked_mpath.assert_called_once_with() @mock.patch.object(hardware, 'ThreadPool', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @@ -4016,6 +4292,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.hardware._nvme_erase, block_device) +@mock.patch.object(hardware, '_enable_multipath', autospec=True) +@mock.patch.object(hardware, '_load_ipmi_modules', autospec=True) @mock.patch.object(hardware.GenericHardwareManager, 'get_os_install_device', autospec=True) @mock.patch.object(hardware, '_md_scan_and_assemble', autospec=True) @@ -4028,7 +4306,8 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): def test_evaluate_hw_waits_for_disks( self, mocked_sleep, mocked_check_for_iscsi, - mocked_md_assemble, mocked_get_inst_dev): + mocked_md_assemble, mocked_get_inst_dev, + mocked_load_ipmi_modules, mocked_enable_mpath): mocked_get_inst_dev.side_effect = [ errors.DeviceNotFound('boom'), None @@ -4046,7 +4325,8 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): @mock.patch.object(hardware, 'LOG', autospec=True) def test_evaluate_hw_no_wait_for_disks( self, mocked_log, mocked_sleep, mocked_check_for_iscsi, - mocked_md_assemble, mocked_get_inst_dev): + mocked_md_assemble, mocked_get_inst_dev, + mocked_load_ipmi_modules, mocked_enable_mpath): CONF.set_override('disk_wait_attempts', '0') result = self.hardware.evaluate_hardware_support() @@ -4060,7 +4340,8 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): @mock.patch.object(hardware, 'LOG', autospec=True) def test_evaluate_hw_waits_for_disks_nonconfigured( self, mocked_log, mocked_sleep, mocked_check_for_iscsi, - mocked_md_assemble, mocked_get_inst_dev): + mocked_md_assemble, mocked_get_inst_dev, + mocked_load_ipmi_modules, mocked_enable_mpath): mocked_get_inst_dev.side_effect = [ errors.DeviceNotFound('boom'), errors.DeviceNotFound('boom'), @@ -4091,7 +4372,9 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): mocked_sleep, mocked_check_for_iscsi, mocked_md_assemble, - mocked_get_inst_dev): + mocked_get_inst_dev, + mocked_load_ipmi_modules, + mocked_enable_mpath): CONF.set_override('disk_wait_attempts', '1') mocked_get_inst_dev.side_effect = [ @@ -4111,7 +4394,9 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): def test_evaluate_hw_disks_timeout_unconfigured(self, mocked_sleep, mocked_check_for_iscsi, mocked_md_assemble, - mocked_get_inst_dev): + mocked_get_inst_dev, + mocked_load_ipmi_modules, + mocked_enable_mpath): mocked_get_inst_dev.side_effect = errors.DeviceNotFound('boom') self.hardware.evaluate_hardware_support() mocked_sleep.assert_called_with(3) @@ -4119,7 +4404,9 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): def test_evaluate_hw_disks_timeout_configured(self, mocked_sleep, mocked_check_for_iscsi, mocked_md_assemble, - mocked_root_dev): + mocked_root_dev, + mocked_load_ipmi_modules, + mocked_enable_mpath): CONF.set_override('disk_wait_delay', '5') mocked_root_dev.side_effect = errors.DeviceNotFound('boom') @@ -4128,7 +4415,9 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): def test_evaluate_hw_disks_timeout( self, mocked_sleep, mocked_check_for_iscsi, - mocked_md_assemble, mocked_get_inst_dev): + mocked_md_assemble, mocked_get_inst_dev, + mocked_load_ipmi_modules, + mocked_enable_mpath): mocked_get_inst_dev.side_effect = errors.DeviceNotFound('boom') result = self.hardware.evaluate_hardware_support() self.assertEqual(hardware.HardwareSupport.GENERIC, result) @@ -4142,6 +4431,7 @@ class TestEvaluateHardwareSupport(base.IronicAgentTest): @mock.patch.object(utils, 'execute', autospec=True) class TestModuleFunctions(base.IronicAgentTest): + @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') @@ -4150,16 +4440,31 @@ class TestModuleFunctions(base.IronicAgentTest): autospec=False) def test_list_all_block_devices_success(self, mocked_fromdevfile, mocked_udev, mocked_readlink, - mocked_execute): + mocked_mpath, mocked_execute): + mocked_mpath.return_value = True mocked_readlink.return_value = '../../sda' mocked_fromdevfile.return_value = {} - mocked_execute.return_value = (hws.BLK_DEVICE_TEMPLATE_SMALL, '') + mocked_execute.side_effect = [ + (hws.BLK_DEVICE_TEMPLATE_SMALL, ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb'), + ] result = hardware.list_all_block_devices() - mocked_execute.assert_called_once_with( - 'lsblk', '-Pbia', '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID') + expected_calls = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sdb') + ] + + mocked_execute.assert_has_calls(expected_calls) self.assertEqual(BLK_DEVICE_TEMPLATE_SMALL_DEVICES, result) mocked_udev.assert_called_once_with() + mocked_mpath.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') @@ -4168,16 +4473,48 @@ class TestModuleFunctions(base.IronicAgentTest): autospec=False) def test_list_all_block_devices_success_raid(self, mocked_fromdevfile, mocked_udev, mocked_readlink, - mocked_execute): + mocked_mpath, mocked_execute): mocked_readlink.return_value = '../../sda' mocked_fromdevfile.return_value = {} - mocked_execute.return_value = (hws.RAID_BLK_DEVICE_TEMPLATE, '') + 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 + ] + expected_calls = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sda1'), + 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/md1'), + ] result = hardware.list_all_block_devices(ignore_empty=False) - mocked_execute.assert_called_once_with( - 'lsblk', '-Pbia', '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID') + mocked_execute.assert_has_calls(expected_calls) self.assertEqual(RAID_BLK_DEVICE_TEMPLATE_DEVICES, 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') @@ -4187,21 +4524,37 @@ class TestModuleFunctions(base.IronicAgentTest): def test_list_all_block_devices_partuuid_success( self, mocked_fromdevfile, mocked_udev, mocked_readlink, - mocked_execute): + mocked_mpath, mocked_execute): mocked_readlink.return_value = '../../sda' mocked_fromdevfile.return_value = {} - mocked_execute.return_value = (hws.PARTUUID_DEVICE_TEMPLATE, '') + mocked_mpath.return_value = True + mocked_execute.side_effect = [ + (hws.PARTUUID_DEVICE_TEMPLATE, ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb'), + ] result = hardware.list_all_block_devices(block_type='part') - mocked_execute.assert_called_once_with( - 'lsblk', '-Pbia', '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID') + expected_calls = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sda1'), + ] + mocked_execute.assert_has_calls(expected_calls) self.assertEqual(BLK_DEVICE_TEMPLATE_PARTUUID_DEVICE, result) mocked_udev.assert_called_once_with() + mocked_mpath.assert_called_once_with() + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) @mock.patch.object(hardware, '_get_device_info', lambda x, y: "FooTastic") @mock.patch.object(hardware, '_udev_settle', autospec=True) def test_list_all_block_devices_wrong_block_type(self, mocked_udev, + mock_mpath_enabled, mocked_execute): + mock_mpath_enabled.return_value = False mocked_execute.return_value = ('TYPE="foo" MODEL="model"', '') result = hardware.list_all_block_devices() mocked_execute.assert_called_once_with( @@ -4209,17 +4562,32 @@ class TestModuleFunctions(base.IronicAgentTest): self.assertEqual([], result) mocked_udev.assert_called_once_with() + @mock.patch.object(hardware, 'get_multipath_status', autospec=True) @mock.patch.object(hardware, '_udev_settle', autospec=True) def test_list_all_block_devices_missing(self, mocked_udev, + mocked_mpath, mocked_execute): """Test for missing values returned from lsblk""" - mocked_execute.return_value = ('TYPE="disk" MODEL="model"', '') + mocked_mpath.return_value = False + mocked_execute.side_effect = [ + ('TYPE="disk" MODEL="model"', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ] + expected_calls = [ + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), + ] + self.assertRaisesRegex( errors.BlockDeviceError, r'^Block device caused unknown error: KNAME, PARTUUID, ROTA, ' r'SIZE, UUID must be returned by lsblk.$', hardware.list_all_block_devices) mocked_udev.assert_called_once_with() + mocked_execute.assert_has_calls(expected_calls) def test__udev_settle(self, mocked_execute): hardware._udev_settle() @@ -4238,6 +4606,85 @@ class TestModuleFunctions(base.IronicAgentTest): mock.call('iscsistart', '-f')]) +@mock.patch.object(il_utils, 'execute', autospec=True) +class TestMultipathEnabled(base.IronicAgentTest): + + @mock.patch.object(os.path, 'isfile', autospec=True) + def test_enable_multipath_with_config(self, mock_isfile, mocked_execute): + mock_isfile.side_effect = [True, True] + mocked_execute.side_effect = [ + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ] + self.assertTrue(hardware._enable_multipath()) + mocked_execute.assert_has_calls([ + mock.call('modprobe', 'dm_multipath'), + mock.call('modprobe', 'multipath'), + mock.call('multipathd'), + mock.call('multipath', '-ll'), + ]) + + @mock.patch.object(os.path, 'isfile', autospec=True) + def test_enable_multipath_mpathconf(self, mock_isfile, mocked_execute): + mock_isfile.side_effect = [True, False] + mocked_execute.side_effect = [ + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ] + self.assertTrue(hardware._enable_multipath()) + mocked_execute.assert_has_calls([ + mock.call('/usr/sbin/mpathconf', '--enable', + '--find_multipaths', 'yes', + '--with_module', 'y', + '--with_multipathd', 'y'), + mock.call('multipathd'), + mock.call('multipath', '-ll'), + ]) + + @mock.patch.object(os.path, 'isfile', autospec=True) + def test_enable_multipath_no_multipath(self, mock_isfile, mocked_execute): + mock_isfile.return_value = False + mocked_execute.side_effect = [ + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ] + self.assertTrue(hardware._enable_multipath()) + mocked_execute.assert_has_calls([ + mock.call('modprobe', 'dm_multipath'), + mock.call('modprobe', 'multipath'), + mock.call('multipathd'), + mock.call('multipath', '-ll'), + ]) + + @mock.patch.object(hardware, '_load_multipath_modules', autospec=True) + def test_enable_multipath_not_found_mpath_config(self, + mock_modules, + mocked_execute): + mocked_execute.side_effect = FileNotFoundError() + self.assertFalse(hardware._enable_multipath()) + self.assertEqual(1, mocked_execute.call_count) + self.assertEqual(1, mock_modules.call_count) + + @mock.patch.object(hardware, '_load_multipath_modules', autospec=True) + def test_enable_multipath_lacking_support(self, + mock_modules, + mocked_execute): + mocked_execute.side_effect = [ + ('', ''), # Help will of course work. + processutils.ProcessExecutionError('lacking kernel support') + ] + self.assertFalse(hardware._enable_multipath()) + self.assertEqual(2, mocked_execute.call_count) + self.assertEqual(1, mock_modules.call_count) + + def create_hdparm_info(supported=False, enabled=False, locked=False, frozen=False, enhanced_erase=False): |