Merge "Support executing in-band deploy steps"
This commit is contained in:
commit
b083a02d1c
@ -88,6 +88,7 @@ def start_deploy(task, manager, configdrive=None, event='deploy'):
|
|||||||
node.instance_info = instance_info
|
node.instance_info = instance_info
|
||||||
|
|
||||||
driver_internal_info = node.driver_internal_info
|
driver_internal_info = node.driver_internal_info
|
||||||
|
driver_internal_info.pop('steps_validated', None)
|
||||||
# Infer the image type to make sure the deploy driver
|
# Infer the image type to make sure the deploy driver
|
||||||
# validates only the necessary variables for different
|
# validates only the necessary variables for different
|
||||||
# image types.
|
# image types.
|
||||||
@ -185,8 +186,9 @@ def do_node_deploy(task, conductor_id=None, configdrive=None):
|
|||||||
traceback=True, clean_up=False)
|
traceback=True, clean_up=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# This gets the deploy steps and puts them in the node's
|
# This gets the deploy steps (if any) and puts them in the node's
|
||||||
# driver_internal_info['deploy_steps'].
|
# driver_internal_info['deploy_steps']. In-band steps are skipped since
|
||||||
|
# we know that an agent is not running yet.
|
||||||
conductor_steps.set_node_deployment_steps(task)
|
conductor_steps.set_node_deployment_steps(task)
|
||||||
except exception.InstanceDeployFailure as e:
|
except exception.InstanceDeployFailure as e:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
@ -313,6 +315,7 @@ def do_next_deploy_step(task, step_index, conductor_id):
|
|||||||
driver_internal_info.pop('deploy_step_index', None)
|
driver_internal_info.pop('deploy_step_index', None)
|
||||||
driver_internal_info.pop('deployment_reboot', None)
|
driver_internal_info.pop('deployment_reboot', None)
|
||||||
driver_internal_info.pop('deployment_polling', None)
|
driver_internal_info.pop('deployment_polling', None)
|
||||||
|
driver_internal_info.pop('steps_validated', None)
|
||||||
# Remove the agent_url cached from the deployment.
|
# Remove the agent_url cached from the deployment.
|
||||||
driver_internal_info.pop('agent_url', None)
|
driver_internal_info.pop('agent_url', None)
|
||||||
node.driver_internal_info = driver_internal_info
|
node.driver_internal_info = driver_internal_info
|
||||||
|
@ -872,6 +872,15 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||||||
|
|
||||||
save_required = False
|
save_required = False
|
||||||
info = node.driver_internal_info
|
info = node.driver_internal_info
|
||||||
|
|
||||||
|
# Agent is running, we're ready to validate the remaining steps
|
||||||
|
if not info.get('steps_validated'):
|
||||||
|
conductor_steps.validate_deploy_templates(task)
|
||||||
|
conductor_steps.set_node_deployment_steps(
|
||||||
|
task, reset_current=False)
|
||||||
|
info['steps_validated'] = True
|
||||||
|
save_required = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
skip_current_step = info.pop('skip_current_deploy_step')
|
skip_current_step = info.pop('skip_current_deploy_step')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -87,6 +87,11 @@ def is_equivalent(step1, step2):
|
|||||||
and step1.get('step') == step2.get('step'))
|
and step1.get('step') == step2.get('step'))
|
||||||
|
|
||||||
|
|
||||||
|
def find_step(steps, step):
|
||||||
|
"""Find an identical step in the list of steps."""
|
||||||
|
return next((x for x in steps if is_equivalent(x, step)), None)
|
||||||
|
|
||||||
|
|
||||||
def _get_steps(task, interfaces, get_method, enabled=False,
|
def _get_steps(task, interfaces, get_method, enabled=False,
|
||||||
sort_step_key=None):
|
sort_step_key=None):
|
||||||
"""Get steps for task.node.
|
"""Get steps for task.node.
|
||||||
@ -299,19 +304,21 @@ def _get_all_deployment_steps(task):
|
|||||||
return _sorted_steps(steps, _deploy_step_key)
|
return _sorted_steps(steps, _deploy_step_key)
|
||||||
|
|
||||||
|
|
||||||
def set_node_deployment_steps(task):
|
def set_node_deployment_steps(task, reset_current=True):
|
||||||
"""Set up the node with deployment step information for deploying.
|
"""Set up the node with deployment step information for deploying.
|
||||||
|
|
||||||
Get the deploy steps from the driver.
|
Get the deploy steps from the driver.
|
||||||
|
|
||||||
|
:param reset_current: Whether to reset the current step to the first one.
|
||||||
:raises: InstanceDeployFailure if there was a problem getting the
|
:raises: InstanceDeployFailure if there was a problem getting the
|
||||||
deployment steps.
|
deployment steps.
|
||||||
"""
|
"""
|
||||||
node = task.node
|
node = task.node
|
||||||
driver_internal_info = node.driver_internal_info
|
driver_internal_info = node.driver_internal_info
|
||||||
driver_internal_info['deploy_steps'] = _get_all_deployment_steps(task)
|
driver_internal_info['deploy_steps'] = _get_all_deployment_steps(task)
|
||||||
node.deploy_step = {}
|
if reset_current:
|
||||||
driver_internal_info['deploy_step_index'] = None
|
node.deploy_step = {}
|
||||||
|
driver_internal_info['deploy_step_index'] = None
|
||||||
node.driver_internal_info = driver_internal_info
|
node.driver_internal_info = driver_internal_info
|
||||||
node.save()
|
node.save()
|
||||||
|
|
||||||
|
@ -477,14 +477,22 @@ class HeartbeatMixin(object):
|
|||||||
{'node': node.uuid})
|
{'node': node.uuid})
|
||||||
|
|
||||||
def _heartbeat_deploy_wait(self, task):
|
def _heartbeat_deploy_wait(self, task):
|
||||||
msg = _('Failed checking if deploy is done')
|
msg = _('Unexpected exception')
|
||||||
node = task.node
|
node = task.node
|
||||||
try:
|
try:
|
||||||
|
# NOTE(dtantsur): on first heartbeat, load in-band steps.
|
||||||
|
if not node.driver_internal_info.get('agent_cached_deploy_steps'):
|
||||||
|
msg = _('Failed to load in-band deploy steps')
|
||||||
|
# Refresh steps since this is the first time IPA has
|
||||||
|
# booted and we need to collect in-band steps.
|
||||||
|
self.refresh_steps(task, 'deploy')
|
||||||
|
|
||||||
# NOTE(mgoddard): Only handle heartbeats during DEPLOYWAIT if we
|
# NOTE(mgoddard): Only handle heartbeats during DEPLOYWAIT if we
|
||||||
# are currently in the core deploy.deploy step. Other deploy steps
|
# are currently in the core deploy.deploy step. Other deploy steps
|
||||||
# may cause the agent to boot, but we should not trigger deployment
|
# 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.
|
# at that point if the driver is polling for completion of a step.
|
||||||
if self.in_core_deploy_step(task):
|
if self.in_core_deploy_step(task):
|
||||||
|
msg = _('Failed checking if deploy is done')
|
||||||
if not self.deploy_has_started(task):
|
if not self.deploy_has_started(task):
|
||||||
msg = _('Node failed to deploy')
|
msg = _('Node failed to deploy')
|
||||||
self.continue_deploy(task)
|
self.continue_deploy(task)
|
||||||
@ -494,15 +502,14 @@ class HeartbeatMixin(object):
|
|||||||
else:
|
else:
|
||||||
node.touch_provisioning()
|
node.touch_provisioning()
|
||||||
else:
|
else:
|
||||||
# The exceptions from RPC are not possible as we using cast
|
node.touch_provisioning()
|
||||||
# here
|
# Check if the driver is polling for completion of
|
||||||
# Check if the driver is polling for completion of a step,
|
# a step, via the 'deployment_polling' flag.
|
||||||
# via the 'deployment_polling' flag.
|
|
||||||
polling = node.driver_internal_info.get(
|
polling = node.driver_internal_info.get(
|
||||||
'deployment_polling', False)
|
'deployment_polling', False)
|
||||||
if not polling:
|
if not polling:
|
||||||
manager_utils.notify_conductor_resume_deploy(task)
|
msg = _('Failed to process the next deploy step')
|
||||||
node.touch_provisioning()
|
self.process_next_step(task, 'deploy')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = _('%(msg)s. Error: %(exc)s') % {'msg': msg, 'exc': e}
|
last_error = _('%(msg)s. Error: %(exc)s') % {'msg': msg, 'exc': e}
|
||||||
LOG.exception('Asynchronous exception for node %(node)s: %(err)s',
|
LOG.exception('Asynchronous exception for node %(node)s: %(err)s',
|
||||||
@ -663,6 +670,25 @@ class AgentDeployMixin(HeartbeatMixin):
|
|||||||
task, 'clean', interface='deploy',
|
task, 'clean', interface='deploy',
|
||||||
override_priorities=new_priorities)
|
override_priorities=new_priorities)
|
||||||
|
|
||||||
|
@METRICS.timer('AgentDeployMixin.get_deploy_steps')
|
||||||
|
def get_deploy_steps(self, task):
|
||||||
|
"""Get the list of deploy steps from the agent.
|
||||||
|
|
||||||
|
:param task: a TaskManager object containing the node
|
||||||
|
:raises InstanceDeployFailure: if the deploy steps are not yet
|
||||||
|
available (cached), for example, when a node has just been
|
||||||
|
enrolled and has not been deployed yet.
|
||||||
|
:returns: A list of deploy step dictionaries
|
||||||
|
"""
|
||||||
|
steps = super(AgentDeployMixin, self).get_deploy_steps(task)[:]
|
||||||
|
ib_steps = get_steps(task, 'deploy', interface='deploy')
|
||||||
|
# NOTE(dtantsur): we allow in-band steps to be shadowed by out-of-band
|
||||||
|
# ones, see the docstring of execute_deploy_step for details.
|
||||||
|
steps += [step for step in ib_steps
|
||||||
|
# FIXME(dtantsur): nested loops are not too efficient
|
||||||
|
if not conductor_steps.find_step(steps, step)]
|
||||||
|
return steps
|
||||||
|
|
||||||
@METRICS.timer('AgentDeployMixin.refresh_steps')
|
@METRICS.timer('AgentDeployMixin.refresh_steps')
|
||||||
def refresh_steps(self, task, step_type):
|
def refresh_steps(self, task, step_type):
|
||||||
"""Refresh the node's cached clean/deploy steps from the booted agent.
|
"""Refresh the node's cached clean/deploy steps from the booted agent.
|
||||||
@ -687,7 +713,18 @@ class AgentDeployMixin(HeartbeatMixin):
|
|||||||
'steps': previous_steps})
|
'steps': previous_steps})
|
||||||
|
|
||||||
call = getattr(self._client, 'get_%s_steps' % step_type)
|
call = getattr(self._client, 'get_%s_steps' % step_type)
|
||||||
agent_result = call(node, task.ports).get('command_result', {})
|
try:
|
||||||
|
agent_result = call(node, task.ports).get('command_result', {})
|
||||||
|
except exception.AgentAPIError as exc:
|
||||||
|
if step_type == 'clean':
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
LOG.warning('Agent running on node %(node)s does not support '
|
||||||
|
'in-band deploy steps: %(err)s. Support for old '
|
||||||
|
'agents will be removed in the V release.',
|
||||||
|
{'node': node.uuid, 'err': exc})
|
||||||
|
return
|
||||||
|
|
||||||
missing = set(['%s_steps' % step_type,
|
missing = set(['%s_steps' % step_type,
|
||||||
'hardware_manager_version']).difference(agent_result)
|
'hardware_manager_version']).difference(agent_result)
|
||||||
if missing:
|
if missing:
|
||||||
@ -741,6 +778,37 @@ class AgentDeployMixin(HeartbeatMixin):
|
|||||||
"""
|
"""
|
||||||
return execute_step(task, step, 'clean')
|
return execute_step(task, step, 'clean')
|
||||||
|
|
||||||
|
@METRICS.timer('AgentDeployMixin.execute_deploy_step')
|
||||||
|
def execute_deploy_step(self, task, step):
|
||||||
|
"""Execute a deploy step.
|
||||||
|
|
||||||
|
We're trying to find a step among both out-of-band and in-band steps.
|
||||||
|
In case of duplicates, out-of-band steps take priority. This property
|
||||||
|
allows having an out-of-band deploy step that calls into
|
||||||
|
a corresponding in-band step after some preparation (e.g. with
|
||||||
|
additional input).
|
||||||
|
|
||||||
|
:param task: a TaskManager object containing the node
|
||||||
|
:param step: a deploy step dictionary to execute
|
||||||
|
:raises: NodeCleaningFailure if the agent does not return a command
|
||||||
|
status
|
||||||
|
:returns: states.DEPLOYWAIT to signify the step will be completed async
|
||||||
|
"""
|
||||||
|
agent_running = task.node.driver_internal_info.get(
|
||||||
|
'agent_cached_deploy_steps')
|
||||||
|
oob_steps = self.deploy_steps
|
||||||
|
|
||||||
|
if conductor_steps.find_step(oob_steps, step):
|
||||||
|
return super(AgentDeployMixin, self).execute_deploy_step(
|
||||||
|
task, step)
|
||||||
|
elif not agent_running:
|
||||||
|
raise exception.InstanceDeployFailure(
|
||||||
|
_('Deploy step %(step)s has not been found. Available '
|
||||||
|
'out-of-band steps: %(oob)s. Agent is not running.') %
|
||||||
|
{'step': step, 'oob': oob_steps})
|
||||||
|
else:
|
||||||
|
return execute_step(task, step, 'deploy')
|
||||||
|
|
||||||
def _process_version_mismatch(self, task, step_type):
|
def _process_version_mismatch(self, task, step_type):
|
||||||
node = task.node
|
node = task.node
|
||||||
# For manual clean, the target provision state is MANAGEABLE, whereas
|
# For manual clean, the target provision state is MANAGEABLE, whereas
|
||||||
@ -811,7 +879,7 @@ class AgentDeployMixin(HeartbeatMixin):
|
|||||||
process (the agent's get_clean|deploy_steps() call) and before
|
process (the agent's get_clean|deploy_steps() call) and before
|
||||||
executing each step. If the version has changed between steps,
|
executing each step. If the version has changed between steps,
|
||||||
the agent is unable to tell if an ordering change will cause an issue
|
the agent is unable to tell if an ordering change will cause an issue
|
||||||
so it returns CLEAN_VERSION_MISMATCH. For automated cleaning, we
|
so it returns VERSION_MISMATCH. For automated cleaning, we
|
||||||
restart the entire cleaning cycle. For manual cleaning or deploy,
|
restart the entire cleaning cycle. For manual cleaning or deploy,
|
||||||
we don't.
|
we don't.
|
||||||
|
|
||||||
@ -854,8 +922,10 @@ class AgentDeployMixin(HeartbeatMixin):
|
|||||||
'type': step_type})
|
'type': step_type})
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
return manager_utils.cleaning_error_handler(task, msg)
|
return manager_utils.cleaning_error_handler(task, msg)
|
||||||
|
# NOTE(dtantsur): VERSION_MISMATCH is a new alias for
|
||||||
|
# CLEAN_VERSION_MISMATCH, remove the old one after IPA removes it.
|
||||||
elif command.get('command_status') in ('CLEAN_VERSION_MISMATCH',
|
elif command.get('command_status') in ('CLEAN_VERSION_MISMATCH',
|
||||||
'DEPLOY_VERSION_MISMATCH'):
|
'VERSION_MISMATCH'):
|
||||||
self._process_version_mismatch(task, step_type)
|
self._process_version_mismatch(task, step_type)
|
||||||
elif command.get('command_status') == 'SUCCEEDED':
|
elif command.get('command_status') == 'SUCCEEDED':
|
||||||
step_hook = _get_post_step_hook(node, step_type)
|
step_hook = _get_post_step_hook(node, step_type)
|
||||||
|
@ -1663,6 +1663,8 @@ class ContinueNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
|
|||||||
'step': 'deploy_start', 'priority': 50, 'interface': 'deploy'}
|
'step': 'deploy_start', 'priority': 50, 'interface': 'deploy'}
|
||||||
self.deploy_end = {
|
self.deploy_end = {
|
||||||
'step': 'deploy_end', 'priority': 20, 'interface': 'deploy'}
|
'step': 'deploy_end', 'priority': 20, 'interface': 'deploy'}
|
||||||
|
self.in_band_step = {
|
||||||
|
'step': 'deploy_middle', 'priority': 30, 'interface': 'deploy'}
|
||||||
self.deploy_steps = [self.deploy_start, self.deploy_end]
|
self.deploy_steps = [self.deploy_start, self.deploy_end]
|
||||||
|
|
||||||
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
|
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
|
||||||
@ -1715,7 +1717,8 @@ class ContinueNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
|
|||||||
prv_state = states.DEPLOYWAIT
|
prv_state = states.DEPLOYWAIT
|
||||||
tgt_prv_state = states.ACTIVE
|
tgt_prv_state = states.ACTIVE
|
||||||
driver_info = {'deploy_steps': self.deploy_steps,
|
driver_info = {'deploy_steps': self.deploy_steps,
|
||||||
'deploy_step_index': 0}
|
'deploy_step_index': 0,
|
||||||
|
'steps_validated': True}
|
||||||
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
|
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
|
||||||
provision_state=prv_state,
|
provision_state=prv_state,
|
||||||
target_provision_state=tgt_prv_state,
|
target_provision_state=tgt_prv_state,
|
||||||
@ -1732,6 +1735,36 @@ class ContinueNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
|
|||||||
deployments.do_next_deploy_step,
|
deployments.do_next_deploy_step,
|
||||||
mock.ANY, 1, mock.ANY)
|
mock.ANY, 1, mock.ANY)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.get_deploy_steps',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
|
||||||
|
autospec=True)
|
||||||
|
def test_continue_node_deploy_first_agent_boot(self, mock_spawn,
|
||||||
|
mock_get_steps):
|
||||||
|
new_steps = [self.deploy_start, self.in_band_step, self.deploy_end]
|
||||||
|
mock_get_steps.return_value = new_steps
|
||||||
|
prv_state = states.DEPLOYWAIT
|
||||||
|
tgt_prv_state = states.ACTIVE
|
||||||
|
driver_info = {'deploy_steps': self.deploy_steps,
|
||||||
|
'deploy_step_index': 0}
|
||||||
|
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
|
||||||
|
provision_state=prv_state,
|
||||||
|
target_provision_state=tgt_prv_state,
|
||||||
|
last_error=None,
|
||||||
|
driver_internal_info=driver_info,
|
||||||
|
deploy_step=self.deploy_steps[0])
|
||||||
|
self._start_service()
|
||||||
|
self.service.continue_node_deploy(self.context, node.uuid)
|
||||||
|
self._stop_service()
|
||||||
|
node.refresh()
|
||||||
|
self.assertEqual(states.DEPLOYING, node.provision_state)
|
||||||
|
self.assertEqual(tgt_prv_state, node.target_provision_state)
|
||||||
|
self.assertTrue(node.driver_internal_info['steps_validated'])
|
||||||
|
self.assertEqual(new_steps, node.driver_internal_info['deploy_steps'])
|
||||||
|
mock_spawn.assert_called_with(mock.ANY,
|
||||||
|
deployments.do_next_deploy_step,
|
||||||
|
mock.ANY, 1, mock.ANY)
|
||||||
|
|
||||||
@mock.patch.object(task_manager.TaskManager, 'process_event',
|
@mock.patch.object(task_manager.TaskManager, 'process_event',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
|
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
|
||||||
@ -1744,7 +1777,8 @@ class ContinueNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
|
|||||||
prv_state = states.DEPLOYING
|
prv_state = states.DEPLOYING
|
||||||
tgt_prv_state = states.ACTIVE
|
tgt_prv_state = states.ACTIVE
|
||||||
driver_info = {'deploy_steps': self.deploy_steps,
|
driver_info = {'deploy_steps': self.deploy_steps,
|
||||||
'deploy_step_index': 0}
|
'deploy_step_index': 0,
|
||||||
|
'steps_validated': True}
|
||||||
self._start_service()
|
self._start_service()
|
||||||
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
|
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
|
||||||
provision_state=prv_state,
|
provision_state=prv_state,
|
||||||
@ -1767,7 +1801,8 @@ class ContinueNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
|
|||||||
def _continue_node_deploy_skip_step(self, mock_spawn, skip=True):
|
def _continue_node_deploy_skip_step(self, mock_spawn, skip=True):
|
||||||
# test that skipping current step mechanism works
|
# test that skipping current step mechanism works
|
||||||
driver_info = {'deploy_steps': self.deploy_steps,
|
driver_info = {'deploy_steps': self.deploy_steps,
|
||||||
'deploy_step_index': 0}
|
'deploy_step_index': 0,
|
||||||
|
'steps_validated': True}
|
||||||
if not skip:
|
if not skip:
|
||||||
driver_info['skip_current_deploy_step'] = skip
|
driver_info['skip_current_deploy_step'] = skip
|
||||||
node = obj_utils.create_test_node(
|
node = obj_utils.create_test_node(
|
||||||
@ -1801,7 +1836,8 @@ class ContinueNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
|
|||||||
# test that deployment_polling flag is cleared
|
# test that deployment_polling flag is cleared
|
||||||
driver_info = {'deploy_steps': self.deploy_steps,
|
driver_info = {'deploy_steps': self.deploy_steps,
|
||||||
'deploy_step_index': 0,
|
'deploy_step_index': 0,
|
||||||
'deployment_polling': True}
|
'deployment_polling': True,
|
||||||
|
'steps_validated': True}
|
||||||
node = obj_utils.create_test_node(
|
node = obj_utils.create_test_node(
|
||||||
self.context, driver='fake-hardware',
|
self.context, driver='fake-hardware',
|
||||||
provision_state=states.DEPLOYWAIT,
|
provision_state=states.DEPLOYWAIT,
|
||||||
|
@ -83,6 +83,8 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
|
|||||||
super(HeartbeatMixinTest, self).setUp()
|
super(HeartbeatMixinTest, self).setUp()
|
||||||
self.deploy = agent_base.HeartbeatMixin()
|
self.deploy = agent_base.HeartbeatMixin()
|
||||||
|
|
||||||
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
|
'refresh_steps', autospec=True)
|
||||||
@mock.patch.object(agent_base.HeartbeatMixin,
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
'in_core_deploy_step', autospec=True)
|
'in_core_deploy_step', autospec=True)
|
||||||
@mock.patch.object(agent_base.HeartbeatMixin,
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
@ -93,7 +95,8 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
|
|||||||
'reboot_to_instance', autospec=True)
|
'reboot_to_instance', autospec=True)
|
||||||
def test_heartbeat_continue_deploy(self, rti_mock, cd_mock,
|
def test_heartbeat_continue_deploy(self, rti_mock, cd_mock,
|
||||||
deploy_started_mock,
|
deploy_started_mock,
|
||||||
in_deploy_mock):
|
in_deploy_mock,
|
||||||
|
refresh_steps_mock):
|
||||||
in_deploy_mock.return_value = True
|
in_deploy_mock.return_value = True
|
||||||
deploy_started_mock.return_value = False
|
deploy_started_mock.return_value = False
|
||||||
self.node.provision_state = states.DEPLOYWAIT
|
self.node.provision_state = states.DEPLOYWAIT
|
||||||
@ -109,6 +112,42 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
|
|||||||
task.node.driver_internal_info['agent_version'])
|
task.node.driver_internal_info['agent_version'])
|
||||||
cd_mock.assert_called_once_with(self.deploy, task)
|
cd_mock.assert_called_once_with(self.deploy, task)
|
||||||
self.assertFalse(rti_mock.called)
|
self.assertFalse(rti_mock.called)
|
||||||
|
refresh_steps_mock.assert_called_once_with(self.deploy,
|
||||||
|
task, 'deploy')
|
||||||
|
|
||||||
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
|
'refresh_steps', 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, 'continue_deploy',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
|
'reboot_to_instance', autospec=True)
|
||||||
|
def test_heartbeat_continue_deploy_second_run(self, rti_mock, cd_mock,
|
||||||
|
deploy_started_mock,
|
||||||
|
in_deploy_mock,
|
||||||
|
refresh_steps_mock):
|
||||||
|
in_deploy_mock.return_value = True
|
||||||
|
deploy_started_mock.return_value = False
|
||||||
|
dii = self.node.driver_internal_info
|
||||||
|
dii['agent_cached_deploy_steps'] = ['step']
|
||||||
|
self.node.driver_internal_info = dii
|
||||||
|
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)
|
||||||
|
self.assertFalse(refresh_steps_mock.called)
|
||||||
|
|
||||||
@mock.patch.object(agent_base.HeartbeatMixin,
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
'in_core_deploy_step', autospec=True)
|
'in_core_deploy_step', autospec=True)
|
||||||
@ -141,8 +180,8 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
|
|||||||
self.assertFalse(cd_mock.called)
|
self.assertFalse(cd_mock.called)
|
||||||
rti_mock.assert_called_once_with(self.deploy, task)
|
rti_mock.assert_called_once_with(self.deploy, task)
|
||||||
|
|
||||||
@mock.patch.object(manager_utils,
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
'notify_conductor_resume_deploy', autospec=True)
|
'process_next_step', autospec=True)
|
||||||
@mock.patch.object(agent_base.HeartbeatMixin,
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
'in_core_deploy_step', autospec=True)
|
'in_core_deploy_step', autospec=True)
|
||||||
@mock.patch.object(agent_base.HeartbeatMixin,
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
@ -157,7 +196,7 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
|
|||||||
deploy_is_done_mock,
|
deploy_is_done_mock,
|
||||||
deploy_started_mock,
|
deploy_started_mock,
|
||||||
in_deploy_mock,
|
in_deploy_mock,
|
||||||
in_resume_deploy_mock):
|
process_next_mock):
|
||||||
# Check that heartbeats do not trigger deployment actions when not in
|
# Check that heartbeats do not trigger deployment actions when not in
|
||||||
# the deploy.deploy step.
|
# the deploy.deploy step.
|
||||||
in_deploy_mock.return_value = False
|
in_deploy_mock.return_value = False
|
||||||
@ -176,7 +215,53 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
|
|||||||
self.assertFalse(deploy_is_done_mock.called)
|
self.assertFalse(deploy_is_done_mock.called)
|
||||||
self.assertFalse(cd_mock.called)
|
self.assertFalse(cd_mock.called)
|
||||||
self.assertFalse(rti_mock.called)
|
self.assertFalse(rti_mock.called)
|
||||||
self.assertTrue(in_resume_deploy_mock.called)
|
process_next_mock.assert_called_once_with(self.deploy,
|
||||||
|
task, 'deploy')
|
||||||
|
|
||||||
|
@mock.patch.object(agent_base.HeartbeatMixin,
|
||||||
|
'refresh_steps', autospec=True)
|
||||||
|
@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_not_in_core_deploy_step_refresh(self, rti_mock, cd_mock,
|
||||||
|
deploy_is_done_mock,
|
||||||
|
deploy_started_mock,
|
||||||
|
in_deploy_mock,
|
||||||
|
process_next_mock,
|
||||||
|
refresh_steps_mock):
|
||||||
|
# Check loading in-band deploy steps.
|
||||||
|
in_deploy_mock.return_value = False
|
||||||
|
self.node.provision_state = states.DEPLOYWAIT
|
||||||
|
info = self.node.driver_internal_info
|
||||||
|
info.pop('agent_cached_deploy_steps', None)
|
||||||
|
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)
|
||||||
|
refresh_steps_mock.assert_called_once_with(self.deploy,
|
||||||
|
task, 'deploy')
|
||||||
|
process_next_mock.assert_called_once_with(self.deploy,
|
||||||
|
task, 'deploy')
|
||||||
|
|
||||||
@mock.patch.object(manager_utils,
|
@mock.patch.object(manager_utils,
|
||||||
'notify_conductor_resume_deploy', autospec=True)
|
'notify_conductor_resume_deploy', autospec=True)
|
||||||
@ -200,6 +285,7 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
|
|||||||
in_deploy_mock.return_value = False
|
in_deploy_mock.return_value = False
|
||||||
self.node.provision_state = states.DEPLOYWAIT
|
self.node.provision_state = states.DEPLOYWAIT
|
||||||
info = self.node.driver_internal_info
|
info = self.node.driver_internal_info
|
||||||
|
info['agent_cached_deploy_steps'] = ['step1']
|
||||||
info['deployment_polling'] = True
|
info['deployment_polling'] = True
|
||||||
self.node.driver_internal_info = info
|
self.node.driver_internal_info = info
|
||||||
self.node.save()
|
self.node.save()
|
||||||
@ -2132,6 +2218,10 @@ class TestRefreshCleanSteps(AgentDeployMixinBaseTest):
|
|||||||
task.ports)
|
task.ports)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeAgentDeploy(agent_base.AgentDeployMixin, fake.FakeDeploy):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class StepMethodsTestCase(db_base.DbTestCase):
|
class StepMethodsTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -2159,6 +2249,7 @@ class StepMethodsTestCase(db_base.DbTestCase):
|
|||||||
self.node = object_utils.create_test_node(self.context, **n)
|
self.node = object_utils.create_test_node(self.context, **n)
|
||||||
self.ports = [object_utils.create_test_port(self.context,
|
self.ports = [object_utils.create_test_port(self.context,
|
||||||
node_id=self.node.id)]
|
node_id=self.node.id)]
|
||||||
|
self.deploy = FakeAgentDeploy()
|
||||||
|
|
||||||
def test_agent_get_steps(self):
|
def test_agent_get_steps(self):
|
||||||
with task_manager.acquire(
|
with task_manager.acquire(
|
||||||
@ -2222,6 +2313,26 @@ class StepMethodsTestCase(db_base.DbTestCase):
|
|||||||
self.context, self.node.uuid, shared=False) as task:
|
self.context, self.node.uuid, shared=False) as task:
|
||||||
self.assertEqual([], agent_base.get_steps(task, 'clean'))
|
self.assertEqual([], agent_base.get_steps(task, 'clean'))
|
||||||
|
|
||||||
|
def test_get_deploy_steps(self):
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=False) as task:
|
||||||
|
task.node.driver_internal_info = {
|
||||||
|
'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)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
@mock.patch('ironic.objects.Port.list_by_node_id',
|
@mock.patch('ironic.objects.Port.list_by_node_id',
|
||||||
spec_set=types.FunctionType)
|
spec_set=types.FunctionType)
|
||||||
@mock.patch.object(agent_client.AgentClient, 'execute_clean_step',
|
@mock.patch.object(agent_client.AgentClient, 'execute_clean_step',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user