diff --git a/ironic/drivers/modules/inspector/agent.py b/ironic/drivers/modules/inspector/agent.py index 7b7a227ada..5fed307ae4 100644 --- a/ironic/drivers/modules/inspector/agent.py +++ b/ironic/drivers/modules/inspector/agent.py @@ -53,16 +53,17 @@ class AgentInspect(common.Common): ep = f'{ep}/v1/continue_inspection' common.prepare_managed_inspection(task, ep) - cond_utils.node_power_action(task, states.POWER_ON) + self._power_on_or_reboot(task) def _start_unmanaged_inspection(self, task): """Start unmanaged inspection.""" try: - cond_utils.node_power_action(task, states.POWER_OFF) + if not task.node.disable_power_off: + cond_utils.node_power_action(task, states.POWER_OFF) # Only network boot is supported for unmanaged inspection. cond_utils.node_set_boot_device(task, boot_devices.PXE, persistent=False) - cond_utils.node_power_action(task, states.POWER_ON) + self._power_on_or_reboot(task) except Exception as exc: LOG.exception('Unable to start unmanaged inspection for node ' '%(uuid)s: %(err)s', diff --git a/ironic/drivers/modules/inspector/interface.py b/ironic/drivers/modules/inspector/interface.py index 2eb985d9df..7518acec9f 100644 --- a/ironic/drivers/modules/inspector/interface.py +++ b/ironic/drivers/modules/inspector/interface.py @@ -86,11 +86,20 @@ def tear_down_managed_boot(task, always_power_off=False): if ((ironic_manages_boot or always_power_off) and CONF.inspector.power_off and not utils.fast_track_enabled(task.node)): + if task.node.disable_power_off: + LOG.debug('Rebooting node %s instead of powering it off because ' + 'disable_power_off is set to True', task.node.uuid) + power_state = states.REBOOT + err_msg = _('unable to reboot the node: %s') + else: + power_state = states.POWER_OFF + err_msg = _('unable to power off the node: %s') + try: - cond_utils.node_power_action(task, states.POWER_OFF) + cond_utils.node_power_action(task, power_state) except Exception as exc: - errors.append(_('unable to power off the node: %s') % exc) - LOG.exception('Unable to power off node %s', task.node.uuid) + errors.append(err_msg % exc) + LOG.exception(err_msg, task.node.uuid) return errors @@ -146,7 +155,8 @@ def prepare_managed_inspection(task, endpoint): if utils.fast_track_enabled(task.node): params['ipa-api-url'] = deploy_utils.get_ironic_api_url() - cond_utils.node_power_action(task, states.POWER_OFF) + if not task.node.disable_power_off: + cond_utils.node_power_action(task, states.POWER_OFF) with cond_utils.power_state_for_network_configuration(task): task.driver.network.add_inspection_network(task) task.driver.boot.prepare_ramdisk(task, ramdisk_params=params) @@ -232,6 +242,12 @@ class Common(base.InspectInterface): self._start_unmanaged_inspection(task) return states.INSPECTWAIT + def _power_on_or_reboot(self, task): + # Handles disable_power_off properly + next_state = (states.REBOOT if task.node.disable_power_off + else states.POWER_ON) + cond_utils.node_power_action(task, next_state) + class Inspector(Common): """In-band inspection via ironic-inspector project.""" @@ -242,7 +258,7 @@ class Inspector(Common): endpoint = _get_callback_endpoint(cli) prepare_managed_inspection(task, endpoint) cli.start_introspection(task.node.uuid, manage_boot=False) - cond_utils.node_power_action(task, states.POWER_ON) + self._power_on_or_reboot(task) def _start_unmanaged_inspection(self, task): """Call to inspector to start inspection.""" diff --git a/ironic/tests/unit/drivers/modules/inspector/test_agent.py b/ironic/tests/unit/drivers/modules/inspector/test_agent.py index 6399a48f6c..13d959dc38 100644 --- a/ironic/tests/unit/drivers/modules/inspector/test_agent.py +++ b/ironic/tests/unit/drivers/modules/inspector/test_agent.py @@ -61,6 +61,24 @@ class InspectHardwareTestCase(db_base.DbTestCase): self.assertFalse(self.driver.network.remove_inspection_network.called) self.assertFalse(self.driver.boot.clean_up_ramdisk.called) + def test_unmanaged_disable_power_off(self, mock_create_ports_if_not_exist): + CONF.set_override('require_managed_boot', False, group='inspector') + self.driver.boot.validate_inspection.side_effect = ( + exception.UnsupportedDriverExtension('')) + self.node.disable_power_off = True + self.assertEqual(states.INSPECTWAIT, + self.iface.inspect_hardware(self.task)) + mock_create_ports_if_not_exist.assert_called_once_with(self.task) + self.assertFalse(self.driver.boot.prepare_ramdisk.called) + self.assertFalse(self.driver.network.add_inspection_network.called) + self.driver.management.set_boot_device.assert_called_once_with( + self.task, device=boot_devices.PXE, persistent=False) + self.driver.power.reboot.assert_called_once_with( + self.task, timeout=None) + self.driver.power.set_power_state.assert_not_called() + self.assertFalse(self.driver.network.remove_inspection_network.called) + self.assertFalse(self.driver.boot.clean_up_ramdisk.called) + def test_unmanaged_disallowed(self, mock_create_ports_if_not_exist): self.driver.boot.validate_inspection.side_effect = ( exception.UnsupportedDriverExtension('')) @@ -109,6 +127,27 @@ class InspectHardwareTestCase(db_base.DbTestCase): self.assertFalse(self.driver.network.remove_inspection_network.called) self.assertFalse(self.driver.boot.clean_up_ramdisk.called) + @mock.patch.object(deploy_utils, 'get_ironic_api_url', autospec=True) + def test_managed_disable_power_off(self, mock_get_url, + mock_create_ports_if_not_exist): + endpoint = 'http://192.169.0.42:6385/v1' + mock_get_url.return_value = endpoint + self.node.disable_power_off = True + self.assertEqual(states.INSPECTWAIT, + self.iface.inspect_hardware(self.task)) + self.driver.boot.prepare_ramdisk.assert_called_once_with( + self.task, ramdisk_params={ + 'ipa-inspection-callback-url': + f'{endpoint}/continue_inspection', + }) + self.driver.network.add_inspection_network.assert_called_once_with( + self.task) + self.driver.power.reboot.assert_called_once_with( + self.task, timeout=None) + self.driver.power.set_power_state.assert_not_called() + self.assertFalse(self.driver.network.remove_inspection_network.called) + self.assertFalse(self.driver.boot.clean_up_ramdisk.called) + @mock.patch.object(common, 'tear_down_managed_boot', autospec=True) @mock.patch.object(inspector, 'run_inspection_hooks', autospec=True) diff --git a/ironic/tests/unit/drivers/modules/inspector/test_interface.py b/ironic/tests/unit/drivers/modules/inspector/test_interface.py index d9a356a5b9..3b5a17e9ba 100644 --- a/ironic/tests/unit/drivers/modules/inspector/test_interface.py +++ b/ironic/tests/unit/drivers/modules/inspector/test_interface.py @@ -322,6 +322,31 @@ class InspectHardwareTestCase(BaseTestCase): self.driver.power.set_power_state.assert_called_with( self.task, 'power off', timeout=None) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + @mock.patch.object(inspect_utils, 'create_ports_if_not_exist', + autospec=True) + def test_managed_disable_power_off(self, mock_create_ports_if_not_exist, + mock_get_system, mock_client): + endpoint = 'http://192.169.0.42:5050/v1' + mock_client.return_value.get_endpoint.return_value = endpoint + mock_introspect = mock_client.return_value.start_introspection + self.node.disable_power_off = True + self.assertEqual(states.INSPECTWAIT, + self.iface.inspect_hardware(self.task)) + mock_introspect.assert_called_once_with(self.node.uuid, + manage_boot=False) + self.driver.boot.prepare_ramdisk.assert_called_once_with( + self.task, ramdisk_params={ + 'ipa-inspection-callback-url': endpoint + '/continue', + }) + self.driver.network.add_inspection_network.assert_called_once_with( + self.task) + self.driver.power.reboot.assert_called_once_with( + self.task, timeout=None) + self.driver.power.set_power_state.assert_not_called() + self.assertFalse(self.driver.network.remove_inspection_network.called) + self.assertFalse(self.driver.boot.clean_up_ramdisk.called) + class TearDownManagedInspectionTestCase(BaseTestCase): @@ -379,6 +404,20 @@ class TearDownManagedInspectionTestCase(BaseTestCase): self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task) self.assertFalse(self.driver.power.set_power_state.called) + def test_managed_disable_power_off(self): + utils.set_node_nested_field(self.node, 'driver_internal_info', + 'inspector_manage_boot', True) + self.node.disable_power_off = True + self.node.save() + + inspector.tear_down_managed_boot(self.task) + + self.driver.network.remove_inspection_network.assert_called_once_with( + self.task) + self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task) + self.driver.power.reboot.assert_called_once_with( + self.task, timeout=None) + def _test_clean_up_failed(self): utils.set_node_nested_field(self.node, 'driver_internal_info', 'inspector_manage_boot', True)