summaryrefslogtreecommitdiff
path: root/ironic
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2020-07-23 04:48:48 +0000
committerGerrit Code Review <review@openstack.org>2020-07-23 04:48:48 +0000
commit0e26dfe83902fdc84890f81d9ca06b72fae35ce5 (patch)
tree63be32738f5120c56cd5e037572d9eaaf45e043b /ironic
parent8f754180e849d3f22767eecd413c2297abb7303c (diff)
parente804f6c56bbfc9c71dda9096710ad6288b5d618a (diff)
downloadironic-0e26dfe83902fdc84890f81d9ca06b72fae35ce5.tar.gz
Merge "Account for power interfaces that cannot power on"
Diffstat (limited to 'ironic')
-rw-r--r--ironic/conductor/manager.py3
-rw-r--r--ironic/drivers/base.py12
-rw-r--r--ironic/drivers/modules/agent_base.py19
-rw-r--r--ironic/tests/unit/conductor/test_manager.py15
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_base.py44
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)