Merge "agent_base: support inserting in-band deploy steps"

This commit is contained in:
Zuul 2020-07-09 12:42:05 +00:00 committed by Gerrit Code Review
commit c42656ea57
7 changed files with 300 additions and 386 deletions

View File

@ -40,15 +40,35 @@ BIOS, and RAID interfaces.
.. _node-deployment-core-steps:
Core steps
----------
Agent steps
-----------
Certain default deploy steps are designated as 'core' deploy steps. The
following deploy steps are core:
All deploy interfaces based on ironic-python-agent (i.e. ``direct``, ``iscsi``
and ``ansible`` and any derivatives) expose the following deploy steps:
``deploy.deploy``
``deploy.deploy`` (priority 100)
In this step the node is booted using a provisioning image, and the user
image is written to the node's disk. It has a priority of 100.
image is written to the node's disk.
``deploy.tear_down_agent`` (priority 40)
In this step the provisioning image is shut down.
``deploy.switch_to_tenant_network`` (priority 30)
In this step networking for the node is switched from provisioning to
tenant networks.
``deploy.boot_instance`` (priority 20)
In this step the node is booted into the user image.
Accordingly, the following priority ranges can be used for custom deploy steps:
> 100
Out-of-band steps to run before deployment.
41 to 59
In-band steps to run after the image is written the bootloader is installed.
21 to 39
Out-of-band steps to run after the provisioning image is shut down.
1 to 19
Any steps that are run when the user instance is already running.
.. note:: Priorities 60 to 99 are currently reserved and should not be used.
Writing a Deploy Step
---------------------

View File

@ -268,6 +268,13 @@ def do_next_deploy_step(task, step_index, conductor_id):
_("Failed to deploy. Exception: %s") % e, traceback=True)
return
if task.node.provision_state == states.DEPLOYFAIL:
# NOTE(dtantsur): some deploy steps do not raise but rather update
# the node and return. Take them into account.
LOG.debug('Node %s is in error state, not processing '
'the remaining deploy steps', task.node)
return
if ind == 0:
# We've done the very first deploy step.
# Update conductor_affinity to reference this conductor's ID

View File

@ -376,7 +376,6 @@ class AgentDeployMixin(agent_base.AgentDeployMixin):
if CONF.agent.image_download_source == 'http':
deploy_utils.remove_http_instance_symlink(task.node.uuid)
LOG.debug('Rebooting node %s to instance', node.uuid)
self.reboot_and_finish_deploy(task)

View File

