Software RAID: try to assemble RAID on start-up

TinyIPA does not do it, which prevents deployment on RAID devices.

Story: #2004581
Task: #36196
Change-Id: I6c1d90b4669102ab59588ab15f7166626c4d72be
This commit is contained in:
Dmitry Tantsur 2019-08-09 10:17:54 +02:00
parent 258d963e40
commit c7c307c497
4 changed files with 75 additions and 68 deletions

View File

@ -212,6 +212,17 @@ def md_restart(raid_device):
raise errors.CommandExecutionError(error_msg) 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', def list_all_block_devices(block_type='disk',
ignore_raid=False): ignore_raid=False):
"""List all physical block devices """List all physical block devices
@ -700,6 +711,7 @@ class GenericHardwareManager(HardwareManager):
def evaluate_hardware_support(self): def evaluate_hardware_support(self):
# Do some initialization before we declare ourself ready # Do some initialization before we declare ourself ready
_check_for_iscsi() _check_for_iscsi()
_md_scan_and_assemble()
self.wait_for_disks() self.wait_for_disks()
return HardwareSupport.GENERIC return HardwareSupport.GENERIC

View File

@ -128,6 +128,8 @@ class TestHeartbeater(ironic_agent_base.IronicAgentTest):
self.assertEqual(2.7, self.heartbeater.error_delay) 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', @mock.patch.object(hardware.GenericHardwareManager, 'wait_for_disks',
lambda self: None) lambda self: None)
class TestBaseAgent(ironic_agent_base.IronicAgentTest): 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') self.assertEqual(pkg_resources.get_distribution('ironic-python-agent')
.version, status.version) .version, status.version)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
@mock.patch( @mock.patch(
'ironic_python_agent.hardware_managers.cna._detect_cna_card', 'ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@ -219,7 +220,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
self.agent.heartbeater.start.assert_called_once_with() self.agent.heartbeater.start.assert_called_once_with()
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
@mock.patch( @mock.patch(
'ironic_python_agent.hardware_managers.cna._detect_cna_card', 'ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@ -276,7 +276,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
self.agent.heartbeater.start.assert_called_once_with() self.agent.heartbeater.start.assert_called_once_with()
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
@mock.patch( @mock.patch(
'ironic_python_agent.hardware_managers.cna._detect_cna_card', 'ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@ -338,7 +337,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
# changed via mdns # changed via mdns
self.assertEqual(42, CONF.disk_wait_attempts) self.assertEqual(42, CONF.disk_wait_attempts)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
@mock.patch( @mock.patch(
'ironic_python_agent.hardware_managers.cna._detect_cna_card', 'ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@ -382,7 +380,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
self.agent.heartbeater.start.assert_called_once_with() self.agent.heartbeater.start.assert_called_once_with()
self.assertTrue(wsgi_server.handle_request.called) 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.patch('ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@mock.patch.object(agent.IronicPythonAgent, @mock.patch.object(agent.IronicPythonAgent,
@ -436,7 +433,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
self.agent.heartbeater.start.assert_called_once_with() self.agent.heartbeater.start.assert_called_once_with()
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
@mock.patch( @mock.patch(
'ironic_python_agent.hardware_managers.cna._detect_cna_card', 'ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@ -494,7 +490,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
self.assertFalse(mock_dispatch.called) self.assertFalse(mock_dispatch.called)
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True) @mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
@mock.patch( @mock.patch(
'ironic_python_agent.hardware_managers.cna._detect_cna_card', 'ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@ -710,6 +705,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
mock_log.warning.assert_called_once() 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', @mock.patch.object(hardware.GenericHardwareManager, 'wait_for_disks',
lambda self: None) lambda self: None)
class TestAgentStandalone(ironic_agent_base.IronicAgentTest): class TestAgentStandalone(ironic_agent_base.IronicAgentTest):
@ -730,7 +727,6 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest):
'agent_ipmitool', 'agent_ipmitool',
True) True)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
@mock.patch( @mock.patch(
'ironic_python_agent.hardware_managers.cna._detect_cna_card', 'ironic_python_agent.hardware_managers.cna._detect_cna_card',
mock.Mock()) mock.Mock())
@ -768,6 +764,7 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest):
self.assertFalse(self.agent.api_client.lookup_node.called) 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, '_check_for_iscsi', lambda: None)
@mock.patch.object(hardware.GenericHardwareManager, 'wait_for_disks', @mock.patch.object(hardware.GenericHardwareManager, 'wait_for_disks',
lambda self: None) lambda self: None)

View File

@ -3069,12 +3069,44 @@ class TestGenericHardwareManager(base.IronicAgentTest):
self.assertEqual('', vendor_info.serial_number) self.assertEqual('', vendor_info.serial_number)
self.assertEqual('', vendor_info.manufacturer) self.assertEqual('', vendor_info.manufacturer)
@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, @mock.patch.object(hardware.GenericHardwareManager,
'get_os_install_device', autospec=True) '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(hardware, '_check_for_iscsi', autospec=True)
@mock.patch.object(time, 'sleep', 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( 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 = [ mocked_get_inst_dev.side_effect = [
errors.DeviceNotFound('boom'), errors.DeviceNotFound('boom'),
None None
@ -3083,19 +3115,16 @@ class TestGenericHardwareManager(base.IronicAgentTest):
result = self.hardware.evaluate_hardware_support() result = self.hardware.evaluate_hardware_support()
self.assertTrue(mocked_check_for_iscsi.called) self.assertTrue(mocked_check_for_iscsi.called)
self.assertTrue(mocked_md_assemble.called)
self.assertEqual(hardware.HardwareSupport.GENERIC, result) self.assertEqual(hardware.HardwareSupport.GENERIC, result)
mocked_get_inst_dev.assert_called_with(mock.ANY) mocked_get_inst_dev.assert_called_with(mock.ANY)
self.assertEqual(2, mocked_get_inst_dev.call_count) self.assertEqual(2, mocked_get_inst_dev.call_count)
mocked_sleep.assert_called_once_with(CONF.disk_wait_delay) mocked_sleep.assert_called_once_with(CONF.disk_wait_delay)
@mock.patch.object(hardware, 'LOG', autospec=True) @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( def test_evaluate_hw_no_wait_for_disks(
self, mocked_sleep, mocked_check_for_iscsi, mocked_get_inst_dev, self, mocked_log, mocked_sleep, mocked_check_for_iscsi,
mocked_log): mocked_md_assemble, mocked_get_inst_dev):
CONF.set_override('disk_wait_attempts', '0') CONF.set_override('disk_wait_attempts', '0')
result = self.hardware.evaluate_hardware_support() result = self.hardware.evaluate_hardware_support()
@ -3107,12 +3136,9 @@ class TestGenericHardwareManager(base.IronicAgentTest):
self.assertFalse(mocked_log.called) self.assertFalse(mocked_log.called)
@mock.patch.object(hardware, 'LOG', autospec=True) @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( 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 = [ mocked_get_inst_dev.side_effect = [
errors.DeviceNotFound('boom'), errors.DeviceNotFound('boom'),
errors.DeviceNotFound('boom'), errors.DeviceNotFound('boom'),
@ -3139,13 +3165,11 @@ class TestGenericHardwareManager(base.IronicAgentTest):
CONF.disk_wait_delay * 9) CONF.disk_wait_delay * 9)
@mock.patch.object(hardware, 'LOG', autospec=True) @mock.patch.object(hardware, 'LOG', autospec=True)
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock()) def test_evaluate_hw_waits_for_disks_configured(self, mocked_log,
@mock.patch.object(hardware.GenericHardwareManager, mocked_sleep,
'get_os_install_device', autospec=True) mocked_check_for_iscsi,
@mock.patch.object(time, 'sleep', autospec=True) mocked_md_assemble,
def test_evaluate_hw_waits_for_disks_configured(self, mocked_sleep, mocked_get_inst_dev):
mocked_get_inst_dev,
mocked_log):
CONF.set_override('disk_wait_attempts', '1') CONF.set_override('disk_wait_attempts', '1')
mocked_get_inst_dev.side_effect = [ mocked_get_inst_dev.side_effect = [
@ -3162,21 +3186,17 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mocked_log.warning.assert_called_once_with( mocked_log.warning.assert_called_once_with(
'The root device was not detected') '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, 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_get_inst_dev.side_effect = errors.DeviceNotFound('boom') mocked_get_inst_dev.side_effect = errors.DeviceNotFound('boom')
self.hardware.evaluate_hardware_support() self.hardware.evaluate_hardware_support()
mocked_sleep.assert_called_with(3) 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, def test_evaluate_hw_disks_timeout_configured(self, mocked_sleep,
mocked_check_for_iscsi,
mocked_md_assemble,
mocked_root_dev): mocked_root_dev):
CONF.set_override('disk_wait_delay', '5') CONF.set_override('disk_wait_delay', '5')
mocked_root_dev.side_effect = errors.DeviceNotFound('boom') mocked_root_dev.side_effect = errors.DeviceNotFound('boom')
@ -3184,12 +3204,9 @@ class TestGenericHardwareManager(base.IronicAgentTest):
self.hardware.evaluate_hardware_support() self.hardware.evaluate_hardware_support()
mocked_sleep.assert_called_with(5) 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( 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') mocked_get_inst_dev.side_effect = errors.DeviceNotFound('boom')
result = self.hardware.evaluate_hardware_support() result = self.hardware.evaluate_hardware_support()
self.assertEqual(hardware.HardwareSupport.GENERIC, result) self.assertEqual(hardware.HardwareSupport.GENERIC, result)
@ -3198,30 +3215,6 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mocked_get_inst_dev.call_count) mocked_get_inst_dev.call_count)
mocked_sleep.assert_called_with(CONF.disk_wait_delay) 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(os, 'listdir', lambda *_: [])
@mock.patch.object(utils, 'execute', autospec=True) @mock.patch.object(utils, 'execute', autospec=True)

View File

@ -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).