summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ironic/drivers/modules/agent_client.py24
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_client.py67
-rw-r--r--releasenotes/notes/handle-older-agent-command-5930124fd03bb327.yaml6
3 files changed, 94 insertions, 3 deletions
diff --git a/ironic/drivers/modules/agent_client.py b/ironic/drivers/modules/agent_client.py
index 090007d8f..4cb349509 100644
--- a/ironic/drivers/modules/agent_client.py
+++ b/ironic/drivers/modules/agent_client.py
@@ -207,15 +207,35 @@ class AgentClient(object):
'res': result.get('command_result'),
'error': error,
'code': response.status_code})
-
if response.status_code >= http_client.BAD_REQUEST:
+ faultstring = result.get('faultstring')
+ if 'agent_token' in faultstring and agent_token:
+ # NOTE(TheJulia) We have an agent that is out of date.
+ # which means I guess grenade updates the agent image
+ # for upgrades... :(
+ if not CONF.require_agent_token:
+ LOG.warning('Agent command %(method)s for node %(node)s '
+ 'failed. Expected 2xx HTTP status code, got '
+ '%(code)d. Error suggests an older ramdisk '
+ 'which does not support ``agent_token``. '
+ 'Removing the token for the next retry.',
+ {'method': method, 'node': node.uuid,
+ 'code': response.status_code})
+ i_info = node.driver_internal_info
+ i_info.pop('agent_secret_token')
+ node.driver_internal_info = i_info
+ node.save()
+ msg = ('Node {} does not appear to support '
+ 'agent_token and it is not required. Next retry '
+ 'will be without the token.').format(node.uuid)
+ raise exception.AgentConnectionFailed(reason=msg)
LOG.error('Agent command %(method)s for node %(node)s failed. '
'Expected 2xx HTTP status code, got %(code)d.',
{'method': method, 'node': node.uuid,
'code': response.status_code})
raise exception.AgentAPIError(node=node.uuid,
status=response.status_code,
- error=result.get('faultstring'))
+ error=faultstring)
self._raise_if_typeerror(result, node, method)
diff --git a/ironic/tests/unit/drivers/modules/test_agent_client.py b/ironic/tests/unit/drivers/modules/test_agent_client.py
index 1f8cc1f6a..cb794c94c 100644
--- a/ironic/tests/unit/drivers/modules/test_agent_client.py
+++ b/ironic/tests/unit/drivers/modules/test_agent_client.py
@@ -43,7 +43,8 @@ class MockResponse(object):
class MockCommandStatus(MockResponse):
- def __init__(self, status, name='fake', error=None):
+ def __init__(self, status, name='fake', error=None,
+ status_code=http_client.OK):
super().__init__({
'commands': [
{'command_name': name,
@@ -54,6 +55,12 @@ class MockCommandStatus(MockResponse):
})
+class MockFault(MockResponse):
+ def __init__(self, faultstring, status_code=http_client.BAD_REQUEST):
+ super().__init__({'faultstring': faultstring},
+ status_code=status_code)
+
+
class MockNode(object):
def __init__(self):
self.uuid = 'uuid'
@@ -73,6 +80,9 @@ class MockNode(object):
'driver_info': self.driver_info,
}
+ def save(self):
+ pass
+
class TestAgentClient(base.TestCase):
def setUp(self):
@@ -606,6 +616,61 @@ class TestAgentClientAttempts(base.TestCase):
verify=True)
@mock.patch.object(retrying.time, 'sleep', autospec=True)
+ def test__command_succeed_after_agent_token(self, mock_sleep):
+ self.config(require_agent_token=False)
+ mock_sleep.return_value = None
+ error = 'Unknown Argument: "agent_token"'
+ response_data = {'status': 'ok'}
+ method = 'standby.run_image'
+ image_info = {'image_id': 'test_image'}
+ params = {'image_info': image_info}
+ i_info = self.node.driver_internal_info
+ i_info['agent_secret_token'] = 'meowmeowmeow'
+ self.client.session.post.side_effect = [
+ MockFault(error),
+ MockResponse(response_data),
+ ]
+
+ response = self.client._command(self.node, method, params)
+ self.assertEqual(2, self.client.session.post.call_count)
+ self.assertEqual(response, response_data)
+ self.client.session.post.assert_called_with(
+ self.client._get_command_url(self.node),
+ data=self.client._get_command_body(method, params),
+ params={'wait': 'false'},
+ timeout=60,
+ verify=True)
+ self.assertNotIn('agent_secret_token', self.node.driver_internal_info)
+
+ @mock.patch.object(retrying.time, 'sleep', autospec=True)
+ def test__command_fail_agent_token_required(self, mock_sleep):
+ self.config(require_agent_token=True)
+ mock_sleep.return_value = None
+ error = 'Unknown Argument: "agent_token"'
+ method = 'standby.run_image'
+ image_info = {'image_id': 'test_image'}
+ params = {'image_info': image_info}
+ i_info = self.node.driver_internal_info
+ i_info['agent_secret_token'] = 'meowmeowmeow'
+ self.client.session.post.side_effect = [
+ MockFault(error)
+ ]
+
+ self.assertRaises(exception.AgentAPIError,
+ self.client._command,
+ self.node, method, params)
+ self.assertEqual(1, self.client.session.post.call_count)
+ self.client.session.post.assert_called_with(
+ self.client._get_command_url(self.node),
+ data=self.client._get_command_body(method, params),
+ params={'wait': 'false', 'agent_token': 'meowmeowmeow'},
+ timeout=60,
+ verify=True)
+ self.assertEqual(
+ 'meowmeowmeow',
+ self.node.driver_internal_info.get('agent_secret_token'))
+
+ @mock.patch.object(retrying.time, 'sleep', autospec=True)
def test__command_succeed_after_one_timeout(self, mock_sleep):
mock_sleep.return_value = None
error = 'Connection Timeout'
diff --git a/releasenotes/notes/handle-older-agent-command-5930124fd03bb327.yaml b/releasenotes/notes/handle-older-agent-command-5930124fd03bb327.yaml
new file mode 100644
index 000000000..babe89da5
--- /dev/null
+++ b/releasenotes/notes/handle-older-agent-command-5930124fd03bb327.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Fixes an issue with agent token handling where the agent has not been
+ upgraded resulting in an AgentAPIError, when the token is not required.
+ The conductor now retries without sending an agent token.