@ -35,6 +35,7 @@ from ironic.conductor import steps as conductor_steps
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import agent_client
from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_utils
@ -382,8 +383,21 @@ def _step_failure_handler(task, msg, step_type):
class HeartbeatMixin(object):
"""Mixin class implementing heartbeat processing."""
has_decomposed_deploy_steps = False
"""Whether the driver supports decomposed deploy steps.
Previously (since Rocky), drivers used a single 'deploy' deploy step on
the deploy interface. Some additional steps were added for the 'direct'
and 'iscsi' deploy interfaces in the Ussuri cycle, which means that
more of the deployment flow is driven by deploy steps.
"""
def __init__(self):
self._client = _get_client()
if not self.has_decomposed_deploy_steps:
LOG.warning('%s does not support decomposed deploy steps. This '
'is deprecated and will stop working in a future '
'release', self.__class__.__name__)
def continue_deploy(self, task):
"""Continues the deployment of baremetal node.
@ -501,8 +515,12 @@ class HeartbeatMixin(object):
# are currently in the core deploy.deploy step. Other deploy steps
# may cause the agent to boot, but we should not trigger deployment
# at that point if the driver is polling for completion of a step.
if self.in_core_deploy_step(task):
if (not self.has_decomposed_deploy_steps
and self.in_core_deploy_step(task)):
msg = _('Failed checking if deploy is done')
# NOTE(mgoddard): support backwards compatibility for
# drivers which do not implement continue_deploy and
# reboot_to_instance as deploy steps.
if not self.deploy_has_started(task):
msg = _('Node failed to deploy')
self.continue_deploy(task)
@ -1067,16 +1085,13 @@ class AgentDeployMixin(HeartbeatMixin):
LOG.error(msg)
return _step_failure_handler(task, msg, step_type)
@METRICS.timer('AgentDeployMixin.reboot_and_finish_deploy')
def reboot_and_finish_deploy(self, task):
"""Helper method to trigger reboot on the node and finish deploy.
This method initiates a reboot on the node. On success, it
marks the deploy as complete. On failure, it logs the error
and marks deploy as failure.
@METRICS.timer('AgentDeployMixin.tear_down_agent')
@base.deploy_step(priority=40)
@task_manager.require_exclusive_lock
def tear_down_agent(self, task):
"""A deploy step to tear down the agent.
:param task: a TaskManager object containing the node
:raises: InstanceDeployFailure, if node reboot failed.
"""
wait = CONF.agent.post_deploy_get_power_state_retry_interval * 1000
attempts = CONF.agent.post_deploy_get_power_state_retries + 1
@ -1145,23 +1160,63 @@ class AgentDeployMixin(HeartbeatMixin):
'error': e})
log_and_raise_deployment_error(task, msg, exc=e)
@METRICS.timer('AgentDeployMixin.switch_networking')
@base.deploy_step(priority=30)
@task_manager.require_exclusive_lock
def switch_to_tenant_network(self, task):
"""Deploy step to switch the node to the tenant network.
:param task: a TaskManager object containing the node
"""
try:
with manager_utils.power_state_for_network_configuration(task):
task.driver.network.remove_provisioning_network(task)
task.driver.network.configure_tenant_networks(task)
manager_utils.node_power_action(task, states.POWER_ON)
except Exception as e:
msg = (_('Error rebooting node %(node)s after deploy. '
'%(cls)s: %(error)s') %
{'node': node.uuid, 'cls': e.__class__.__name__,
msg = (_('Error changing node %(node)s to tenant networks after '
'deploy. %(cls)s: %(error)s') %
{'node': task.node.uuid, 'cls': e.__class__.__name__,
'error': e})
# NOTE(mgoddard): Don't collect logs since the node has been
# powered off.
log_and_raise_deployment_error(task, msg, collect_logs=False,
exc=e)
# TODO(dtantsur): remove these two calls when this function becomes a
# real deploy step.
@METRICS.timer('AgentDeployMixin.boot_instance')
@base.deploy_step(priority=20)
@task_manager.require_exclusive_lock
def boot_instance(self, task):
"""Deploy step to boot the final instance.
:param task: a TaskManager object containing the node
"""
try:
manager_utils.node_power_action(task, states.POWER_ON)
except Exception as e:
msg = (_('Error booting node %(node)s after deploy. '
'%(cls)s: %(error)s') %
{'node': task.node.uuid, 'cls': e.__class__.__name__,
'error': e})
# NOTE(mgoddard): Don't collect logs since the node has been
# powered off.
log_and_raise_deployment_error(task, msg, collect_logs=False,
exc=e)
# TODO(dtantsur): remove in W
@METRICS.timer('AgentDeployMixin.reboot_and_finish_deploy')
def reboot_and_finish_deploy(self, task):
"""Helper method to trigger reboot on the node and finish deploy.
This method initiates a reboot on the node. On success, it
marks the deploy as complete. On failure, it logs the error
and marks deploy as failure.
:param task: a TaskManager object containing the node
:raises: InstanceDeployFailure, if node reboot failed.
"""
# NOTE(dtantsur): do nothing here, the new deploy steps tear_down_agent
# and boot_instance will be picked up and finish the deploy (even for
# legacy deploy interfaces without decomposed steps).
task.process_event('wait')
manager_utils.notify_conductor_resume_deploy(task)

View File

@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import types
from unittest import mock
from oslo_config import cfg
@ -1275,27 +1274,18 @@ class TestAgentDeploy(db_base.DbTestCase):
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@mock.patch.object(manager_utils, 'power_on_node_if_needed',
autospec=True)
@mock.patch.object(deploy_utils, 'remove_http_instance_symlink',
autospec=True)
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_partition_uuids',
autospec=True)
@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.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance(self, check_deploy_mock,
prepare_instance_mock, power_off_mock,
get_power_state_mock, node_power_action_mock,
def test_reboot_to_instance(self, check_deploy_mock, prepare_instance_mock,
uuid_mock, log_mock, remove_symlink_mock,
power_on_node_if_needed_mock, resume_mock):
resume_mock):
self.config(manage_agent_boot=True, group='agent')
self.config(image_download_source='http', group='agent')
check_deploy_mock.return_value = None
@ -1305,8 +1295,6 @@ class TestAgentDeploy(db_base.DbTestCase):
self.node.save()
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
get_power_state_mock.return_value = states.POWER_OFF
power_on_node_if_needed_mock.return_value = None
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.deploy.reboot_to_instance(task)
check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
@ -1316,10 +1304,6 @@ class TestAgentDeploy(db_base.DbTestCase):
self.assertFalse(log_mock.called)
prepare_instance_mock.assert_called_once_with(mock.ANY, task,
None, None, None)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.POWER_ON)
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
self.assertTrue(remove_symlink_mock.called)
@ -1327,26 +1311,17 @@ class TestAgentDeploy(db_base.DbTestCase):
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@mock.patch.object(manager_utils, 'power_on_node_if_needed',
autospec=True)
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_partition_uuids',
autospec=True)
@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.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance_no_manage_agent_boot(
self, check_deploy_mock, prepare_instance_mock, power_off_mock,
get_power_state_mock, node_power_action_mock, uuid_mock,
bootdev_mock, log_mock, power_on_node_if_needed_mock,
resume_mock):
self, check_deploy_mock, prepare_instance_mock, uuid_mock,
bootdev_mock, log_mock, resume_mock):
self.config(manage_agent_boot=False, group='agent')
check_deploy_mock.return_value = None
uuid_mock.return_value = {}
@ -1355,8 +1330,6 @@ class TestAgentDeploy(db_base.DbTestCase):
self.node.save()
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
power_on_node_if_needed_mock.return_value = None
get_power_state_mock.return_value = states.POWER_OFF
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.deploy.reboot_to_instance(task)
check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
@ -1366,40 +1339,25 @@ class TestAgentDeploy(db_base.DbTestCase):
self.assertFalse(log_mock.called)
self.assertFalse(prepare_instance_mock.called)
bootdev_mock.assert_called_once_with(task, 'disk', persistent=True)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.POWER_ON)
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
resume_mock.assert_called_once_with(task)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@mock.patch.object(manager_utils, 'power_on_node_if_needed',
autospec=True)
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_partition_uuids',
autospec=True)
@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.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance_partition_image(self, check_deploy_mock,
prepare_instance_mock,
power_off_mock,
get_power_state_mock,
node_power_action_mock,
uuid_mock, boot_mode_mock,
log_mock,
power_on_node_if_needed_mock,
resume_mock):
check_deploy_mock.return_value = None
self.node.instance_info = {
@ -1413,8 +1371,6 @@ class TestAgentDeploy(db_base.DbTestCase):
boot_mode_mock.return_value = 'bios'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
power_on_node_if_needed_mock.return_value = None
get_power_state_mock.return_value = states.POWER_OFF
driver_internal_info = task.node.driver_internal_info
driver_internal_info['is_whole_disk_image'] = False
task.node.driver_internal_info = driver_internal_info
@ -1430,18 +1386,12 @@ class TestAgentDeploy(db_base.DbTestCase):
task,
'root_uuid',
None, None)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.POWER_ON)
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
resume_mock.assert_called_once_with(task)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@mock.patch.object(manager_utils, 'power_on_node_if_needed',
autospec=True)
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
autospec=True)
@ -1449,20 +1399,13 @@ class TestAgentDeploy(db_base.DbTestCase):
autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_partition_uuids',
autospec=True)
@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.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance_partition_image_compat(
self, check_deploy_mock, prepare_instance_mock, power_off_mock,
get_power_state_mock, node_power_action_mock, uuid_mock,
old_uuid_mock, boot_mode_mock, log_mock,
power_on_node_if_needed_mock, resume_mock):
self, check_deploy_mock, prepare_instance_mock, uuid_mock,
old_uuid_mock, boot_mode_mock, log_mock, resume_mock):
check_deploy_mock.return_value = None
self.node.instance_info = {
'capabilities': {'boot_option': 'netboot'}}
@ -1474,8 +1417,6 @@ class TestAgentDeploy(db_base.DbTestCase):
boot_mode_mock.return_value = 'bios'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
power_on_node_if_needed_mock.return_value = None
get_power_state_mock.return_value = states.POWER_OFF
driver_internal_info = task.node.driver_internal_info
driver_internal_info['is_whole_disk_image'] = False
task.node.driver_internal_info = driver_internal_info
@ -1492,37 +1433,24 @@ class TestAgentDeploy(db_base.DbTestCase):
task,
'root_uuid',
None, None)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.POWER_ON)
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
resume_mock.assert_called_once_with(task)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@mock.patch.object(manager_utils, 'power_on_node_if_needed',
autospec=True)
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_partition_uuids',
autospec=True)
@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.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance_partition_localboot_ppc64(
self, check_deploy_mock, prepare_instance_mock,
power_off_mock, get_power_state_mock,
node_power_action_mock, uuid_mock, boot_mode_mock, log_mock,
power_on_node_if_needed_mock, resume_mock):
uuid_mock, boot_mode_mock, log_mock, resume_mock):
check_deploy_mock.return_value = None
uuid_mock.return_value = {
'command_result': {
@ -1536,8 +1464,6 @@ class TestAgentDeploy(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
power_on_node_if_needed_mock.return_value = None
get_power_state_mock.return_value = states.POWER_OFF
driver_internal_info = task.node.driver_internal_info
driver_internal_info['is_whole_disk_image'] = False
task.node.driver_internal_info = driver_internal_info
@ -1558,10 +1484,6 @@ class TestAgentDeploy(db_base.DbTestCase):
self.assertFalse(log_mock.called)
prepare_instance_mock.assert_called_once_with(
mock.ANY, task, 'root_uuid', None, 'prep_boot_part_uuid')
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.POWER_ON)
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
@ -1569,18 +1491,12 @@ class TestAgentDeploy(db_base.DbTestCase):
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_partition_uuids',
autospec=True)
@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.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance_boot_error(
self, check_deploy_mock, prepare_instance_mock,
power_off_mock, get_power_state_mock, node_power_action_mock,
uuid_mock, collect_ramdisk_logs_mock, log_mock):
check_deploy_mock.return_value = "Error"
uuid_mock.return_value = None
@ -1589,43 +1505,30 @@ class TestAgentDeploy(db_base.DbTestCase):
self.node.save()
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
get_power_state_mock.return_value = states.POWER_OFF
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.deploy.reboot_to_instance(task)
check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
self.assertFalse(prepare_instance_mock.called)
self.assertFalse(log_mock.called)
self.assertFalse(power_off_mock.called)
collect_ramdisk_logs_mock.assert_called_once_with(task.node)
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@mock.patch.object(manager_utils, 'power_on_node_if_needed',
autospec=True)
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_partition_uuids',
autospec=True)
@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.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance_localboot(self, check_deploy_mock,
prepare_instance_mock,
power_off_mock,
get_power_state_mock,
node_power_action_mock,
uuid_mock, boot_mode_mock,
log_mock,
power_on_node_if_needed_mock,
resume_mock):
check_deploy_mock.return_value = None
uuid_mock.return_value = {
@ -1640,8 +1543,6 @@ class TestAgentDeploy(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
power_on_node_if_needed_mock.return_value = None
get_power_state_mock.return_value = states.POWER_OFF
driver_internal_info = task.node.driver_internal_info
driver_internal_info['is_whole_disk_image'] = False
task.node.driver_internal_info = driver_internal_info
@ -1659,10 +1560,6 @@ class TestAgentDeploy(db_base.DbTestCase):
self.assertFalse(log_mock.called)
prepare_instance_mock.assert_called_once_with(
mock.ANY, task, 'root_uuid', 'efi_uuid', None)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.POWER_ON)
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
resume_mock.assert_called_once_with(task)

View File

@ -171,6 +171,7 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.deploy.heartbeat(task, 'url', '3.2.0')
self.assertIsNone(task.node.last_error)
self.assertFalse(task.shared)
self.assertEqual(
'url', task.node.driver_internal_info['agent_url'])
@ -304,6 +305,44 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
self.assertFalse(rti_mock.called)
self.assertFalse(in_resume_deploy_mock.called)
@mock.patch.object(agent_base.HeartbeatMixin, 'process_next_step',
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_decomposed_steps(self, rti_mock, cd_mock,
deploy_is_done_mock,
deploy_started_mock,
in_deploy_mock,
next_step_mock):
self.deploy.has_decomposed_deploy_steps = True
# Check that heartbeats do not trigger deployment actions when the
# driver has decomposed deploy steps.
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(in_deploy_mock.called)
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(next_step_mock.called)
@mock.patch.object(agent_base.HeartbeatMixin, 'continue_deploy',
autospec=True)
@mock.patch.object(agent_base.HeartbeatMixin,
@ -849,8 +888,6 @@ class AgentRescueTests(AgentDeployMixinBaseTest):
class AgentDeployMixinTest(AgentDeployMixinBaseTest):
@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)
@ -858,34 +895,27 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
def test_reboot_and_finish_deploy(
def test_tear_down_agent(
self, power_off_mock, get_power_state_mock,
node_power_action_mock, collect_mock, resume_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,
shared=True) as task:
with task_manager.acquire(self.context, self.node.uuid) 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)
self.deploy.tear_down_agent(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.DEPLOYWAIT, task.node.provision_state)
self.assertFalse(node_power_action_mock.called)
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)
@ -893,38 +923,23 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
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, resume_mock,
power_on_node_if_needed_mock):
def test_tear_down_agent_soft_poweroff_doesnt_complete(
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:
power_on_node_if_needed_mock.return_value = None
with task_manager.acquire(self.context, self.node.uuid) as task:
get_power_state_mock.return_value = states.POWER_ON
self.deploy.reboot_and_finish_deploy(task)
self.deploy.tear_down_agent(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.DEPLOYWAIT, task.node.provision_state)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
self.assertEqual(states.DEPLOYING, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
self.assertFalse(mock_collect.called)
resume_mock.assert_called_once_with(task)
@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)
@ -932,35 +947,23 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
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_fails(
self, configure_tenant_net_mock, remove_provisioning_net_mock,
power_off_mock, get_power_state_mock, node_power_action_mock,
mock_collect, resume_mock):
def test_tear_down_agent_soft_poweroff_fails(
self, power_off_mock, get_power_state_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:
with task_manager.acquire(self.context, self.node.uuid) as task:
get_power_state_mock.return_value = states.POWER_ON
self.deploy.reboot_and_finish_deploy(task)
self.deploy.tear_down_agent(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.DEPLOYWAIT, task.node.provision_state)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
self.assertEqual(states.DEPLOYING, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
self.assertFalse(mock_collect.called)
@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)
@ -968,38 +971,26 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
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_race(
self, configure_tenant_net_mock, remove_provisioning_net_mock,
power_off_mock, get_power_state_mock, node_power_action_mock,
mock_collect, resume_mock):
def test_tear_down_agent_soft_poweroff_race(
self, power_off_mock, get_power_state_mock, node_power_action_mock,
mock_collect):
# Test the situation when soft power off works, but ironic doesn't
# learn about it.
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:
with task_manager.acquire(self.context, self.node.uuid) as task:
get_power_state_mock.side_effect = [states.POWER_ON,
states.POWER_OFF]
self.deploy.reboot_and_finish_deploy(task)
self.deploy.tear_down_agent(task)
power_off_mock.assert_called_once_with(task.node)
node_power_action_mock.assert_called_once_with(
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.DEPLOYWAIT, task.node.provision_state)
self.assertFalse(node_power_action_mock.called)
self.assertEqual(states.DEPLOYING, task.node.provision_state)
self.assertEqual(states.ACTIVE, 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(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)
@ -1007,67 +998,21 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
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, resume_mock, power_on_node_if_needed_mock):
def test_tear_down_agent_get_power_state_fails(
self, 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:
with task_manager.acquire(self.context, self.node.uuid) 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)
self.deploy.tear_down_agent(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.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, 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)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
self.assertEqual(states.DEPLOYING, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
self.assertFalse(mock_collect.called)
@ -1078,78 +1023,31 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
def test_reboot_and_finish_deploy_power_off_fails(
def test_tear_down_agent_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:
with task_manager.acquire(self.context, self.node.uuid) 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,
self.deploy.tear_down_agent,
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)])
node_power_action_mock.assert_called_with(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(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@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,
resume_mock):
def test_tear_down_agent_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
@ -1158,30 +1056,23 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
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)
with task_manager.acquire(self.context, self.node.uuid) as task:
self.deploy.tear_down_agent(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.DEPLOYWAIT, task.node.provision_state)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
self.assertEqual(states.DEPLOYING, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
self.assertFalse(mock_collect.called)
resume_mock.assert_called_once_with(task)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
@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,
resume_mock):
def test_tear_down_agent_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
@ -1190,17 +1081,16 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
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:
with task_manager.acquire(self.context, self.node.uuid) as task:
log_mock.reset_mock()
sync_mock.return_value = {'faultstring': 'Unknown command: blah'}
self.deploy.reboot_and_finish_deploy(task)
self.deploy.tear_down_agent(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.DEPLOYWAIT, task.node.provision_state)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
self.assertEqual(states.DEPLOYING, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
log_error = ('The version of the IPA ramdisk used in the '
'deployment do not support the command "sync"')
@ -1210,6 +1100,51 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
{'node': task.node.uuid, 'error': log_error})
self.assertFalse(mock_collect.called)
@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('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_switch_to_tenant_network(self, configure_tenant_net_mock,
remove_provisioning_net_mock,
power_on_node_if_needed_mock,
restore_power_state_mock):
power_on_node_if_needed_mock.return_value = states.POWER_OFF
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.switch_to_tenant_network(task)
remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
task)
configure_tenant_net_mock.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)
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
@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_switch_to_tenant_network_fails(self, configure_tenant_net_mock,
remove_provisioning_net_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) as task:
configure_tenant_net_mock.side_effect = exception.NetworkError(
"boom")
self.assertRaises(exception.InstanceDeployFailure,
self.deploy.switch_to_tenant_network, task)
remove_provisioning_net_mock.assert_called_once_with(mock.ANY,
task)
configure_tenant_net_mock.assert_called_once_with(mock.ANY, task)
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)
@ -2107,46 +2042,6 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
hook_returned = agent_base._get_post_step_hook(self.node, 'clean')
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', 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_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.DEPLOYWAIT, 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):
@ -2244,6 +2139,7 @@ class TestRefreshCleanSteps(AgentDeployMixinBaseTest):
with task_manager.acquire(
self.context, self.node.uuid, shared=False) as task:
log_mock.reset_mock()
self.deploy.refresh_steps(task, 'deploy')
client_mock.assert_called_once_with(mock.ANY, task.node,
@ -2252,7 +2148,7 @@ class TestRefreshCleanSteps(AgentDeployMixinBaseTest):
task.node.driver_internal_info)
self.assertIsNone(task.node.driver_internal_info.get(
'agent_cached_deploy_steps'))
self.assertFalse(log_mock.called)
log_mock.assert_not_called()
@mock.patch.object(agent_client.AgentClient, 'get_clean_steps',
autospec=True)
@ -2390,18 +2286,35 @@ class StepMethodsTestCase(db_base.DbTestCase):
'agent_cached_deploy_steps': self.clean_steps
}
steps = self.deploy.get_deploy_steps(task)
# 2 in-band steps + one out-of-band
self.assertEqual(3, len(steps))
self.assertIn(self.clean_steps['deploy'][0], steps)
self.assertIn(self.clean_steps['deploy'][1], steps)
self.assertNotIn(self.clean_steps['raid'][0], steps)
# 2 in-band steps + 3 out-of-band
expected = [
{'step': 'deploy', 'priority': 100, 'argsinfo': None,
'interface': 'deploy'},
{'step': 'tear_down_agent', 'priority': 40, 'argsinfo': None,
'interface': 'deploy'},
{'step': 'switch_to_tenant_network', 'priority': 30,
'argsinfo': None, 'interface': 'deploy'},
{'step': 'boot_instance', 'priority': 20, 'argsinfo': None,
'interface': 'deploy'},
] + self.clean_steps['deploy']
self.assertCountEqual(expected, steps)
def test_get_deploy_steps_only_oob(self):
with task_manager.acquire(
self.context, self.node.uuid, shared=False) as task:
steps = self.deploy.get_deploy_steps(task)
# one out-of-band step
self.assertEqual(1, len(steps))
# three base out-of-band steps
expected = [
{'step': 'deploy', 'priority': 100, 'argsinfo': None,
'interface': 'deploy'},
{'step': 'tear_down_agent', 'priority': 40, 'argsinfo': None,
'interface': 'deploy'},
{'step': 'switch_to_tenant_network', 'priority': 30,
'argsinfo': None, 'interface': 'deploy'},
{'step': 'boot_instance', 'priority': 20, 'argsinfo': None,
'interface': 'deploy'},
]
self.assertCountEqual(expected, steps)
@mock.patch('ironic.objects.Port.list_by_node_id',
spec_set=types.FunctionType)

View File

@ -0,0 +1,23 @@
---
features:
- |
Adds support for running custom in-band deploy steps when provisioning.
Step priorities from 41 to 59 can be used for steps that run after
the image is written and the bootloader is installed.
deprecations:
- |
Running the whole deployment process as a monolithic ``deploy.deploy``
deploy step is now deprecated. In a future release this step will only be
used to prepare deployment and starting the agent, and special handling
will be removed. All third party deploy interfaces must be updated
to provide real deploy steps instead and set the
``has_decomposed_deploy_steps`` attribute to ``True`` on the deploy
interface level.
other:
- |
As part of the agent deploy interfaces refactoring, breaking changes will
be made to implementations of ``AgentDeploy`` and ``ISCSIDeploy``.
Third party deploy interfaces must be updated to inherit
``HeartbeatMixin``, ``AgentBaseMixin`` or ``AgentDeployMixin``
from ``ironic.drivers.modules.agent_base`` instead since their API is
considered more stable.