Merge "Handle agent still doing the prior command"
This commit is contained in:
commit
31277f2c95
@ -813,3 +813,8 @@ class UnknownAttribute(ClientSideError):
|
|||||||
else:
|
else:
|
||||||
self.fieldname = name
|
self.fieldname = name
|
||||||
super(UnknownAttribute, self).__init__(self.msg)
|
super(UnknownAttribute, self).__init__(self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentInProgress(IronicException):
|
||||||
|
_msg_fmt = _('Node %(node)s command "%(command)s" failed. Agent is '
|
||||||
|
'presently executing a command. Error %(error)s')
|
||||||
|
@ -179,6 +179,17 @@ def do_next_clean_step(task, step_index):
|
|||||||
else None)
|
else None)
|
||||||
task.process_event('wait', target_state=target_state)
|
task.process_event('wait', target_state=target_state)
|
||||||
return
|
return
|
||||||
|
if isinstance(e, exception.AgentInProgress):
|
||||||
|
LOG.info('Conductor attempted to process clean step for '
|
||||||
|
'node %(node)s. Agent indicated it is presently '
|
||||||
|
'executing a command. Error: %(error)s',
|
||||||
|
{'node': task.node.uuid,
|
||||||
|
'error': e})
|
||||||
|
driver_internal_info['skip_current_clean_step'] = False
|
||||||
|
node.driver_internal_info = driver_internal_info
|
||||||
|
target_state = states.MANAGEABLE if manual_clean else None
|
||||||
|
task.process_event('wait', target_state=target_state)
|
||||||
|
return
|
||||||
|
|
||||||
msg = (_('Node %(node)s failed step %(step)s: '
|
msg = (_('Node %(node)s failed step %(step)s: '
|
||||||
'%(exc)s') %
|
'%(exc)s') %
|
||||||
|
@ -257,6 +257,16 @@ def do_next_deploy_step(task, step_index):
|
|||||||
node.driver_internal_info = driver_internal_info
|
node.driver_internal_info = driver_internal_info
|
||||||
task.process_event('wait')
|
task.process_event('wait')
|
||||||
return
|
return
|
||||||
|
if isinstance(e, exception.AgentInProgress):
|
||||||
|
LOG.info('Conductor attempted to process deploy step for '
|
||||||
|
'node %(node)s. Agent indicated it is presently '
|
||||||
|
'executing a command. Error: %(error)s',
|
||||||
|
{'node': task.node.uuid,
|
||||||
|
'error': e})
|
||||||
|
driver_internal_info['skip_current_deploy_step'] = False
|
||||||
|
node.driver_internal_info = driver_internal_info
|
||||||
|
task.process_event('wait')
|
||||||
|
return
|
||||||
log_msg = ('Node %(node)s failed deploy step %(step)s. Error: '
|
log_msg = ('Node %(node)s failed deploy step %(step)s. Error: '
|
||||||
'%(err)s' %
|
'%(err)s' %
|
||||||
{'node': node.uuid, 'step': node.deploy_step, 'err': e})
|
{'node': node.uuid, 'step': node.deploy_step, 'err': e})
|
||||||
|
@ -898,11 +898,11 @@ class AgentDeployMixin(HeartbeatMixin, AgentOobStepsMixin):
|
|||||||
call = getattr(self._client, 'get_%s_steps' % step_type)
|
call = getattr(self._client, 'get_%s_steps' % step_type)
|
||||||
try:
|
try:
|
||||||
agent_result = call(node, task.ports).get('command_result', {})
|
agent_result = call(node, task.ports).get('command_result', {})
|
||||||
except exception.AgentAPIError as exc:
|
except exception.AgentInProgress as exc:
|
||||||
if 'agent is busy' in str(exc):
|
LOG.debug('Agent for node %(node)s is busy with a command, '
|
||||||
LOG.debug('Agent is busy with a command, will refresh steps '
|
'will refresh steps on the next heartbeat.',
|
||||||
'on the next heartbeat')
|
{'node': task.node.uuid})
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO(dtantsur): change to just 'raise'
|
# TODO(dtantsur): change to just 'raise'
|
||||||
if step_type == 'clean':
|
if step_type == 'clean':
|
||||||
|
@ -153,6 +153,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command result from agent, see
|
:returns: A dict containing command result from agent, see
|
||||||
get_commands_status for a sample.
|
get_commands_status for a sample.
|
||||||
"""
|
"""
|
||||||
@ -223,6 +225,17 @@ class AgentClient(object):
|
|||||||
'Expected 2xx HTTP status code, got %(code)d.',
|
'Expected 2xx HTTP status code, got %(code)d.',
|
||||||
{'method': method, 'node': node.uuid,
|
{'method': method, 'node': node.uuid,
|
||||||
'code': response.status_code})
|
'code': response.status_code})
|
||||||
|
if (response.status_code == http_client.CONFLICT
|
||||||
|
or 'agent is busy' in faultstring.lower()):
|
||||||
|
# HTTP 409 check as an explicit check of if the agent
|
||||||
|
# is already busy.
|
||||||
|
# NOTE(TheJulia): The agent sends upper case A as of
|
||||||
|
# late victoria, but lower case the entire message
|
||||||
|
# for compatability with pre-late victoria agents
|
||||||
|
# which returns HTTP 409.
|
||||||
|
raise exception.AgentInProgress(node=node.uuid,
|
||||||
|
command=method,
|
||||||
|
error=faultstring)
|
||||||
raise exception.AgentAPIError(node=node.uuid,
|
raise exception.AgentAPIError(node=node.uuid,
|
||||||
status=response.status_code,
|
status=response.status_code,
|
||||||
error=faultstring)
|
error=faultstring)
|
||||||
@ -337,6 +350,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command status from agent.
|
:returns: A dict containing command status from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
"""
|
"""
|
||||||
@ -370,6 +385,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
"""
|
"""
|
||||||
@ -400,6 +417,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
"""
|
"""
|
||||||
@ -456,6 +475,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
The value of key command_result is in the form of:
|
The value of key command_result is in the form of:
|
||||||
@ -487,6 +508,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
The value of key command_result is in the form of:
|
The value of key command_result is in the form of:
|
||||||
@ -550,6 +573,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
The value of key command_result is in the form of:
|
The value of key command_result is in the form of:
|
||||||
@ -581,6 +606,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -597,6 +624,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
"""
|
"""
|
||||||
@ -612,6 +641,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
"""
|
"""
|
||||||
@ -627,6 +658,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
"""
|
"""
|
||||||
@ -643,6 +676,8 @@ class AgentClient(object):
|
|||||||
:raises: IronicException when failed to issue the request or there was
|
:raises: IronicException when failed to issue the request or there was
|
||||||
a malformed response from the agent.
|
a malformed response from the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
See :func:`get_commands_status` for a command result sample.
|
See :func:`get_commands_status` for a command result sample.
|
||||||
"""
|
"""
|
||||||
@ -660,6 +695,8 @@ class AgentClient(object):
|
|||||||
to issue the request, or there was a malformed response from
|
to issue the request, or there was a malformed response from
|
||||||
the agent.
|
the agent.
|
||||||
:raises: AgentAPIError when agent failed to execute specified command.
|
:raises: AgentAPIError when agent failed to execute specified command.
|
||||||
|
:raises: AgentInProgress when the command fails to execute as the agent
|
||||||
|
is presently executing the prior command.
|
||||||
:raises: InstanceRescueFailure when the agent ramdisk is too old
|
:raises: InstanceRescueFailure when the agent ramdisk is too old
|
||||||
to support transmission of the rescue password.
|
to support transmission of the rescue password.
|
||||||
:returns: A dict containing command response from agent.
|
:returns: A dict containing command response from agent.
|
||||||
|
@ -770,6 +770,37 @@ class DoNodeCleanTestCase(db_base.DbTestCase):
|
|||||||
mock_execute.assert_called_once_with(
|
mock_execute.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, self.clean_steps[0])
|
mock.ANY, mock.ANY, self.clean_steps[0])
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_clean_step',
|
||||||
|
autospec=True)
|
||||||
|
def test_do_next_clean_step_agent_busy(self, mock_execute):
|
||||||
|
# When a clean step fails, go to CLEANWAIT
|
||||||
|
tgt_prov_state = states.MANAGEABLE
|
||||||
|
|
||||||
|
node = obj_utils.create_test_node(
|
||||||
|
self.context, driver='fake-hardware',
|
||||||
|
provision_state=states.CLEANING,
|
||||||
|
target_provision_state=tgt_prov_state,
|
||||||
|
last_error=None,
|
||||||
|
driver_internal_info={'clean_steps': self.clean_steps,
|
||||||
|
'clean_step_index': None,
|
||||||
|
'cleaning_reboot': True},
|
||||||
|
clean_step={})
|
||||||
|
mock_execute.side_effect = exception.AgentInProgress(
|
||||||
|
reason='still meowing')
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, node.uuid, shared=False) as task:
|
||||||
|
cleaning.do_next_clean_step(task, 0)
|
||||||
|
|
||||||
|
node.refresh()
|
||||||
|
# Make sure we go to CLEANWAIT
|
||||||
|
self.assertEqual(states.CLEANWAIT, node.provision_state)
|
||||||
|
self.assertEqual(tgt_prov_state, node.target_provision_state)
|
||||||
|
self.assertEqual(self.clean_steps[0], node.clean_step)
|
||||||
|
self.assertEqual(0, node.driver_internal_info['clean_step_index'])
|
||||||
|
self.assertFalse(node.driver_internal_info['skip_current_clean_step'])
|
||||||
|
mock_execute.assert_called_once_with(
|
||||||
|
mock.ANY, mock.ANY, self.clean_steps[0])
|
||||||
|
|
||||||
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_clean_step',
|
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_clean_step',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_do_next_clean_step_oob_reboot_last_step(self, mock_execute):
|
def test_do_next_clean_step_oob_reboot_last_step(self, mock_execute):
|
||||||
|
@ -769,7 +769,42 @@ class DoNextDeployStepTestCase(mgr_utils.ServiceSetUpMixin,
|
|||||||
self._stop_service()
|
self._stop_service()
|
||||||
node.refresh()
|
node.refresh()
|
||||||
|
|
||||||
# Make sure we go to CLEANWAIT
|
# Make sure we go to DEPLOYWAIT
|
||||||
|
self.assertEqual(states.DEPLOYWAIT, node.provision_state)
|
||||||
|
self.assertEqual(tgt_prov_state, node.target_provision_state)
|
||||||
|
self.assertEqual(self.deploy_steps[0], node.deploy_step)
|
||||||
|
self.assertEqual(0, node.driver_internal_info['deploy_step_index'])
|
||||||
|
self.assertFalse(node.driver_internal_info['skip_current_deploy_step'])
|
||||||
|
mock_execute.assert_called_once_with(
|
||||||
|
mock.ANY, mock.ANY, self.deploy_steps[0])
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_deploy_step',
|
||||||
|
autospec=True)
|
||||||
|
def test_do_next_deploy_step_agent_busy(self, mock_execute):
|
||||||
|
# When a deploy step fails, go to DEPLOYWAIT
|
||||||
|
tgt_prov_state = states.ACTIVE
|
||||||
|
|
||||||
|
self._start_service()
|
||||||
|
node = obj_utils.create_test_node(
|
||||||
|
self.context, driver='fake-hardware',
|
||||||
|
provision_state=states.DEPLOYING,
|
||||||
|
target_provision_state=tgt_prov_state,
|
||||||
|
last_error=None,
|
||||||
|
driver_internal_info={'deploy_steps': self.deploy_steps,
|
||||||
|
'deploy_step_index': None,
|
||||||
|
'deployment_reboot': True},
|
||||||
|
clean_step={})
|
||||||
|
mock_execute.side_effect = exception.AgentInProgress(
|
||||||
|
reason='meow')
|
||||||
|
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, node.uuid, shared=False) as task:
|
||||||
|
deployments.do_next_deploy_step(task, 0)
|
||||||
|
|
||||||
|
self._stop_service()
|
||||||
|
node.refresh()
|
||||||
|
|
||||||
|
# Make sure we go to DEPLOYWAIT
|
||||||
self.assertEqual(states.DEPLOYWAIT, node.provision_state)
|
self.assertEqual(states.DEPLOYWAIT, node.provision_state)
|
||||||
self.assertEqual(tgt_prov_state, node.target_provision_state)
|
self.assertEqual(tgt_prov_state, node.target_provision_state)
|
||||||
self.assertEqual(self.deploy_steps[0], node.deploy_step)
|
self.assertEqual(self.deploy_steps[0], node.deploy_step)
|
||||||
|
@ -2223,8 +2223,8 @@ class TestRefreshCleanSteps(AgentDeployMixinBaseTest):
|
|||||||
@mock.patch.object(agent_client.AgentClient, 'get_deploy_steps',
|
@mock.patch.object(agent_client.AgentClient, 'get_deploy_steps',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_refresh_steps_busy(self, client_mock, log_mock):
|
def test_refresh_steps_busy(self, client_mock, log_mock):
|
||||||
client_mock.side_effect = exception.AgentAPIError(
|
client_mock.side_effect = exception.AgentInProgress(
|
||||||
node="node", status="500", error='agent is busy')
|
node="node", error='Agent is busy : maximum meowing')
|
||||||
|
|
||||||
with task_manager.acquire(
|
with task_manager.acquire(
|
||||||
self.context, self.node.uuid, shared=False) as task:
|
self.context, self.node.uuid, shared=False) as task:
|
||||||
|
@ -237,6 +237,51 @@ class TestAgentClient(base.TestCase):
|
|||||||
timeout=60,
|
timeout=60,
|
||||||
verify=True)
|
verify=True)
|
||||||
|
|
||||||
|
def test__command_error_code_agent_busy(self):
|
||||||
|
# Victoria and previous busy status check.
|
||||||
|
response_text = {"faultstring": "Agent is busy - meowing"}
|
||||||
|
self.client.session.post.return_value = MockResponse(
|
||||||
|
response_text, status_code=http_client.BAD_REQUEST)
|
||||||
|
method = 'standby.run_image'
|
||||||
|
image_info = {'image_id': 'test_image'}
|
||||||
|
params = {'image_info': image_info}
|
||||||
|
|
||||||
|
url = self.client._get_command_url(self.node)
|
||||||
|
body = self.client._get_command_body(method, params)
|
||||||
|
|
||||||
|
self.assertRaises(exception.AgentInProgress,
|
||||||
|
self.client._command,
|
||||||
|
self.node, method, params)
|
||||||
|
self.client.session.post.assert_called_once_with(
|
||||||
|
url,
|
||||||
|
data=body,
|
||||||
|
params={'wait': 'false'},
|
||||||
|
timeout=60,
|
||||||
|
verify=True)
|
||||||
|
|
||||||
|
def test__command_error_code_agent_busy_conflict(self):
|
||||||
|
# Post Wallaby logic as the IPA return code changed to
|
||||||
|
# better delineate the state.
|
||||||
|
response_text = {"faultstring": "lolcat says busy meowing"}
|
||||||
|
self.client.session.post.return_value = MockResponse(
|
||||||
|
response_text, status_code=http_client.CONFLICT)
|
||||||
|
method = 'standby.run_image'
|
||||||
|
image_info = {'image_id': 'test_image'}
|
||||||
|
params = {'image_info': image_info}
|
||||||
|
|
||||||
|
url = self.client._get_command_url(self.node)
|
||||||
|
body = self.client._get_command_body(method, params)
|
||||||
|
|
||||||
|
self.assertRaises(exception.AgentInProgress,
|
||||||
|
self.client._command,
|
||||||
|
self.node, method, params)
|
||||||
|
self.client.session.post.assert_called_once_with(
|
||||||
|
url,
|
||||||
|
data=body,
|
||||||
|
params={'wait': 'false'},
|
||||||
|
timeout=60,
|
||||||
|
verify=True)
|
||||||
|
|
||||||
@mock.patch('os.path.exists', autospec=True, return_value=True)
|
@mock.patch('os.path.exists', autospec=True, return_value=True)
|
||||||
def test__command_verify(self, mock_exists):
|
def test__command_verify(self, mock_exists):
|
||||||
response_data = {'status': 'ok'}
|
response_data = {'status': 'ok'}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixes recognition of a busy agent to also handle recognition
|
||||||
|
during deployment steps by more uniformly detecting and
|
||||||
|
identifying when the ``ironic-python-agent`` service is busy.
|
Loading…
Reference in New Issue
Block a user