diff options
author | Zuul <zuul@review.opendev.org> | 2020-07-23 04:48:48 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2020-07-23 04:48:48 +0000 |
commit | 0e26dfe83902fdc84890f81d9ca06b72fae35ce5 (patch) | |
tree | 63be32738f5120c56cd5e037572d9eaaf45e043b /ironic | |
parent | 8f754180e849d3f22767eecd413c2297abb7303c (diff) | |
parent | e804f6c56bbfc9c71dda9096710ad6288b5d618a (diff) | |
download | ironic-0e26dfe83902fdc84890f81d9ca06b72fae35ce5.tar.gz |
Merge "Account for power interfaces that cannot power on"
Diffstat (limited to 'ironic')
-rw-r--r-- | ironic/conductor/manager.py | 3 | ||||
-rw-r--r-- | ironic/drivers/base.py | 12 | ||||
-rw-r--r-- | ironic/drivers/modules/agent_base.py | 19 | ||||
-rw-r--r-- | ironic/tests/unit/conductor/test_manager.py | 15 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/test_agent_base.py | 44 |
5 files changed, 90 insertions, 3 deletions
diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py index 88cc083b0..222ad5507 100644 --- a/ironic/conductor/manager.py +++ b/ironic/conductor/manager.py @@ -3669,7 +3669,8 @@ def do_sync_power_state(task, count): handle_sync_power_state_max_retries_exceeded(task, power_state) return count - if CONF.conductor.force_power_state_during_sync: + if (CONF.conductor.force_power_state_during_sync + and task.driver.power.supports_power_sync(task)): LOG.warning("During sync_power_state, node %(node)s state " "'%(actual)s' does not match expected state. " "Changing hardware state to '%(state)s'.", diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py index 3fbecba49..c33ad05b4 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py @@ -616,6 +616,18 @@ class PowerInterface(BaseInterface): """ return [states.POWER_ON, states.POWER_OFF, states.REBOOT] + def supports_power_sync(self, task): + """Check if power sync is supported for the given node. + + If ``False``, the conductor will simply store whatever + ``get_power_state`` returns in the database instead of trying + to force the expected power state. + + :param task: A TaskManager instance containing the node to act on. + :returns: boolean, whether power sync is supported. + """ + return True + class ConsoleInterface(BaseInterface): """Interface for console-related actions.""" diff --git a/ironic/drivers/modules/agent_base.py b/ironic/drivers/modules/agent_base.py index 267214073..49debc5a9 100644 --- a/ironic/drivers/modules/agent_base.py +++ b/ironic/drivers/modules/agent_base.py @@ -800,8 +800,15 @@ class AgentOobStepsMixin(object): :param task: a TaskManager object containing the node """ + can_power_on = (states.POWER_ON in + task.driver.power.get_supported_power_states(task)) try: - manager_utils.node_power_action(task, states.POWER_ON) + if can_power_on: + manager_utils.node_power_action(task, states.POWER_ON) + else: + LOG.debug('Not trying to power on node %s that does not ' + 'support powering on, assuming already running', + task.node.uuid) except Exception as e: msg = (_('Error booting node %(node)s after deploy. ' '%(cls)s: %(error)s') % @@ -1168,9 +1175,17 @@ class AgentDeployMixin(HeartbeatMixin, AgentOobStepsMixin): # in-band methods oob_power_off = strutils.bool_from_string( node.driver_info.get('deploy_forces_oob_reboot', False)) + can_power_on = (states.POWER_ON in + task.driver.power.get_supported_power_states(task)) try: - if not oob_power_off: + if not can_power_on: + LOG.info('Power interface of node %(node)s does not support ' + 'power on, using reboot to switch to the instance', + node.uuid) + self._client.sync(node) + manager_utils.node_power_action(task, states.REBOOT) + elif not oob_power_off: try: self._client.power_off(node) except Exception as e: diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 3556d4df2..9ae4a0428 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -4878,6 +4878,21 @@ class ManagerDoSyncPowerStateTestCase(db_base.DbTestCase): self.service.power_state_sync_count[self.node.uuid]) @mock.patch.object(nova, 'power_update', autospec=True) + def test_no_power_sync_support(self, mock_power_update, node_power_action): + self.config(force_power_state_during_sync=True, group='conductor') + self.power.supports_power_sync.return_value = False + + self._do_sync_power_state(states.POWER_ON, states.POWER_OFF) + + self.assertFalse(self.power.validate.called) + self.power.get_power_state.assert_called_once_with(self.task) + self.assertFalse(node_power_action.called) + self.assertEqual(states.POWER_OFF, self.node.power_state) + self.task.upgrade_lock.assert_called_once_with() + mock_power_update.assert_called_once_with( + self.task.context, self.node.instance_uuid, states.POWER_OFF) + + @mock.patch.object(nova, 'power_update', autospec=True) def test_max_retries_exceeded(self, mock_power_update, node_power_action): self.config(force_power_state_during_sync=True, group='conductor') self.config(power_state_sync_max_retries=1, group='conductor') diff --git a/ironic/tests/unit/drivers/modules/test_agent_base.py b/ironic/tests/unit/drivers/modules/test_agent_base.py index 25fa4dc23..dac8e2fca 100644 --- a/ironic/tests/unit/drivers/modules/test_agent_base.py +++ b/ironic/tests/unit/drivers/modules/test_agent_base.py @@ -1100,6 +1100,29 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): {'node': task.node.uuid, 'error': log_error}) self.assertFalse(mock_collect.called) + @mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True) + @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True) + @mock.patch.object(time, 'sleep', lambda seconds: None) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + @mock.patch.object(fake.FakePower, 'get_supported_power_states', + lambda self, task: [states.REBOOT]) + @mock.patch.object(agent_client.AgentClient, 'sync', autospec=True) + def test_tear_down_agent_no_power_on_support( + self, sync_mock, node_power_action_mock, collect_mock, + power_on_node_if_needed_mock): + cfg.CONF.set_override('deploy_logs_collect', 'always', 'agent') + self.node.provision_state = states.DEPLOYING + self.node.target_provision_state = states.ACTIVE + self.node.save() + with task_manager.acquire(self.context, self.node.uuid) as task: + self.deploy.tear_down_agent(task) + node_power_action_mock.assert_called_once_with(task, states.REBOOT) + self.assertEqual(states.DEPLOYING, task.node.provision_state) + self.assertEqual(states.ACTIVE, task.node.target_provision_state) + collect_mock.assert_called_once_with(task.node) + self.assertFalse(power_on_node_if_needed_mock.called) + sync_mock.assert_called_once_with(self.deploy._client, task.node) + @mock.patch.object(manager_utils, 'restore_power_state_if_needed', autospec=True) @mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True) @@ -1145,6 +1168,27 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): configure_tenant_net_mock.assert_called_once_with(mock.ANY, task) self.assertFalse(mock_collect.called) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + def test_boot_instance(self, node_power_action_mock): + self.node.provision_state = states.DEPLOYING + self.node.target_provision_state = states.ACTIVE + self.node.save() + with task_manager.acquire(self.context, self.node.uuid) as task: + self.deploy.boot_instance(task) + node_power_action_mock.assert_called_once_with(task, + states.POWER_ON) + + @mock.patch.object(fake.FakePower, 'get_supported_power_states', + lambda self, task: [states.REBOOT]) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + def test_boot_instance_no_power_on(self, node_power_action_mock): + self.node.provision_state = states.DEPLOYING + self.node.target_provision_state = states.ACTIVE + self.node.save() + with task_manager.acquire(self.context, self.node.uuid) as task: + self.deploy.boot_instance(task) + self.assertFalse(node_power_action_mock.called) + @mock.patch.object(agent_client.AgentClient, 'install_bootloader', autospec=True) @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True) |