diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index 37e36f7cd..9ed0c5b85 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -212,6 +212,17 @@ def md_restart(raid_device): raise errors.CommandExecutionError(error_msg) +def _md_scan_and_assemble(): + """Scan all md devices and assemble RAID arrays from them. + + This call does not fail if no md devices are present. + """ + try: + utils.execute('mdadm', '--assemble', '--scan', '--verbose') + except processutils.ProcessExecutionError: + LOG.info('No new RAID devices assembled during start-up') + + def list_all_block_devices(block_type='disk', ignore_raid=False): """List all physical block devices @@ -700,6 +711,7 @@ class GenericHardwareManager(HardwareManager): def evaluate_hardware_support(self): # Do some initialization before we declare ourself ready _check_for_iscsi() + _md_scan_and_assemble() self.wait_for_disks() return HardwareSupport.GENERIC diff --git a/ironic_python_agent/tests/unit/test_agent.py b/ironic_python_agent/tests/unit/test_agent.py index 8fbffe0ec..869b7b887 100644 --- a/ironic_python_agent/tests/unit/test_agent.py +++ b/ironic_python_agent/tests/unit/test_agent.py @@ -128,6 +128,8 @@ class TestHeartbeater(ironic_agent_base.IronicAgentTest): self.assertEqual(2.7, self.heartbeater.error_delay) +@mock.patch.object(hardware, '_md_scan_and_assemble', lambda: None) +@mock.patch.object(hardware, '_check_for_iscsi', lambda: None) @mock.patch.object(hardware.GenericHardwareManager, 'wait_for_disks', lambda self: None) class TestBaseAgent(ironic_agent_base.IronicAgentTest): @@ -173,7 +175,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.assertEqual(pkg_resources.get_distribution('ironic-python-agent') .version, status.version) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @@ -219,7 +220,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.heartbeater.start.assert_called_once_with() @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @@ -276,7 +276,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.heartbeater.start.assert_called_once_with() @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @@ -338,7 +337,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): # changed via mdns self.assertEqual(42, CONF.disk_wait_attempts) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @@ -382,7 +380,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.heartbeater.start.assert_called_once_with() self.assertTrue(wsgi_server.handle_request.called) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch('ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @mock.patch.object(agent.IronicPythonAgent, @@ -436,7 +433,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.heartbeater.start.assert_called_once_with() @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @@ -494,7 +490,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.assertFalse(mock_dispatch.called) @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @@ -710,6 +705,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): mock_log.warning.assert_called_once() +@mock.patch.object(hardware, '_md_scan_and_assemble', lambda: None) +@mock.patch.object(hardware, '_check_for_iscsi', lambda: None) @mock.patch.object(hardware.GenericHardwareManager, 'wait_for_disks', lambda self: None) class TestAgentStandalone(ironic_agent_base.IronicAgentTest): @@ -730,7 +727,6 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest): 'agent_ipmitool', True) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @@ -768,6 +764,7 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest): self.assertFalse(self.agent.api_client.lookup_node.called) +@mock.patch.object(hardware, '_md_scan_and_assemble', lambda: None) @mock.patch.object(hardware, '_check_for_iscsi', lambda: None) @mock.patch.object(hardware.GenericHardwareManager, 'wait_for_disks', lambda self: None) diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index a84969349..f540f1f59 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -3069,12 +3069,44 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual('', vendor_info.serial_number) self.assertEqual('', vendor_info.manufacturer) - @mock.patch.object(hardware.GenericHardwareManager, - 'get_os_install_device', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', autospec=True) - @mock.patch.object(time, 'sleep', autospec=True) + @mock.patch.object(utils, 'get_agent_params', + lambda: {'BOOTIF': 'boot:if'}) + @mock.patch.object(os.path, 'isdir', autospec=True) + def test_get_boot_info_pxe_interface(self, mocked_isdir): + mocked_isdir.return_value = False + result = self.hardware.get_boot_info() + self.assertEqual(hardware.BootInfo(current_boot_mode='bios', + pxe_interface='boot:if'), + result) + + @mock.patch.object(os.path, 'isdir', autospec=True) + def test_get_boot_info_bios(self, mocked_isdir): + mocked_isdir.return_value = False + result = self.hardware.get_boot_info() + self.assertEqual(hardware.BootInfo(current_boot_mode='bios'), result) + mocked_isdir.assert_called_once_with('/sys/firmware/efi') + + @mock.patch.object(os.path, 'isdir', autospec=True) + def test_get_boot_info_uefi(self, mocked_isdir): + mocked_isdir.return_value = True + result = self.hardware.get_boot_info() + self.assertEqual(hardware.BootInfo(current_boot_mode='uefi'), result) + mocked_isdir.assert_called_once_with('/sys/firmware/efi') + + +@mock.patch.object(hardware.GenericHardwareManager, + 'get_os_install_device', autospec=True) +@mock.patch.object(hardware, '_md_scan_and_assemble', autospec=True) +@mock.patch.object(hardware, '_check_for_iscsi', autospec=True) +@mock.patch.object(time, 'sleep', autospec=True) +class TestEvaluateHardwareSupport(base.IronicAgentTest): + def setUp(self): + super(TestEvaluateHardwareSupport, self).setUp() + self.hardware = hardware.GenericHardwareManager() + def test_evaluate_hw_waits_for_disks( - self, mocked_sleep, mocked_check_for_iscsi, mocked_get_inst_dev): + self, mocked_sleep, mocked_check_for_iscsi, + mocked_md_assemble, mocked_get_inst_dev): mocked_get_inst_dev.side_effect = [ errors.DeviceNotFound('boom'), None @@ -3083,19 +3115,16 @@ class TestGenericHardwareManager(base.IronicAgentTest): result = self.hardware.evaluate_hardware_support() self.assertTrue(mocked_check_for_iscsi.called) + self.assertTrue(mocked_md_assemble.called) self.assertEqual(hardware.HardwareSupport.GENERIC, result) mocked_get_inst_dev.assert_called_with(mock.ANY) self.assertEqual(2, mocked_get_inst_dev.call_count) mocked_sleep.assert_called_once_with(CONF.disk_wait_delay) @mock.patch.object(hardware, 'LOG', autospec=True) - @mock.patch.object(hardware.GenericHardwareManager, - 'get_os_install_device', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', autospec=True) - @mock.patch.object(time, 'sleep', autospec=True) def test_evaluate_hw_no_wait_for_disks( - self, mocked_sleep, mocked_check_for_iscsi, mocked_get_inst_dev, - mocked_log): + self, mocked_log, mocked_sleep, mocked_check_for_iscsi, + mocked_md_assemble, mocked_get_inst_dev): CONF.set_override('disk_wait_attempts', '0') result = self.hardware.evaluate_hardware_support() @@ -3107,12 +3136,9 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertFalse(mocked_log.called) @mock.patch.object(hardware, 'LOG', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) - @mock.patch.object(hardware.GenericHardwareManager, - 'get_os_install_device', autospec=True) - @mock.patch.object(time, 'sleep', autospec=True) def test_evaluate_hw_waits_for_disks_nonconfigured( - self, mocked_sleep, mocked_get_inst_dev, mocked_log): + self, mocked_log, mocked_sleep, mocked_check_for_iscsi, + mocked_md_assemble, mocked_get_inst_dev): mocked_get_inst_dev.side_effect = [ errors.DeviceNotFound('boom'), errors.DeviceNotFound('boom'), @@ -3139,13 +3165,11 @@ class TestGenericHardwareManager(base.IronicAgentTest): CONF.disk_wait_delay * 9) @mock.patch.object(hardware, 'LOG', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) - @mock.patch.object(hardware.GenericHardwareManager, - 'get_os_install_device', autospec=True) - @mock.patch.object(time, 'sleep', autospec=True) - def test_evaluate_hw_waits_for_disks_configured(self, mocked_sleep, - mocked_get_inst_dev, - mocked_log): + def test_evaluate_hw_waits_for_disks_configured(self, mocked_log, + mocked_sleep, + mocked_check_for_iscsi, + mocked_md_assemble, + mocked_get_inst_dev): CONF.set_override('disk_wait_attempts', '1') mocked_get_inst_dev.side_effect = [ @@ -3162,21 +3186,17 @@ class TestGenericHardwareManager(base.IronicAgentTest): mocked_log.warning.assert_called_once_with( 'The root device was not detected') - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) - @mock.patch.object(hardware.GenericHardwareManager, - 'get_os_install_device', autospec=True) - @mock.patch.object(time, 'sleep', autospec=True) 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.side_effect = errors.DeviceNotFound('boom') self.hardware.evaluate_hardware_support() mocked_sleep.assert_called_with(3) - @mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) - @mock.patch.object(hardware.GenericHardwareManager, - 'get_os_install_device', autospec=True) - @mock.patch.object(time, 'sleep', autospec=True) def test_evaluate_hw_disks_timeout_configured(self, mocked_sleep, + mocked_check_for_iscsi, + mocked_md_assemble, mocked_root_dev): CONF.set_override('disk_wait_delay', '5') mocked_root_dev.side_effect = errors.DeviceNotFound('boom') @@ -3184,12 +3204,9 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.hardware.evaluate_hardware_support() mocked_sleep.assert_called_with(5) - @mock.patch.object(hardware.GenericHardwareManager, - 'get_os_install_device', autospec=True) - @mock.patch.object(hardware, '_check_for_iscsi', autospec=True) - @mock.patch.object(time, 'sleep', autospec=True) def test_evaluate_hw_disks_timeout( - self, mocked_sleep, mocked_check_for_iscsi, mocked_get_inst_dev): + self, mocked_sleep, mocked_check_for_iscsi, + mocked_md_assemble, mocked_get_inst_dev): mocked_get_inst_dev.side_effect = errors.DeviceNotFound('boom') result = self.hardware.evaluate_hardware_support() self.assertEqual(hardware.HardwareSupport.GENERIC, result) @@ -3198,30 +3215,6 @@ class TestGenericHardwareManager(base.IronicAgentTest): mocked_get_inst_dev.call_count) mocked_sleep.assert_called_with(CONF.disk_wait_delay) - @mock.patch.object(utils, 'get_agent_params', - lambda: {'BOOTIF': 'boot:if'}) - @mock.patch.object(os.path, 'isdir', autospec=True) - def test_get_boot_info_pxe_interface(self, mocked_isdir): - mocked_isdir.return_value = False - result = self.hardware.get_boot_info() - self.assertEqual(hardware.BootInfo(current_boot_mode='bios', - pxe_interface='boot:if'), - result) - - @mock.patch.object(os.path, 'isdir', autospec=True) - def test_get_boot_info_bios(self, mocked_isdir): - mocked_isdir.return_value = False - result = self.hardware.get_boot_info() - self.assertEqual(hardware.BootInfo(current_boot_mode='bios'), result) - mocked_isdir.assert_called_once_with('/sys/firmware/efi') - - @mock.patch.object(os.path, 'isdir', autospec=True) - def test_get_boot_info_uefi(self, mocked_isdir): - mocked_isdir.return_value = True - result = self.hardware.get_boot_info() - self.assertEqual(hardware.BootInfo(current_boot_mode='uefi'), result) - mocked_isdir.assert_called_once_with('/sys/firmware/efi') - @mock.patch.object(os, 'listdir', lambda *_: []) @mock.patch.object(utils, 'execute', autospec=True) diff --git a/releasenotes/notes/sw-raid-assemble-9c20fe967f73d1dd.yaml b/releasenotes/notes/sw-raid-assemble-9c20fe967f73d1dd.yaml new file mode 100644 index 000000000..3cb6b9155 --- /dev/null +++ b/releasenotes/notes/sw-raid-assemble-9c20fe967f73d1dd.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Tries to assemble software RAID automatically on start up to avoid problems + with ramdisks that don't do it automatically (like tinyipa).