summaryrefslogtreecommitdiff
path: root/ironic/tests/unit/drivers/modules/test_agent_base.py
diff options
context:
space:
mode:
authorDmitry Tantsur <dtantsur@protonmail.com>2020-02-19 17:04:12 +0100
committerDmitry Tantsur <dtantsur@protonmail.com>2020-02-19 18:08:41 +0100
commit5ffef36fdc634e617f86028ed47d8756c6a8efc1 (patch)
treee5c8ca45db8231fcd8c40f61cdc3d3ce01a2b5ad /ironic/tests/unit/drivers/modules/test_agent_base.py
parent5ebd7392ab5c2838561988281575cf945a5f1619 (diff)
downloadironic-5ffef36fdc634e617f86028ed47d8756c6a8efc1.tar.gz
Refactoring: rename agent_base_vendor to agent_base
There is no longer a common vendor passthru, so the current name does not make much sense. Change-Id: I1610e54774826de369be0fef88e98bdf3f64ae1d
Diffstat (limited to 'ironic/tests/unit/drivers/modules/test_agent_base.py')
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_base.py2051
1 files changed, 2051 insertions, 0 deletions
diff --git a/ironic/tests/unit/drivers/modules/test_agent_base.py b/ironic/tests/unit/drivers/modules/test_agent_base.py
new file mode 100644
index 000000000..5f51938b4
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/test_agent_base.py
@@ -0,0 +1,2051 @@
+# Copyright 2015 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+import types
+
+import mock
+from oslo_config import cfg
+
+from ironic.common import boot_devices
+from ironic.common import exception
+from ironic.common import image_service
+from ironic.common import states
+from ironic.conductor import steps as conductor_steps
+from ironic.conductor import task_manager
+from ironic.conductor import utils as manager_utils
+from ironic.drivers import base as drivers_base
+from ironic.drivers.modules import agent
+from ironic.drivers.modules import agent_base
+from ironic.drivers.modules import agent_client
+from ironic.drivers.modules import boot_mode_utils
+from ironic.drivers.modules import deploy_utils
+from ironic.drivers.modules import fake
+from ironic.drivers.modules import pxe
+from ironic.drivers import utils as driver_utils
+from ironic import objects
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
+from ironic.tests.unit.objects import utils as object_utils
+
+CONF = cfg.CONF
+
+INSTANCE_INFO = db_utils.get_test_agent_instance_info()
+DRIVER_INFO = db_utils.get_test_agent_driver_info()
+DRIVER_INTERNAL_INFO = db_utils.get_test_agent_driver_internal_info()
+
+
+class AgentDeployMixinBaseTest(db_base.DbTestCase):
+
+ def setUp(self):
+ super(AgentDeployMixinBaseTest, self).setUp()
+ for iface in drivers_base.ALL_INTERFACES:
+ impl = 'fake'
+ if iface == 'deploy':
+ impl = 'direct'
+ if iface == 'boot':
+ impl = 'pxe'
+ if iface == 'rescue':
+ impl = 'agent'
+ if iface == 'network':
+ continue
+ config_kwarg = {'enabled_%s_interfaces' % iface: [impl],
+ 'default_%s_interface' % iface: impl}
+ self.config(**config_kwarg)
+ self.config(enabled_hardware_types=['fake-hardware'])
+ self.deploy = agent_base.AgentDeployMixin()
+ n = {
+ 'driver': 'fake-hardware',
+ 'instance_info': INSTANCE_INFO,
+ 'driver_info': DRIVER_INFO,
+ 'driver_internal_info': DRIVER_INTERNAL_INFO,
+ 'network_interface': 'noop'
+ }
+ self.node = object_utils.create_test_node(self.context, **n)
+
+
+class HeartbeatMixinTest(AgentDeployMixinBaseTest):
+
+ def setUp(self):
+ super(HeartbeatMixinTest, self).setUp()
+ self.deploy = agent_base.HeartbeatMixin()
+
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'in_core_deploy_step', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_has_started', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ def test_heartbeat_continue_deploy(self, rti_mock, cd_mock,
+ deploy_started_mock,
+ in_deploy_mock):
+ in_deploy_mock.return_value = True
+ deploy_started_mock.return_value = False
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.deploy.heartbeat(task, 'url', '3.2.0')
+ self.assertFalse(task.shared)
+ self.assertEqual(
+ 'url', task.node.driver_internal_info['agent_url'])
+ self.assertEqual(
+ '3.2.0',
+ task.node.driver_internal_info['agent_version'])
+ cd_mock.assert_called_once_with(self.deploy, task)
+ self.assertFalse(rti_mock.called)
+
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'in_core_deploy_step', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_has_started', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_is_done', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ def test_heartbeat_reboot_to_instance(self, rti_mock, cd_mock,
+ deploy_is_done_mock,
+ deploy_started_mock,
+ in_deploy_mock):
+ in_deploy_mock.return_value = True
+ deploy_started_mock.return_value = True
+ deploy_is_done_mock.return_value = True
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.deploy.heartbeat(task, 'url', '3.2.0')
+ self.assertFalse(task.shared)
+ self.assertEqual(
+ 'url', task.node.driver_internal_info['agent_url'])
+ self.assertEqual(
+ '3.2.0',
+ task.node.driver_internal_info['agent_version'])
+ self.assertFalse(cd_mock.called)
+ rti_mock.assert_called_once_with(self.deploy, task)
+
+ @mock.patch.object(manager_utils,
+ 'notify_conductor_resume_deploy', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'in_core_deploy_step', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_has_started', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_is_done', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ def test_heartbeat_not_in_core_deploy_step(self, rti_mock, cd_mock,
+ deploy_is_done_mock,
+ deploy_started_mock,
+ in_deploy_mock,
+ in_resume_deploy_mock):
+ # Check that heartbeats do not trigger deployment actions when not in
+ # the deploy.deploy step.
+ in_deploy_mock.return_value = False
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.deploy.heartbeat(task, 'url', '3.2.0')
+ self.assertFalse(task.shared)
+ self.assertEqual(
+ 'url', task.node.driver_internal_info['agent_url'])
+ self.assertEqual(
+ '3.2.0',
+ task.node.driver_internal_info['agent_version'])
+ self.assertFalse(deploy_started_mock.called)
+ self.assertFalse(deploy_is_done_mock.called)
+ self.assertFalse(cd_mock.called)
+ self.assertFalse(rti_mock.called)
+ self.assertTrue(in_resume_deploy_mock.called)
+
+ @mock.patch.object(manager_utils,
+ 'notify_conductor_resume_deploy', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'in_core_deploy_step', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_has_started', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_is_done', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ def test_heartbeat_not_in_core_deploy_step_polling(self, rti_mock, cd_mock,
+ deploy_is_done_mock,
+ deploy_started_mock,
+ in_deploy_mock,
+ in_resume_deploy_mock):
+ # Check that heartbeats do not trigger deployment actions when not in
+ # the deploy.deploy step.
+ in_deploy_mock.return_value = False
+ self.node.provision_state = states.DEPLOYWAIT
+ info = self.node.driver_internal_info
+ info['deployment_polling'] = True
+ self.node.driver_internal_info = info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.deploy.heartbeat(task, 'url', '3.2.0')
+ self.assertFalse(task.shared)
+ self.assertEqual(
+ 'url', task.node.driver_internal_info['agent_url'])
+ self.assertEqual(
+ '3.2.0',
+ task.node.driver_internal_info['agent_version'])
+ self.assertFalse(deploy_started_mock.called)
+ self.assertFalse(deploy_is_done_mock.called)
+ self.assertFalse(cd_mock.called)
+ self.assertFalse(rti_mock.called)
+ self.assertFalse(in_resume_deploy_mock.called)
+
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ def test_heartbeat_in_maintenance(self, ncrc_mock, rti_mock, cd_mock):
+ # NOTE(pas-ha) checking only for states that are not noop
+ for state in (states.DEPLOYWAIT, states.CLEANWAIT):
+ for m in (ncrc_mock, rti_mock, cd_mock):
+ m.reset_mock()
+ self.node.provision_state = state
+ self.node.maintenance = True
+ self.node.save()
+ agent_url = 'url-%s' % state
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.deploy.heartbeat(task, agent_url, '3.2.0')
+ self.assertFalse(task.shared)
+ self.assertEqual(
+ agent_url,
+ task.node.driver_internal_info['agent_url'])
+ self.assertEqual(
+ '3.2.0',
+ task.node.driver_internal_info['agent_version'])
+ self.assertEqual(state, task.node.provision_state)
+ self.assertIsNone(task.node.last_error)
+ self.assertEqual(0, ncrc_mock.call_count)
+ self.assertEqual(0, rti_mock.call_count)
+ self.assertEqual(0, cd_mock.call_count)
+
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ def test_heartbeat_in_maintenance_abort(self, ncrc_mock, rti_mock,
+ cd_mock):
+ CONF.set_override('allow_provisioning_in_maintenance', False,
+ group='conductor')
+ for state, expected in [(states.DEPLOYWAIT, states.DEPLOYFAIL),
+ (states.CLEANWAIT, states.CLEANFAIL),
+ (states.RESCUEWAIT, states.RESCUEFAIL)]:
+ for m in (ncrc_mock, rti_mock, cd_mock):
+ m.reset_mock()
+ self.node.provision_state = state
+ self.node.maintenance = True
+ self.node.save()
+ agent_url = 'url-%s' % state
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.deploy.heartbeat(task, agent_url, '3.2.0')
+ self.assertFalse(task.shared)
+ self.assertIsNone(
+ task.node.driver_internal_info.get('agent_url', None))
+ self.assertEqual(
+ '3.2.0',
+ task.node.driver_internal_info['agent_version'])
+ self.node.refresh()
+ self.assertEqual(expected, self.node.provision_state)
+ self.assertIn('aborted', self.node.last_error)
+ self.assertEqual(0, ncrc_mock.call_count)
+ self.assertEqual(0, rti_mock.call_count)
+ self.assertEqual(0, cd_mock.call_count)
+
+ @mock.patch('time.sleep', lambda _t: None)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ def test_heartbeat_with_reservation(self, ncrc_mock, rti_mock, cd_mock):
+ # NOTE(pas-ha) checking only for states that are not noop
+ for state in (states.DEPLOYWAIT, states.CLEANWAIT):
+ for m in (ncrc_mock, rti_mock, cd_mock):
+ m.reset_mock()
+ self.node.provision_state = state
+ self.node.reservation = 'localhost'
+ self.node.save()
+ old_drv_info = self.node.driver_internal_info.copy()
+ agent_url = 'url-%s' % state
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.deploy.heartbeat(task, agent_url, '3.2.0')
+ self.assertTrue(task.shared)
+ self.assertEqual(old_drv_info, task.node.driver_internal_info)
+ self.assertIsNone(task.node.last_error)
+ self.assertEqual(0, ncrc_mock.call_count)
+ self.assertEqual(0, rti_mock.call_count)
+ self.assertEqual(0, cd_mock.call_count)
+
+ @mock.patch.object(agent_base.LOG, 'error', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ def test_heartbeat_noops_in_wrong_state(self, ncrc_mock, rti_mock,
+ cd_mock, log_mock):
+ allowed = {states.DEPLOYWAIT, states.CLEANWAIT, states.RESCUEWAIT,
+ states.DEPLOYING, states.CLEANING, states.RESCUING}
+ for state in set(states.machine.states) - allowed:
+ for m in (ncrc_mock, rti_mock, cd_mock, log_mock):
+ m.reset_mock()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.provision_state = state
+ self.deploy.heartbeat(task, 'url', '1.0.0')
+ self.assertTrue(task.shared)
+ self.assertNotIn('agent_last_heartbeat',
+ task.node.driver_internal_info)
+ self.assertEqual(0, ncrc_mock.call_count)
+ self.assertEqual(0, rti_mock.call_count)
+ self.assertEqual(0, cd_mock.call_count)
+ log_mock.assert_called_once_with(mock.ANY,
+ {'node': self.node.uuid,
+ 'state': state})
+
+ @mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
+ autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'reboot_to_instance', autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ def test_heartbeat_noops_in_wrong_state2(self, ncrc_mock, rti_mock,
+ cd_mock):
+ CONF.set_override('allow_provisioning_in_maintenance', False,
+ group='conductor')
+ allowed = {states.DEPLOYWAIT, states.CLEANWAIT}
+ for state in set(states.machine.states) - allowed:
+ for m in (ncrc_mock, rti_mock, cd_mock):
+ m.reset_mock()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.node.provision_state = state
+ self.deploy.heartbeat(task, 'url', '1.0.0')
+ self.assertTrue(task.shared)
+ self.assertEqual(0, ncrc_mock.call_count)
+ self.assertEqual(0, rti_mock.call_count)
+ self.assertEqual(0, cd_mock.call_count)
+
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'in_core_deploy_step', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_has_started', autospec=True)
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'deploy_is_done',
+ autospec=True)
+ @mock.patch.object(agent_base.LOG, 'exception', autospec=True)
+ def test_heartbeat_deploy_done_fails(self, log_mock, done_mock,
+ failed_mock, deploy_started_mock,
+ in_deploy_mock):
+ in_deploy_mock.return_value = True
+ deploy_started_mock.return_value = True
+ done_mock.side_effect = Exception('LlamaException')
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+ failed_mock.assert_called_once_with(
+ task, mock.ANY, collect_logs=True)
+ log_mock.assert_called_once_with(
+ 'Asynchronous exception: Failed checking if deploy is done. '
+ 'Exception: LlamaException for node %(node)s',
+ {'node': task.node.uuid})
+
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'in_core_deploy_step', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_has_started', autospec=True)
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin, 'deploy_is_done',
+ autospec=True)
+ @mock.patch.object(agent_base.LOG, 'exception', autospec=True)
+ def test_heartbeat_deploy_done_raises_with_event(self, log_mock, done_mock,
+ failed_mock,
+ deploy_started_mock,
+ in_deploy_mock):
+ in_deploy_mock.return_value = True
+ deploy_started_mock.return_value = True
+ with task_manager.acquire(
+ self.context, self.node['uuid'], shared=False) as task:
+
+ def driver_failure(*args, **kwargs):
+ # simulate driver failure that both advances the FSM
+ # and raises an exception
+ task.node.provision_state = states.DEPLOYFAIL
+ raise Exception('LlamaException')
+
+ task.node.provision_state = states.DEPLOYWAIT
+ task.node.target_provision_state = states.ACTIVE
+ done_mock.side_effect = driver_failure
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+ # task.node.provision_state being set to DEPLOYFAIL
+ # within the driver_failue, hearbeat should not call
+ # deploy_utils.set_failed_state anymore
+ self.assertFalse(failed_mock.called)
+ log_mock.assert_called_once_with(
+ 'Asynchronous exception: Failed checking if deploy is done. '
+ 'Exception: LlamaException for node %(node)s',
+ {'node': task.node.uuid})
+
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'refresh_clean_steps', autospec=True)
+ @mock.patch.object(conductor_steps, 'set_node_cleaning_steps',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ def test_heartbeat_resume_clean(self, mock_notify, mock_set_steps,
+ mock_refresh, mock_touch):
+ self.node.clean_step = {}
+ self.node.provision_state = states.CLEANWAIT
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+
+ mock_touch.assert_called_once_with(mock.ANY)
+ mock_refresh.assert_called_once_with(mock.ANY, task)
+ mock_notify.assert_called_once_with(task)
+ mock_set_steps.assert_called_once_with(task)
+
+ @mock.patch.object(manager_utils, 'cleaning_error_handler')
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'refresh_clean_steps', autospec=True)
+ @mock.patch.object(conductor_steps, 'set_node_cleaning_steps',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ def test_heartbeat_resume_clean_fails(self, mock_notify, mock_set_steps,
+ mock_refresh, mock_touch,
+ mock_handler):
+ mocks = [mock_refresh, mock_set_steps, mock_notify]
+ self.node.clean_step = {}
+ self.node.provision_state = states.CLEANWAIT
+ self.node.save()
+ for i in range(len(mocks)):
+ before_failed_mocks = mocks[:i]
+ failed_mock = mocks[i]
+ after_failed_mocks = mocks[i + 1:]
+ failed_mock.side_effect = Exception()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+
+ mock_touch.assert_called_once_with(mock.ANY)
+ mock_handler.assert_called_once_with(task, mock.ANY)
+ for called in before_failed_mocks + [failed_mock]:
+ self.assertTrue(called.called)
+ for not_called in after_failed_mocks:
+ self.assertFalse(not_called.called)
+
+ # Reset mocks for the next interaction
+ for m in mocks + [mock_touch, mock_handler]:
+ m.reset_mock()
+ failed_mock.side_effect = None
+
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'continue_cleaning', autospec=True)
+ def test_heartbeat_continue_cleaning(self, mock_continue, mock_touch):
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'foo',
+ 'reboot_requested': False
+ }
+ self.node.provision_state = states.CLEANWAIT
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+
+ mock_touch.assert_called_once_with(mock.ANY)
+ mock_continue.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'continue_cleaning', autospec=True)
+ def test_heartbeat_continue_cleaning_polling(self, mock_continue,
+ mock_touch):
+ info = self.node.driver_internal_info
+ info['cleaning_polling'] = True
+ self.node.driver_internal_info = info
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'foo',
+ 'reboot_requested': False
+ }
+ self.node.provision_state = states.CLEANWAIT
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+
+ mock_touch.assert_called_once_with(mock.ANY)
+ self.assertFalse(mock_continue.called)
+
+ @mock.patch.object(manager_utils, 'cleaning_error_handler')
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'continue_cleaning', autospec=True)
+ def test_heartbeat_continue_cleaning_fails(self, mock_continue,
+ mock_handler):
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'foo',
+ 'reboot_requested': False
+ }
+
+ mock_continue.side_effect = Exception()
+
+ self.node.provision_state = states.CLEANWAIT
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+
+ mock_continue.assert_called_once_with(mock.ANY, task)
+ mock_handler.assert_called_once_with(task, mock.ANY)
+
+ @mock.patch.object(manager_utils, 'rescuing_error_handler')
+ @mock.patch.object(agent_base.HeartbeatMixin, '_finalize_rescue',
+ autospec=True)
+ def test_heartbeat_rescue(self, mock_finalize_rescue,
+ mock_rescue_err_handler):
+ self.node.provision_state = states.RESCUEWAIT
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+
+ mock_finalize_rescue.assert_called_once_with(mock.ANY, task)
+ self.assertFalse(mock_rescue_err_handler.called)
+
+ @mock.patch.object(manager_utils, 'rescuing_error_handler')
+ @mock.patch.object(agent_base.HeartbeatMixin, '_finalize_rescue',
+ autospec=True)
+ def test_heartbeat_rescue_fails(self, mock_finalize,
+ mock_rescue_err_handler):
+ self.node.provision_state = states.RESCUEWAIT
+ self.node.save()
+ mock_finalize.side_effect = Exception('some failure')
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '1.0.0')
+
+ mock_finalize.assert_called_once_with(mock.ANY, task)
+ mock_rescue_err_handler.assert_called_once_with(
+ task, 'Asynchronous exception: Node failed to perform '
+ 'rescue operation. Exception: some failure for node')
+
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'in_core_deploy_step', autospec=True)
+ @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True)
+ @mock.patch.object(agent_base.HeartbeatMixin,
+ 'deploy_has_started', autospec=True)
+ def test_heartbeat_touch_provisioning_and_url_save(self,
+ mock_deploy_started,
+ mock_touch,
+ mock_in_deploy):
+ mock_in_deploy.return_value = True
+ mock_deploy_started.return_value = True
+
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '3.2.0')
+ self.assertEqual('http://127.0.0.1:8080',
+ task.node.driver_internal_info['agent_url'])
+ self.assertEqual('3.2.0',
+ task.node.driver_internal_info['agent_version'])
+ self.assertIsNotNone(
+ task.node.driver_internal_info['agent_last_heartbeat'])
+ mock_touch.assert_called_once_with(mock.ANY)
+
+ @mock.patch.object(agent_base.LOG, 'error', autospec=True)
+ def test_heartbeat_records_cleaning_deploying(self, log_mock):
+ for provision_state in (states.CLEANING, states.DEPLOYING):
+ self.node.driver_internal_info = {}
+ self.node.provision_state = provision_state
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '3.2.0')
+ self.assertEqual('http://127.0.0.1:8080',
+ task.node.driver_internal_info['agent_url'])
+ self.assertEqual('3.2.0',
+ task.node.driver_internal_info[
+ 'agent_version'])
+ self.assertIsNotNone(
+ task.node.driver_internal_info['agent_last_heartbeat'])
+ self.assertEqual(provision_state, task.node.provision_state)
+ self.assertFalse(log_mock.called)
+
+ def test_heartbeat_records_fast_track(self):
+ self.config(fast_track=True, group='deploy')
+ for provision_state in [states.ENROLL, states.MANAGEABLE,
+ states.AVAILABLE]:
+ self.node.driver_internal_info = {}
+ self.node.provision_state = provision_state
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.heartbeat(task, 'http://127.0.0.1:8080', '3.2.0')
+ self.assertEqual('http://127.0.0.1:8080',
+ task.node.driver_internal_info['agent_url'])
+ self.assertEqual('3.2.0',
+ task.node.driver_internal_info[
+ 'agent_version'])
+ self.assertIsNotNone(
+ task.node.driver_internal_info['agent_last_heartbeat'])
+ self.assertEqual(provision_state, task.node.provision_state)
+
+ def test_in_core_deploy_step(self):
+ self.node.deploy_step = {
+ 'interface': 'deploy', 'step': 'deploy', 'priority': 100}
+ info = self.node.driver_internal_info
+ info['deploy_steps'] = [self.node.deploy_step]
+ self.node.driver_internal_info = info
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.assertTrue(self.deploy.in_core_deploy_step(task))
+
+ def test_in_core_deploy_step_no_steps_list(self):
+ # Need to handle drivers without deploy step support, remove in the
+ # Train release.
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.assertTrue(self.deploy.in_core_deploy_step(task))
+
+ def test_in_core_deploy_step_in_other_step(self):
+ self.node.deploy_step = {
+ 'interface': 'deploy', 'step': 'other-step', 'priority': 100}
+ info = self.node.driver_internal_info
+ info['deploy_steps'] = [self.node.deploy_step]
+ self.node.driver_internal_info = info
+ self.node.save()
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.assertFalse(self.deploy.in_core_deploy_step(task))
+
+
+class AgentRescueTests(AgentDeployMixinBaseTest):
+
+ def setUp(self):
+ super(AgentRescueTests, self).setUp()
+
+ @mock.patch.object(agent.AgentRescue, 'clean_up',
+ spec_set=True, autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'finalize_rescue',
+ spec=types.FunctionType)
+ def test__finalize_rescue(self, mock_finalize_rescue,
+ mock_clean_up):
+ node = self.node
+ node.provision_state = states.RESCUEWAIT
+ node.save()
+ mock_finalize_rescue.return_value = {'command_status': 'SUCCEEDED'}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.driver.network.configure_tenant_networks = mock.Mock()
+ task.process_event = mock.Mock()
+ self.deploy._finalize_rescue(task)
+ mock_finalize_rescue.assert_called_once_with(task.node)
+ task.process_event.assert_has_calls([mock.call('resume'),
+ mock.call('done')])
+ mock_clean_up.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(agent_client.AgentClient, 'finalize_rescue',
+ spec=types.FunctionType)
+ def test__finalize_rescue_bad_command_result(self, mock_finalize_rescue):
+ node = self.node
+ node.provision_state = states.RESCUEWAIT
+ node.save()
+ mock_finalize_rescue.return_value = {'command_status': 'FAILED',
+ 'command_error': 'bad'}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InstanceRescueFailure,
+ self.deploy._finalize_rescue, task)
+ mock_finalize_rescue.assert_called_once_with(task.node)
+
+ @mock.patch.object(agent_client.AgentClient, 'finalize_rescue',
+ spec=types.FunctionType)
+ def test__finalize_rescue_exc(self, mock_finalize_rescue):
+ node = self.node
+ node.provision_state = states.RESCUEWAIT
+ node.save()
+ mock_finalize_rescue.side_effect = exception.IronicException("No pass")
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InstanceRescueFailure,
+ self.deploy._finalize_rescue, task)
+ mock_finalize_rescue.assert_called_once_with(task.node)
+
+ @mock.patch.object(agent_client.AgentClient, 'finalize_rescue',
+ spec=types.FunctionType)
+ def test__finalize_rescue_missing_command_result(self,
+ mock_finalize_rescue):
+ node = self.node
+ node.provision_state = states.RESCUEWAIT
+ node.save()
+ mock_finalize_rescue.return_value = {}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InstanceRescueFailure,
+ self.deploy._finalize_rescue, task)
+ mock_finalize_rescue.assert_called_once_with(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)
+ @mock.patch.object(agent.AgentRescue, 'clean_up',
+ spec_set=True, autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'finalize_rescue',
+ spec=types.FunctionType)
+ def test__finalize_rescue_with_smartnic_port(
+ self, mock_finalize_rescue, mock_clean_up,
+ power_on_node_if_needed_mock, restore_power_state_mock):
+ node = self.node
+ node.provision_state = states.RESCUEWAIT
+ node.save()
+ mock_finalize_rescue.return_value = {'command_status': 'SUCCEEDED'}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.driver.network.configure_tenant_networks = mock.Mock()
+ task.process_event = mock.Mock()
+ power_on_node_if_needed_mock.return_value = states.POWER_OFF
+ self.deploy._finalize_rescue(task)
+ mock_finalize_rescue.assert_called_once_with(task.node)
+ task.process_event.assert_has_calls([mock.call('resume'),
+ mock.call('done')])
+ mock_clean_up.assert_called_once_with(mock.ANY, task)
+ power_on_node_if_needed_mock.assert_called_once_with(task)
+ restore_power_state_mock.assert_called_once_with(
+ task, states.POWER_OFF)
+
+
+class AgentDeployMixinTest(AgentDeployMixinBaseTest):
+ @mock.patch.object(manager_utils, 'power_on_node_if_needed')
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
+ 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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy(
+ self, power_off_mock, get_power_state_mock,
+ node_power_action_mock, collect_mock, resume_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.deploy_step = {
+ 'step': 'deploy', 'priority': 50, 'interface': 'deploy'}
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ get_power_state_mock.side_effect = [states.POWER_ON,
+ states.POWER_OFF]
+
+ power_on_node_if_needed_mock.return_value = None
+ self.deploy.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(2, get_power_state_mock.call_count)
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_ON)
+ 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)
+ resume_mock.assert_called_once_with(task)
+
+ @mock.patch.object(manager_utils, 'power_on_node_if_needed',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
+ 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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_deprecated(
+ self, power_off_mock, get_power_state_mock,
+ node_power_action_mock, collect_mock, resume_mock,
+ power_on_node_if_needed_mock):
+ # TODO(rloo): no deploy steps; delete this when we remove support
+ # for handling no deploy steps.
+ cfg.CONF.set_override('deploy_logs_collect', 'always', 'agent')
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.deploy_step = None
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ get_power_state_mock.side_effect = [states.POWER_ON,
+ states.POWER_OFF]
+ power_on_node_if_needed_mock.return_value = None
+ self.deploy.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(2, get_power_state_mock.call_count)
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_ON)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ collect_mock.assert_called_once_with(task.node)
+ self.assertFalse(resume_mock.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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'remove_provisioning_network', spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'configure_tenant_networks', spec_set=True, autospec=True)
+ def test_reboot_and_finish_deploy_soft_poweroff_doesnt_complete(
+ self, configure_tenant_net_mock, remove_provisioning_net_mock,
+ power_off_mock, get_power_state_mock,
+ node_power_action_mock, mock_collect,
+ power_on_node_if_needed_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,
+ shared=True) as task:
+ power_on_node_if_needed_mock.return_value = None
+ get_power_state_mock.return_value = states.POWER_ON
+ self.deploy.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.POWER_OFF),
+ mock.call(task, states.POWER_ON)])
+ remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
+ task)
+ configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ self.assertFalse(mock_collect.called)
+
+ @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'remove_provisioning_network', spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'configure_tenant_networks', spec_set=True, autospec=True)
+ def test_reboot_and_finish_deploy_soft_poweroff_fails(
+ self, configure_tenant_net_mock, remove_provisioning_net_mock,
+ power_off_mock, node_power_action_mock, mock_collect):
+ power_off_mock.side_effect = RuntimeError("boom")
+ 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,
+ shared=True) as task:
+ self.deploy.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.POWER_OFF),
+ mock.call(task, states.POWER_ON)])
+ remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
+ task)
+ configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ self.assertFalse(mock_collect.called)
+
+ @mock.patch.object(manager_utils, 'power_on_node_if_needed')
+ @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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'remove_provisioning_network', spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'configure_tenant_networks', spec_set=True, autospec=True)
+ def test_reboot_and_finish_deploy_get_power_state_fails(
+ self, configure_tenant_net_mock, remove_provisioning_net_mock,
+ power_off_mock, get_power_state_mock, node_power_action_mock,
+ mock_collect, power_on_node_if_needed_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,
+ shared=True) as task:
+ get_power_state_mock.side_effect = RuntimeError("boom")
+ power_on_node_if_needed_mock.return_value = None
+ self.deploy.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.POWER_OFF),
+ mock.call(task, states.POWER_ON)])
+ remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
+ task)
+ configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ 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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch('ironic.drivers.modules.network.neutron.NeutronNetwork.'
+ 'remove_provisioning_network', spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.network.neutron.NeutronNetwork.'
+ 'configure_tenant_networks', spec_set=True, autospec=True)
+ def test_reboot_and_finish_deploy_configure_tenant_network_exception(
+ self, configure_tenant_net_mock, remove_provisioning_net_mock,
+ power_off_mock, get_power_state_mock, node_power_action_mock,
+ mock_collect, power_on_node_if_needed_mock):
+ self.node.network_interface = 'neutron'
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ power_on_node_if_needed_mock.return_value = None
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ configure_tenant_net_mock.side_effect = exception.NetworkError(
+ "boom")
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.deploy.reboot_and_finish_deploy, task)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
+ task)
+ configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertFalse(mock_collect.called)
+
+ @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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_power_off_fails(
+ self, power_off_mock, get_power_state_mock,
+ node_power_action_mock, mock_collect):
+ 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,
+ shared=True) as task:
+ get_power_state_mock.return_value = states.POWER_ON
+ node_power_action_mock.side_effect = RuntimeError("boom")
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.deploy.reboot_and_finish_deploy,
+ task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.POWER_OFF)])
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ mock_collect.assert_called_once_with(task.node)
+
+ @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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'remove_provisioning_network', spec_set=True, autospec=True)
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
+ 'configure_tenant_networks', spec_set=True, autospec=True)
+ def test_reboot_and_finish_deploy_power_on_fails(
+ self, configure_tenant_net_mock, remove_provisioning_net_mock,
+ power_off_mock, get_power_state_mock,
+ node_power_action_mock, mock_collect,
+ power_on_node_if_needed_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,
+ shared=True) as task:
+ power_on_node_if_needed_mock.return_value = None
+ get_power_state_mock.return_value = states.POWER_ON
+ node_power_action_mock.side_effect = [None,
+ RuntimeError("boom")]
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.deploy.reboot_and_finish_deploy,
+ task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(7, get_power_state_mock.call_count)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.POWER_OFF),
+ mock.call(task, states.POWER_ON)])
+ remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
+ task)
+ configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertFalse(mock_collect.called)
+
+ @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'sync',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_power_action_oob_power_off(
+ self, sync_mock, node_power_action_mock, mock_collect):
+ # Enable force power off
+ driver_info = self.node.driver_info
+ driver_info['deploy_forces_oob_reboot'] = True
+ self.node.driver_info = driver_info
+
+ 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,
+ shared=True) as task:
+ self.deploy.reboot_and_finish_deploy(task)
+
+ sync_mock.assert_called_once_with(task.node)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.POWER_OFF),
+ mock.call(task, states.POWER_ON),
+ ])
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ self.assertFalse(mock_collect.called)
+
+ @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
+ @mock.patch.object(agent_base.LOG, 'warning', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'sync',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_power_action_oob_power_off_failed(
+ self, sync_mock, node_power_action_mock, log_mock, mock_collect):
+ # Enable force power off
+ driver_info = self.node.driver_info
+ driver_info['deploy_forces_oob_reboot'] = True
+ self.node.driver_info = driver_info
+
+ 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,
+ shared=True) as task:
+ sync_mock.return_value = {'faultstring': 'Unknown command: blah'}
+ self.deploy.reboot_and_finish_deploy(task)
+
+ sync_mock.assert_called_once_with(task.node)
+ node_power_action_mock.assert_has_calls([
+ mock.call(task, states.POWER_OFF),
+ mock.call(task, states.POWER_ON),
+ ])
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ log_error = ('The version of the IPA ramdisk used in the '
+ 'deployment do not support the command "sync"')
+ log_mock.assert_called_once_with(
+ 'Failed to flush the file system prior to hard rebooting the '
+ 'node %(node)s. Error: %(error)s',
+ {'node': task.node.uuid, 'error': log_error})
+ self.assertFalse(mock_collect.called)
+
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ def test_configure_local_boot(self, try_set_boot_device_mock,
+ install_bootloader_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.deploy.configure_local_boot(task, root_uuid='some-root-uuid')
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None, prep_boot_part_uuid=None)
+
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ def test_configure_local_boot_with_prep(self, try_set_boot_device_mock,
+ install_bootloader_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.deploy.configure_local_boot(task, root_uuid='some-root-uuid',
+ prep_boot_part_uuid='fake-prep')
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None, prep_boot_part_uuid='fake-prep')
+
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ def test_configure_local_boot_uefi(self, try_set_boot_device_mock,
+ install_bootloader_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.deploy.configure_local_boot(
+ task, root_uuid='some-root-uuid',
+ efi_system_part_uuid='efi-system-part-uuid')
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid='efi-system-part-uuid',
+ prep_boot_part_uuid=None)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_whole_disk_image(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_no_root_uuid(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.deploy.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(boot_mode_utils, 'get_boot_mode',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_no_root_uuid_whole_disk(
+ self, install_bootloader_mock, try_set_boot_device_mock,
+ boot_mode_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ boot_mode_mock.return_value = 'uefi'
+ self.deploy.configure_local_boot(
+ task, root_uuid=None,
+ efi_system_part_uuid='efi-system-part-uuid')
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid=None,
+ efi_system_part_uuid='efi-system-part-uuid',
+ prep_boot_part_uuid=None)
+
+ @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_on_software_raid(
+ self, install_bootloader_mock, try_set_boot_device_mock,
+ GlanceImageService_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ task.node.target_raid_config = {
+ "logical_disks": [
+ {
+ "size_gb": 100,
+ "raid_level": "1",
+ "controller": "software",
+ },
+ {
+ "size_gb": 'MAX',
+ "raid_level": "0",
+ "controller": "software",
+ }
+ ]
+ }
+ self.deploy.configure_local_boot(task)
+ self.assertTrue(GlanceImageService_mock.called)
+ self.assertTrue(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_on_software_raid_exception(
+ self, install_bootloader_mock, try_set_boot_device_mock,
+ GlanceImageService_mock):
+ GlanceImageService_mock.side_effect = Exception('Glance not found')
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ root_uuid = "1efecf88-2b58-4d4e-8fbd-7bef1a40a1b0"
+ task.node.driver_internal_info['root_uuid_or_disk_id'] = root_uuid
+ task.node.target_raid_config = {
+ "logical_disks": [
+ {
+ "size_gb": 100,
+ "raid_level": "1",
+ "controller": "software",
+ },
+ {
+ "size_gb": 'MAX',
+ "raid_level": "0",
+ "controller": "software",
+ }
+ ]
+ }
+ self.deploy.configure_local_boot(task)
+ self.assertTrue(GlanceImageService_mock.called)
+ # check if the root_uuid comes from the driver_internal_info
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid=root_uuid,
+ efi_system_part_uuid=None, prep_boot_part_uuid=None)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_on_non_software_raid(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ task.node.target_raid_config = {
+ "logical_disks": [
+ {
+ "size_gb": 100,
+ "raid_level": "1",
+ },
+ {
+ "size_gb": 'MAX',
+ "raid_level": "0",
+ }
+ ]
+ }
+ self.deploy.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_enforce_persistent_boot_device_default(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ driver_info = task.node.driver_info
+ driver_info['force_persistent_boot_device'] = 'Default'
+ task.node.driver_info = driver_info
+ driver_info['force_persistent_boot_device'] = 'Always'
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.deploy.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_enforce_persistent_boot_device_always(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ driver_info = task.node.driver_info
+ driver_info['force_persistent_boot_device'] = 'Always'
+ task.node.driver_info = driver_info
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.deploy.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_enforce_persistent_boot_device_never(
+ self, install_bootloader_mock, try_set_boot_device_mock):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ driver_info = task.node.driver_info
+ driver_info['force_persistent_boot_device'] = 'Never'
+ task.node.driver_info = driver_info
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.deploy.configure_local_boot(task)
+ self.assertFalse(install_bootloader_mock.called)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=False)
+
+ @mock.patch.object(agent_client.AgentClient, 'collect_system_logs',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_boot_loader_install_fail(
+ self, install_bootloader_mock, collect_logs_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'FAILED', 'command_error': 'boom'}
+ 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'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.deploy.configure_local_boot,
+ task, root_uuid='some-root-uuid')
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None, prep_boot_part_uuid=None)
+ collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+
+ @mock.patch.object(agent_client.AgentClient, 'collect_system_logs',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'install_bootloader',
+ autospec=True)
+ def test_configure_local_boot_set_boot_device_fail(
+ self, install_bootloader_mock, try_set_boot_device_mock,
+ collect_logs_mock):
+ install_bootloader_mock.return_value = {
+ 'command_status': 'SUCCESS', 'command_error': None}
+ try_set_boot_device_mock.side_effect = RuntimeError('error')
+ 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'],
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.deploy.configure_local_boot,
+ task, root_uuid='some-root-uuid',
+ prep_boot_part_uuid=None)
+ install_bootloader_mock.assert_called_once_with(
+ mock.ANY, task.node, root_uuid='some-root-uuid',
+ efi_system_part_uuid=None, prep_boot_part_uuid=None)
+ try_set_boot_device_mock.assert_called_once_with(
+ task, boot_devices.DISK, persistent=True)
+ collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(agent_base.AgentDeployMixin,
+ 'configure_local_boot', autospec=True)
+ def test_prepare_instance_to_boot_netboot(self, configure_mock,
+ boot_option_mock,
+ prepare_instance_mock,
+ failed_state_mock):
+ boot_option_mock.return_value = 'netboot'
+ prepare_instance_mock.return_value = None
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ root_uuid = 'root_uuid'
+ efi_system_part_uuid = 'efi_sys_uuid'
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.prepare_instance_to_boot(task, root_uuid,
+ efi_system_part_uuid)
+ self.assertFalse(configure_mock.called)
+ boot_option_mock.assert_called_once_with(task.node)
+ prepare_instance_mock.assert_called_once_with(task.driver.boot,
+ task)
+ self.assertFalse(failed_state_mock.called)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(agent_base.AgentDeployMixin,
+ 'configure_local_boot', autospec=True)
+ def test_prepare_instance_to_boot_localboot(self, configure_mock,
+ boot_option_mock,
+ prepare_instance_mock,
+ failed_state_mock):
+ boot_option_mock.return_value = 'local'
+ prepare_instance_mock.return_value = None
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ root_uuid = 'root_uuid'
+ efi_system_part_uuid = 'efi_sys_uuid'
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.prepare_instance_to_boot(task, root_uuid,
+ efi_system_part_uuid)
+ configure_mock.assert_called_once_with(
+ self.deploy, task,
+ root_uuid=root_uuid,
+ efi_system_part_uuid=efi_system_part_uuid,
+ prep_boot_part_uuid=None)
+ boot_option_mock.assert_called_once_with(task.node)
+ prepare_instance_mock.assert_called_once_with(task.driver.boot,
+ task)
+ self.assertFalse(failed_state_mock.called)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(agent_base.AgentDeployMixin,
+ 'configure_local_boot', autospec=True)
+ def test_prepare_instance_to_boot_localboot_prep_partition(
+ self, configure_mock, boot_option_mock,
+ prepare_instance_mock, failed_state_mock):
+ boot_option_mock.return_value = 'local'
+ prepare_instance_mock.return_value = None
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ root_uuid = 'root_uuid'
+ efi_system_part_uuid = 'efi_sys_uuid'
+ prep_boot_part_uuid = 'prep_boot_part_uuid'
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.prepare_instance_to_boot(task, root_uuid,
+ efi_system_part_uuid,
+ prep_boot_part_uuid)
+ configure_mock.assert_called_once_with(
+ self.deploy, task,
+ root_uuid=root_uuid,
+ efi_system_part_uuid=efi_system_part_uuid,
+ prep_boot_part_uuid=prep_boot_part_uuid)
+ boot_option_mock.assert_called_once_with(task.node)
+ prepare_instance_mock.assert_called_once_with(task.driver.boot,
+ task)
+ self.assertFalse(failed_state_mock.called)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(agent_base.AgentDeployMixin,
+ 'configure_local_boot', autospec=True)
+ def test_prepare_instance_to_boot_configure_fails(self, configure_mock,
+ boot_option_mock,
+ prepare_mock,
+ failed_state_mock):
+ boot_option_mock.return_value = 'local'
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ root_uuid = 'root_uuid'
+ efi_system_part_uuid = 'efi_sys_uuid'
+ reason = 'reason'
+ configure_mock.side_effect = (
+ exception.InstanceDeployFailure(reason=reason))
+ prepare_mock.side_effect = (
+ exception.InstanceDeployFailure(reason=reason))
+
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.deploy.prepare_instance_to_boot, task,
+ root_uuid, efi_system_part_uuid)
+ configure_mock.assert_called_once_with(
+ self.deploy, task,
+ root_uuid=root_uuid,
+ efi_system_part_uuid=efi_system_part_uuid,
+ prep_boot_part_uuid=None)
+ boot_option_mock.assert_called_once_with(task.node)
+ self.assertFalse(prepare_mock.called)
+ self.assertFalse(failed_state_mock.called)
+
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning(self, status_mock, notify_mock):
+ # Test a successful execute clean step on the agent
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'erase_devices',
+ 'reboot_requested': False
+ }
+ self.node.save()
+ status_mock.return_value = [{
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {
+ 'clean_step': self.node.clean_step
+ }
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ notify_mock.assert_called_once_with(task)
+
+ @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ def test__cleaning_reboot(self, mock_reboot, mock_prepare, mock_build_opt):
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ agent_base._cleaning_reboot(task)
+ self.assertTrue(mock_build_opt.called)
+ self.assertTrue(mock_prepare.called)
+ mock_reboot.assert_called_once_with(task, states.REBOOT)
+ self.assertTrue(task.node.driver_internal_info['cleaning_reboot'])
+
+ @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ def test__cleaning_reboot_fail(self, mock_reboot, mock_handler,
+ mock_prepare, mock_build_opt):
+ mock_reboot.side_effect = RuntimeError("broken")
+
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ agent_base._cleaning_reboot(task)
+ mock_reboot.assert_called_once_with(task, states.REBOOT)
+ mock_handler.assert_called_once_with(task, mock.ANY)
+ self.assertNotIn('cleaning_reboot',
+ task.node.driver_internal_info)
+
+ @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True,
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_reboot(
+ self, status_mock, reboot_mock, mock_prepare, mock_build_opt):
+ # Test a successful execute clean step on the agent, with reboot
+ self.node.clean_step = {
+ 'priority': 42,
+ 'interface': 'deploy',
+ 'step': 'reboot_me_afterwards',
+ 'reboot_requested': True
+ }
+ self.node.save()
+ status_mock.return_value = [{
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {
+ 'clean_step': self.node.clean_step
+ }
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ reboot_mock.assert_called_once_with(task, states.REBOOT)
+
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_after_reboot(self, status_mock, notify_mock):
+ # Test a successful execute clean step on the agent, with reboot
+ self.node.clean_step = {
+ 'priority': 42,
+ 'interface': 'deploy',
+ 'step': 'reboot_me_afterwards',
+ 'reboot_requested': True
+ }
+ driver_internal_info = self.node.driver_internal_info
+ driver_internal_info['cleaning_reboot'] = True
+ self.node.driver_internal_info = driver_internal_info
+ self.node.save()
+ # Represents a freshly booted agent with no commands
+ status_mock.return_value = []
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ notify_mock.assert_called_once_with(task)
+ self.assertNotIn('cleaning_reboot',
+ task.node.driver_internal_info)
+
+ @mock.patch.object(agent_base,
+ '_get_post_clean_step_hook', autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_with_hook(
+ self, status_mock, notify_mock, get_hook_mock):
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'raid',
+ 'step': 'create_configuration',
+ }
+ self.node.save()
+ command_status = {
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {'clean_step': self.node.clean_step}}
+ status_mock.return_value = [command_status]
+ hook_mock = mock.MagicMock(spec=types.FunctionType, __name__='foo')
+ get_hook_mock.return_value = hook_mock
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+
+ get_hook_mock.assert_called_once_with(task.node)
+ hook_mock.assert_called_once_with(task, command_status)
+ notify_mock.assert_called_once_with(task)
+
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_base,
+ '_get_post_clean_step_hook', autospec=True)
+ @mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_with_hook_fails(
+ self, status_mock, error_handler_mock, get_hook_mock,
+ notify_mock):
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'raid',
+ 'step': 'create_configuration',
+ }
+ self.node.save()
+ command_status = {
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {'clean_step': self.node.clean_step}}
+ status_mock.return_value = [command_status]
+ hook_mock = mock.MagicMock(spec=types.FunctionType, __name__='foo')
+ hook_mock.side_effect = RuntimeError('error')
+ get_hook_mock.return_value = hook_mock
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+
+ get_hook_mock.assert_called_once_with(task.node)
+ hook_mock.assert_called_once_with(task, command_status)
+ error_handler_mock.assert_called_once_with(task, mock.ANY)
+ self.assertFalse(notify_mock.called)
+
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_old_command(self, status_mock, notify_mock):
+ # Test when a second execute_clean_step happens to the agent, but
+ # the new step hasn't started yet.
+ self.node.clean_step = {
+ 'priority': 10,
+ 'interface': 'deploy',
+ 'step': 'erase_devices',
+ 'reboot_requested': False
+ }
+ self.node.save()
+ status_mock.return_value = [{
+ 'command_status': 'SUCCEEDED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {
+ 'priority': 20,
+ 'interface': 'deploy',
+ 'step': 'update_firmware',
+ 'reboot_requested': False
+ }
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ self.assertFalse(notify_mock.called)
+
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_running(self, status_mock, notify_mock):
+ # Test that no action is taken while a clean step is executing
+ status_mock.return_value = [{
+ 'command_status': 'RUNNING',
+ 'command_name': 'execute_clean_step',
+ 'command_result': None
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ self.assertFalse(notify_mock.called)
+
+ @mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_fail(self, status_mock, error_mock):
+ # Test that a failure puts the node in CLEANFAIL
+ status_mock.return_value = [{
+ 'command_status': 'FAILED',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {}
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ error_mock.assert_called_once_with(task, mock.ANY)
+
+ @mock.patch.object(conductor_steps, 'set_node_cleaning_steps',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_base.AgentDeployMixin,
+ 'refresh_clean_steps', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def _test_continue_cleaning_clean_version_mismatch(
+ self, status_mock, refresh_steps_mock, notify_mock, steps_mock,
+ manual=False):
+ status_mock.return_value = [{
+ 'command_status': 'CLEAN_VERSION_MISMATCH',
+ 'command_name': 'execute_clean_step',
+ }]
+ tgt_prov_state = states.MANAGEABLE if manual else states.AVAILABLE
+ self.node.provision_state = states.CLEANWAIT
+ self.node.target_provision_state = tgt_prov_state
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ notify_mock.assert_called_once_with(task)
+ refresh_steps_mock.assert_called_once_with(mock.ANY, task)
+ if manual:
+ self.assertFalse(
+ task.node.driver_internal_info['skip_current_clean_step'])
+ self.assertFalse(steps_mock.called)
+ else:
+ steps_mock.assert_called_once_with(task)
+ self.assertNotIn('skip_current_clean_step',
+ task.node.driver_internal_info)
+
+ def test_continue_cleaning_automated_clean_version_mismatch(self):
+ self._test_continue_cleaning_clean_version_mismatch()
+
+ def test_continue_cleaning_manual_clean_version_mismatch(self):
+ self._test_continue_cleaning_clean_version_mismatch(manual=True)
+
+ @mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
+ @mock.patch.object(conductor_steps, 'set_node_cleaning_steps',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
+ autospec=True)
+ @mock.patch.object(agent_base.AgentDeployMixin,
+ 'refresh_clean_steps', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_clean_version_mismatch_fail(
+ self, status_mock, refresh_steps_mock, notify_mock, steps_mock,
+ error_mock, manual=False):
+ status_mock.return_value = [{
+ 'command_status': 'CLEAN_VERSION_MISMATCH',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {'hardware_manager_version': {'Generic': '1'}}
+ }]
+ refresh_steps_mock.side_effect = exception.NodeCleaningFailure("boo")
+ tgt_prov_state = states.MANAGEABLE if manual else states.AVAILABLE
+ self.node.provision_state = states.CLEANWAIT
+ self.node.target_provision_state = tgt_prov_state
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+
+ status_mock.assert_called_once_with(mock.ANY, task.node)
+ refresh_steps_mock.assert_called_once_with(mock.ANY, task)
+ error_mock.assert_called_once_with(task, mock.ANY)
+ self.assertFalse(notify_mock.called)
+ self.assertFalse(steps_mock.called)
+
+ @mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
+ @mock.patch.object(agent_client.AgentClient, 'get_commands_status',
+ autospec=True)
+ def test_continue_cleaning_unknown(self, status_mock, error_mock):
+ # Test that unknown commands are treated as failures
+ status_mock.return_value = [{
+ 'command_status': 'UNKNOWN',
+ 'command_name': 'execute_clean_step',
+ 'command_result': {}
+ }]
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.deploy.continue_cleaning(task)
+ error_mock.assert_called_once_with(task, mock.ANY)
+
+ def _test_clean_step_hook(self, hook_dict_mock):
+ """Helper method for unit tests related to clean step hooks.
+
+ This is a helper method for other unit tests related to
+ clean step hooks. It acceps a mock 'hook_dict_mock' which is
+ a MagicMock and sets it up to function as a mock dictionary.
+ After that, it defines a dummy hook_method for two clean steps
+ raid.create_configuration and raid.delete_configuration.
+
+ :param hook_dict_mock: An instance of mock.MagicMock() which
+ is the mocked value of agent_base.POST_CLEAN_STEP_HOOKS
+ :returns: a tuple, where the first item is the hook method created
+ by this method and second item is the backend dictionary for
+ the mocked hook_dict_mock
+ """
+ hook_dict = {}
+
+ def get(key, default):
+ return hook_dict.get(key, default)
+
+ def getitem(self, key):
+ return hook_dict[key]
+
+ def setdefault(key, default):
+ if key not in hook_dict:
+ hook_dict[key] = default
+ return hook_dict[key]
+
+ hook_dict_mock.get = get
+ hook_dict_mock.__getitem__ = getitem
+ hook_dict_mock.setdefault = setdefault
+ some_function_mock = mock.MagicMock()
+
+ @agent_base.post_clean_step_hook(
+ interface='raid', step='delete_configuration')
+ @agent_base.post_clean_step_hook(
+ interface='raid', step='create_configuration')
+ def hook_method():
+ some_function_mock('some-arguments')
+
+ return hook_method, hook_dict
+
+ @mock.patch.object(agent_base, 'POST_CLEAN_STEP_HOOKS',
+ spec_set=dict)
+ def test_post_clean_step_hook(self, hook_dict_mock):
+ # This unit test makes sure that hook methods are registered
+ # properly and entries are made in
+ # agent_base.POST_CLEAN_STEP_HOOKS
+ hook_method, hook_dict = self._test_clean_step_hook(hook_dict_mock)
+ self.assertEqual(hook_method,
+ hook_dict['raid']['create_configuration'])
+ self.assertEqual(hook_method,
+ hook_dict['raid']['delete_configuration'])
+
+ @mock.patch.object(agent_base, 'POST_CLEAN_STEP_HOOKS',
+ spec_set=dict)
+ def test__get_post_clean_step_hook(self, hook_dict_mock):
+ # Check if agent_base._get_post_clean_step_hook can get
+ # clean step for which hook is registered.
+ hook_method, hook_dict = self._test_clean_step_hook(hook_dict_mock)
+ self.node.clean_step = {'step': 'create_configuration',
+ 'interface': 'raid'}
+ self.node.save()
+ hook_returned = agent_base._get_post_clean_step_hook(self.node)
+ self.assertEqual(hook_method, hook_returned)
+
+ @mock.patch.object(agent_base, 'POST_CLEAN_STEP_HOOKS',
+ spec_set=dict)
+ def test__get_post_clean_step_hook_no_hook_registered(
+ self, hook_dict_mock):
+ # Make sure agent_base._get_post_clean_step_hook returns
+ # None when no clean step hook is registered for the clean step.
+ hook_method, hook_dict = self._test_clean_step_hook(hook_dict_mock)
+ self.node.clean_step = {'step': 'some-clean-step',
+ 'interface': 'some-other-interface'}
+ self.node.save()
+ hook_returned = agent_base._get_post_clean_step_hook(self.node)
+ self.assertIsNone(hook_returned)
+
+ @mock.patch.object(manager_utils, 'restore_power_state_if_needed',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'power_on_node_if_needed')
+ @mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
+ 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_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ def test_reboot_and_finish_deploy_with_smartnic_port(
+ self, power_off_mock, get_power_state_mock,
+ node_power_action_mock, collect_mock, resume_mock,
+ power_on_node_if_needed_mock, restore_power_state_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.deploy_step = {
+ 'step': 'deploy', 'priority': 50, 'interface': 'deploy'}
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ get_power_state_mock.side_effect = [states.POWER_ON,
+ states.POWER_OFF]
+ power_on_node_if_needed_mock.return_value = states.POWER_OFF
+ self.deploy.reboot_and_finish_deploy(task)
+ power_off_mock.assert_called_once_with(task.node)
+ self.assertEqual(2, get_power_state_mock.call_count)
+ node_power_action_mock.assert_called_once_with(
+ task, states.POWER_ON)
+ 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)
+ resume_mock.assert_called_once_with(task)
+ power_on_node_if_needed_mock.assert_called_once_with(task)
+ restore_power_state_mock.assert_called_once_with(
+ task, states.POWER_OFF)
+
+
+class TestRefreshCleanSteps(AgentDeployMixinBaseTest):
+
+ def setUp(self):
+ super(TestRefreshCleanSteps, self).setUp()
+ self.node.driver_internal_info['agent_url'] = 'http://127.0.0.1:9999'
+ self.ports = [object_utils.create_test_port(self.context,
+ node_id=self.node.id)]
+
+ self.clean_steps = {
+ 'hardware_manager_version': '1',
+ 'clean_steps': {
+ 'GenericHardwareManager': [
+ {'interface': 'deploy',
+ 'step': 'erase_devices',
+ 'priority': 20},
+ ],
+ 'SpecificHardwareManager': [
+ {'interface': 'deploy',
+ 'step': 'update_firmware',
+ 'priority': 30},
+ {'interface': 'raid',
+ 'step': 'create_configuration',
+ 'priority': 10},
+ ]
+ }
+ }
+
+ @mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
+ autospec=True)
+ def test_refresh_clean_steps(self, client_mock):
+ client_mock.return_value = {
+ 'command_result': self.clean_steps}
+
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.deploy.refresh_clean_steps(task)
+
+ client_mock.assert_called_once_with(mock.ANY, task.node,
+ task.ports)
+ self.assertEqual('1', task.node.driver_internal_info[
+ 'hardware_manager_version'])
+ self.assertIn('agent_cached_clean_steps_refreshed',
+ task.node.driver_internal_info)
+ steps = task.node.driver_internal_info['agent_cached_clean_steps']
+ # Since steps are returned in dicts, they have non-deterministic
+ # ordering
+ self.assertEqual(2, len(steps))
+ self.assertIn(self.clean_steps['clean_steps'][
+ 'GenericHardwareManager'][0], steps['deploy'])
+ self.assertIn(self.clean_steps['clean_steps'][
+ 'SpecificHardwareManager'][0], steps['deploy'])
+ self.assertEqual([self.clean_steps['clean_steps'][
+ 'SpecificHardwareManager'][1]], steps['raid'])
+
+ @mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
+ autospec=True)
+ def test_refresh_clean_steps_missing_steps(self, client_mock):
+ del self.clean_steps['clean_steps']
+ client_mock.return_value = {
+ 'command_result': self.clean_steps}
+
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.assertRaisesRegex(exception.NodeCleaningFailure,
+ 'invalid result',
+ self.deploy.refresh_clean_steps,
+ task)
+ client_mock.assert_called_once_with(mock.ANY, task.node,
+ task.ports)
+
+ @mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
+ autospec=True)
+ def test_refresh_clean_steps_missing_interface(self, client_mock):
+ step = self.clean_steps['clean_steps']['SpecificHardwareManager'][1]
+ del step['interface']
+ client_mock.return_value = {
+ 'command_result': self.clean_steps}
+
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+ self.assertRaisesRegex(exception.NodeCleaningFailure,
+ 'invalid clean step',
+ self.deploy.refresh_clean_steps,
+ task)
+ client_mock.assert_called_once_with(mock.ANY, task.node,
+ task.ports)