Files
ironic/ironic/tests/unit/drivers/modules/test_agent_client.py
Julia Kreger e1a0864635 Add service steps call to agent logic
While the prior sevice steps patch had a huge portion of the
needed code already due to copy-pasta, this change finishes
wiring in the ability for the agent to be launched for service
steps and heartbeat to occur, combined with support to retrieve
service steps from the running agent, ultimately to enable
operators to take a deployed node, and ask Ironic to make changes,
or my more favorite use case, go benchmark it for a while.

Also edits the service steps release note to remove the outstanding
issue, and makes some minor corrections in the code which was copied
but didn't quite have testing wired up yet.

Change-Id: Ibfe42037b520a76539234cf1a5e19afd335ce8a8
2023-08-28 20:57:43 +00:00

897 lines
36 KiB
Python

# Copyright 2014 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from http import client as http_client
import json
import ssl
from unittest import mock
import requests
from ironic.common import exception
from ironic import conf
from ironic.drivers.modules import agent_client
from ironic.tests import base
CONF = conf.CONF
class MockResponse(object):
def __init__(self, data=None, status_code=http_client.OK, text=None):
assert not (data and text)
self.text = text
self.data = data
self.status_code = status_code
def json(self):
if self.text:
return json.loads(self.text)
else:
return self.data
class MockCommandStatus(MockResponse):
def __init__(self, status, name='fake', error=None,
status_code=http_client.OK):
super().__init__({
'commands': [
{'command_name': name,
'command_status': status,
'command_result': 'I did something',
'command_error': error}
]
})
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'
self.driver_internal_info = {
'agent_url': "http://127.0.0.1:9999",
'hardware_manager_version': {'generic': '1'}
}
self.instance_info = {}
self.driver_info = {}
def as_dict(self, secure=False):
assert secure, 'agent_client must pass secure=True'
return {
'uuid': self.uuid,
'driver_internal_info': self.driver_internal_info,
'instance_info': self.instance_info,
'driver_info': self.driver_info,
}
def save(self):
pass
class TestAgentClient(base.TestCase):
def setUp(self):
super(TestAgentClient, self).setUp()
self.client = agent_client.AgentClient()
self.client.session = mock.MagicMock(autospec=requests.Session)
self.node = MockNode()
def test_content_type_header(self):
client = agent_client.AgentClient()
self.assertEqual('application/json',
client.session.headers['Content-Type'])
def test__get_command_url(self):
command_url = self.client._get_command_url(self.node)
expected = ('%s/v1/commands/'
% self.node.driver_internal_info['agent_url'])
self.assertEqual(expected, command_url)
def test__get_command_url_fail(self):
del self.node.driver_internal_info['agent_url']
self.assertRaises(exception.AgentConnectionFailed,
self.client._get_command_url,
self.node)
def test__get_command_body(self):
expected = json.dumps({'name': 'get_clean_steps', 'params': {}})
self.assertEqual(expected,
self.client._get_command_body('get_clean_steps', {}))
def test__command(self):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
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)
response = self.client._command(self.node, method, params)
self.assertEqual(response, response_data)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false'},
timeout=60,
verify=True)
def test__command_fail_json(self):
response_text = 'this be not json matey!'
self.client.session.post.return_value = MockResponse(
text=response_text)
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.IronicException,
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_fail_post(self):
error = 'Boom'
self.client.session.post.side_effect = requests.RequestException(error)
method = 'foo.bar'
params = {}
self.client._get_command_url(self.node)
self.client._get_command_body(method, params)
e = self.assertRaises(exception.IronicException,
self.client._command,
self.node, method, params)
self.assertEqual('Error invoking agent command %(method)s for node '
'%(node)s. Error: %(error)s' %
{'method': method, 'node': self.node.uuid,
'error': error}, str(e))
def test__command_fail_connect(self):
error = 'Boom'
self.client.session.post.side_effect = requests.ConnectionError(error)
method = 'foo.bar'
params = {}
url = self.client._get_command_url(self.node)
self.client._get_command_body(method, params)
e = self.assertRaises(exception.AgentConnectionFailed,
self.client._command,
self.node, method, params)
self.assertEqual('Connection to agent failed: Failed to connect to '
'the agent running on node %(node)s for invoking '
'command %(method)s. Error: %(error)s' %
{'method': method, 'node': self.node.uuid,
'error': error}, str(e))
self.client.session.post.assert_called_with(
url,
data=mock.ANY,
params={'wait': 'false'},
timeout=60,
verify=True)
self.assertEqual(3, self.client.session.post.call_count)
self.assertTrue(self.client.session.get.called)
def test__command_fail_connect_no_command_running(self):
error = 'Boom'
self.client.session.post.side_effect = requests.ConnectionError(error)
self.client.session.get.return_value.json.return_value = {
'commands': []
}
method = 'foo.bar'
params = {}
url = self.client._get_command_url(self.node)
self.client._get_command_body(method, params)
e = self.assertRaises(exception.AgentConnectionFailed,
self.client._command,
self.node, method, params)
self.assertEqual('Connection to agent failed: Failed to connect to '
'the agent running on node %(node)s for invoking '
'command %(method)s. Error: %(error)s' %
{'method': method, 'node': self.node.uuid,
'error': error}, str(e))
self.client.session.post.assert_called_with(
url,
data=mock.ANY,
params={'wait': 'false'},
timeout=60,
verify=True)
self.assertEqual(3, self.client.session.post.call_count)
self.assertTrue(self.client.session.get.called)
def test__command_fail_connect_wrong_command_running(self):
error = 'Boom'
self.client.session.post.side_effect = requests.ConnectionError(error)
self.client.session.get.return_value.json.return_value = {
'commands': [
{'command_name': 'meow', 'command_status': 'RUNNING'},
]
}
method = 'foo.bar'
params = {}
url = self.client._get_command_url(self.node)
self.client._get_command_body(method, params)
e = self.assertRaises(exception.AgentConnectionFailed,
self.client._command,
self.node, method, params)
self.assertEqual('Connection to agent failed: Failed to connect to '
'the agent running on node %(node)s for invoking '
'command %(method)s. Error: %(error)s' %
{'method': method, 'node': self.node.uuid,
'error': error}, str(e))
self.client.session.post.assert_called_with(
url,
data=mock.ANY,
params={'wait': 'false'},
timeout=60,
verify=True)
self.assertEqual(3, self.client.session.post.call_count)
self.assertTrue(self.client.session.get.called)
def test__command_fail_connect_command_not_running(self):
error = 'Boom'
self.client.session.post.side_effect = requests.ConnectionError(error)
self.client.session.get.return_value.json.return_value = {
'commands': [
{'command_name': 'bar', 'command_status': 'FINISHED'},
]
}
method = 'foo.bar'
params = {}
url = self.client._get_command_url(self.node)
self.client._get_command_body(method, params)
e = self.assertRaises(exception.AgentConnectionFailed,
self.client._command,
self.node, method, params)
self.assertEqual('Connection to agent failed: Failed to connect to '
'the agent running on node %(node)s for invoking '
'command %(method)s. Error: %(error)s' %
{'method': method, 'node': self.node.uuid,
'error': error}, str(e))
self.client.session.post.assert_called_with(
url,
data=mock.ANY,
params={'wait': 'false'},
timeout=60,
verify=True)
self.assertEqual(3, self.client.session.post.call_count)
self.assertTrue(self.client.session.get.called)
def test__command_fail_connect_command_is_running(self):
error = 'Boom'
self.client.session.post.side_effect = requests.ConnectionError(error)
self.client.session.get.return_value.json.return_value = {
'commands': [
{'command_name': 'bar', 'command_status': 'RUNNING'},
]
}
method = 'foo.bar'
params = {}
url = self.client._get_command_url(self.node)
self.client._get_command_body(method, params)
result = self.client._command(self.node, method, params)
self.assertEqual({'command_name': 'bar', 'command_status': 'RUNNING'},
result)
self.client.session.post.assert_called_once_with(
url,
data=mock.ANY,
params={'wait': 'false'},
timeout=60,
verify=True)
self.assertTrue(self.client.session.get.called)
def test__command_error_code(self):
response_text = {"faultstring": "you dun goofd"}
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.AgentAPIError,
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_okay_error_typeerror_embedded(self):
response_data = {"faultstring": "you dun goofd",
"command_error": {"type": "TypeError"}}
self.client.session.post.return_value = MockResponse(response_data)
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.AgentAPIError,
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(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)
def test__command_verify(self, mock_exists):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.node.driver_info['agent_verify_ca'] = '/path/to/agent.crt'
url = self.client._get_command_url(self.node)
body = self.client._get_command_body(method, params)
response = self.client._command(self.node, method, params)
self.assertEqual(response, response_data)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false'},
timeout=60,
verify='/path/to/agent.crt')
@mock.patch('os.path.exists', autospec=True, return_value=True)
def test__command_verify_internal(self, mock_exists):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.node.driver_info['agent_verify_ca'] = True
self.node.driver_internal_info['agent_verify_ca'] = '/path/to/crt'
url = self.client._get_command_url(self.node)
body = self.client._get_command_body(method, params)
response = self.client._command(self.node, method, params)
self.assertEqual(response, response_data)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false'},
timeout=60,
verify='/path/to/crt')
@mock.patch('os.path.exists', autospec=True, return_value=True)
def test__command_verify_config(self, mock_exists):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.config(verify_ca='/path/to/crt', group='agent')
url = self.client._get_command_url(self.node)
body = self.client._get_command_body(method, params)
response = self.client._command(self.node, method, params)
self.assertEqual(response, response_data)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false'},
timeout=60,
verify='/path/to/crt')
@mock.patch('os.path.exists', autospec=True, return_value=True)
def test__command_verify_disable(self, mock_exists):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.config(verify_ca='False', group='agent')
url = self.client._get_command_url(self.node)
body = self.client._get_command_body(method, params)
response = self.client._command(self.node, method, params)
self.assertEqual(response, response_data)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false'},
timeout=60,
verify=False)
@mock.patch('os.path.exists', autospec=True, return_value=True)
def test__command_verify_disable_in_driver_info(self, mock_exists):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.node.driver_info['agent_verify_ca'] = False
url = self.client._get_command_url(self.node)
body = self.client._get_command_body(method, params)
response = self.client._command(self.node, method, params)
self.assertEqual(response, response_data)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false'},
timeout=60,
verify=False)
@mock.patch('os.path.exists', autospec=True, return_value=False)
def test__command_verify_invalid_file(self, mock_exists):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.config(verify_ca='/path/to/crt', group='agent')
self.assertRaises(exception.InvalidParameterValue,
self.client._command, self.node, method, params)
@mock.patch('time.sleep', autospec=True)
def test__command_poll(self, mock_sleep):
response_data = {'status': 'ok'}
final_status = MockCommandStatus('SUCCEEDED', name='run_image')
self.client.session.post.return_value = MockResponse(response_data)
self.client.session.get.side_effect = [
MockCommandStatus('RUNNING', name='run_image'),
final_status,
]
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
expected = {'command_error': None,
'command_name': 'run_image',
'command_result': 'I did something',
'command_status': 'SUCCEEDED'}
url = self.client._get_command_url(self.node)
body = self.client._get_command_body(method, params)
response = self.client._command(self.node, method, params, poll=True)
self.assertEqual(expected, response)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false'},
timeout=60,
verify=True)
self.client.session.get.assert_called_with(url, timeout=60,
verify=True)
mock_sleep.assert_called_with(CONF.agent.command_wait_interval)
def test_get_commands_status(self):
if not mock._is_instance_mock(self.client.session):
mock.patch.object(self.client.session, 'get',
autospec=True).start()
mock_get = self.client.session.get
res = mock.MagicMock(spec_set=['json'])
res.json.return_value = {'commands': []}
mock_get.return_value = res
self.assertEqual([], self.client.get_commands_status(self.node))
agent_url = self.node.driver_internal_info.get('agent_url')
mock_get.assert_called_once_with(
'%(agent_url)s/%(api_version)s/commands/' % {
'agent_url': agent_url,
'api_version': CONF.agent.agent_api_version},
verify=True, timeout=CONF.agent.command_timeout)
def test_get_commands_status_retries(self):
res = mock.MagicMock(spec_set=['json'])
res.json.return_value = {'commands': []}
self.client.session.get.side_effect = [
requests.ConnectionError('boom'),
res
]
self.assertEqual([], self.client.get_commands_status(self.node))
self.assertEqual(2, self.client.session.get.call_count)
def test_get_commands_status_no_retries(self):
self.client.session.get.side_effect = requests.ConnectionError('boom')
self.assertRaises(exception.AgentConnectionFailed,
self.client.get_commands_status, self.node,
retry_connection=False)
self.assertEqual(1, self.client.session.get.call_count)
@mock.patch('os.path.exists', autospec=True, return_value=True)
def test_get_commands_status_verify(self, mock_exists):
self.node.driver_info['agent_verify_ca'] = '/path/to/agent.crt'
if not mock._is_instance_mock(self.client.session):
mock.patch.object(self.client.session, 'get',
autospec=True).start()
mock_get = self.client.session.get
res = mock.MagicMock(spec_set=['json'])
res.json.return_value = {'commands': []}
mock_get.return_value = res
self.assertEqual([], self.client.get_commands_status(self.node))
agent_url = self.node.driver_internal_info.get('agent_url')
mock_get.assert_called_once_with(
'%(agent_url)s/%(api_version)s/commands/' % {
'agent_url': agent_url,
'api_version': CONF.agent.agent_api_version},
verify='/path/to/agent.crt',
timeout=CONF.agent.command_timeout)
def _test_install_bootloader(self, root_uuid, efi_system_part_uuid=None,
prep_boot_part_uuid=None):
self.client._command = mock.MagicMock(spec_set=[])
params = {'root_uuid': root_uuid,
'efi_system_part_uuid': efi_system_part_uuid,
'prep_boot_part_uuid': prep_boot_part_uuid,
'target_boot_mode': 'hello'}
self.client.install_bootloader(
self.node, root_uuid, efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=prep_boot_part_uuid, target_boot_mode='hello')
self.client._command.assert_called_once_with(
node=self.node, method='image.install_bootloader', params=params,
poll=True)
def test_install_bootloader(self):
self._test_install_bootloader(root_uuid='fake-root-uuid',
efi_system_part_uuid='fake-efi-uuid')
def test_install_bootloader_with_prep(self):
self._test_install_bootloader(root_uuid='fake-root-uuid',
efi_system_part_uuid='fake-efi-uuid',
prep_boot_part_uuid='fake-prep-uuid')
def test_get_clean_steps(self):
self.client._command = mock.MagicMock(spec_set=[])
ports = []
expected_params = {
'node': self.node.as_dict(secure=True),
'ports': []
}
self.client.get_clean_steps(self.node,
ports)
self.client._command.assert_called_once_with(
node=self.node, method='clean.get_clean_steps',
params=expected_params, wait=True)
def test_execute_clean_step(self):
self.client._command = mock.MagicMock(spec_set=[])
ports = []
step = {'priority': 10, 'step': 'erase_devices', 'interface': 'deploy'}
expected_params = {
'step': step,
'node': self.node.as_dict(secure=True),
'ports': [],
'clean_version':
self.node.driver_internal_info['hardware_manager_version']
}
self.client.execute_clean_step(step,
self.node,
ports)
self.client._command.assert_called_once_with(
node=self.node, method='clean.execute_clean_step',
params=expected_params)
def test_get_service_steps(self):
self.client._command = mock.MagicMock(spec_set=[])
ports = []
expected_params = {
'node': self.node.as_dict(secure=True),
'ports': []
}
self.client.get_service_steps(self.node,
ports)
self.client._command.assert_called_once_with(
node=self.node, method='service.get_service_steps',
params=expected_params, wait=True)
def test_get_service_steps_older_client(self):
self.client._command = mock.MagicMock(spec_set=[])
self.client._command.side_effect = exception.AgentAPIError('meow')
ports = []
expected_params = {
'node': self.node.as_dict(secure=True),
'ports': []
}
self.client.get_service_steps(self.node,
ports)
self.client._command.assert_called_once_with(
node=self.node, method='service.get_service_steps',
params=expected_params, wait=True)
def test_execute_service_step(self):
self.client._command = mock.MagicMock(spec_set=[])
ports = []
step = {'priority': 10, 'step': 'erase_devices', 'interface': 'deploy'}
expected_params = {
'step': step,
'node': self.node.as_dict(secure=True),
'ports': [],
'service_version':
self.node.driver_internal_info['hardware_manager_version']
}
self.client.execute_service_step(step,
self.node,
ports)
self.client._command.assert_called_once_with(
node=self.node, method='service.execute_service_step',
params=expected_params)
def test_power_off(self):
self.client._command = mock.MagicMock(spec_set=[])
self.client.power_off(self.node)
self.client._command.assert_called_once_with(
node=self.node, method='standby.power_off', params={})
def test_sync(self):
self.client._command = mock.MagicMock(spec_set=[])
self.client.sync(self.node)
self.client._command.assert_called_once_with(
node=self.node, method='standby.sync', params={}, wait=True)
def test_finalize_rescue(self):
self.client._command = mock.MagicMock(spec_set=[])
self.node.instance_info['rescue_password'] = 'password'
self.node.instance_info['hashed_rescue_password'] = '1234'
expected_params = {
'rescue_password': '1234',
'hashed': True,
}
self.client.finalize_rescue(self.node)
self.client._command.assert_called_once_with(
node=self.node, method='rescue.finalize_rescue',
params=expected_params)
def test_finalize_rescue_exc(self):
# node does not have 'rescue_password' set in its 'instance_info'
self.client._command = mock.MagicMock(spec_set=[])
self.assertRaises(exception.IronicException,
self.client.finalize_rescue,
self.node)
self.assertFalse(self.client._command.called)
def test_finalize_rescue_fallback(self):
self.config(require_rescue_password_hashed=False, group="conductor")
self.client._command = mock.MagicMock(spec_set=[])
self.node.instance_info['rescue_password'] = 'password'
self.node.instance_info['hashed_rescue_password'] = '1234'
self.client._command.side_effect = [
exception.AgentAPIError('blah'),
('', '')]
self.client.finalize_rescue(self.node)
self.client._command.assert_has_calls([
mock.call(node=mock.ANY, method='rescue.finalize_rescue',
params={'rescue_password': '1234',
'hashed': True}),
mock.call(node=mock.ANY, method='rescue.finalize_rescue',
params={'rescue_password': 'password'})])
def test_finalize_rescue_fallback_restricted(self):
self.config(require_rescue_password_hashed=True, group="conductor")
self.client._command = mock.MagicMock(spec_set=[])
self.node.instance_info['rescue_password'] = 'password'
self.node.instance_info['hashed_rescue_password'] = '1234'
self.client._command.side_effect = exception.AgentAPIError('blah')
self.assertRaises(exception.InstanceRescueFailure,
self.client.finalize_rescue,
self.node)
self.client._command.assert_has_calls([
mock.call(node=mock.ANY, method='rescue.finalize_rescue',
params={'rescue_password': '1234',
'hashed': True})])
def test__command_agent_client(self):
response_data = {'status': 'ok'}
self.client.session.post.return_value = MockResponse(response_data)
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'] = 'magical'
self.node.driver_internal_info = i_info
url = self.client._get_command_url(self.node)
body = self.client._get_command_body(method, params)
response = self.client._command(self.node, method, params)
self.assertEqual(response, response_data)
self.client.session.post.assert_called_once_with(
url,
data=body,
params={'wait': 'false',
'agent_token': 'magical'},
timeout=60,
verify=True)
class TestAgentClientAttempts(base.TestCase):
def setUp(self):
super(TestAgentClientAttempts, self).setUp()
self.client = agent_client.AgentClient()
self.client.session = mock.MagicMock(autospec=requests.Session)
self.node = MockNode()
def test__command_fail_all_attempts(self):
error = 'Connection Timeout'
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.client.session.post.side_effect = [
requests.Timeout(error),
ssl.SSLError('timed out'),
requests.ConnectionError(error),
requests.Timeout(error)]
self.client._get_command_url(self.node)
self.client._get_command_body(method, params)
e = self.assertRaises(exception.AgentConnectionFailed,
self.client._command,
self.node, method, params)
self.assertEqual('Connection to agent failed: Failed to connect to '
'the agent running on node %(node)s for invoking '
'command %(method)s. Error: %(error)s' %
{'method': method, 'node': self.node.uuid,
'error': error}, str(e))
self.assertEqual(3, self.client.session.post.call_count)
def test__command_succeed_after_two_timeouts(self):
error = 'Connection Timeout'
response_data = {'status': 'ok'}
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.client.session.post.side_effect = [requests.Timeout(error),
ssl.SSLError('timed out'),
MockResponse(response_data)]
response = self.client._command(self.node, method, params)
self.assertEqual(3, 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)
def test__command_fail_agent_token_required(self):
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'))
def test__command_succeed_after_one_timeout(self):
error = 'Connection Timeout'
response_data = {'status': 'ok'}
method = 'standby.run_image'
image_info = {'image_id': 'test_image'}
params = {'image_info': image_info}
self.client.session.post.side_effect = [requests.Timeout(error),
MockResponse(response_data),
requests.Timeout(error)]
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)