diff options
Diffstat (limited to 'tests/protocol/test_hostplugin.py')
-rw-r--r-- | tests/protocol/test_hostplugin.py | 326 |
1 files changed, 259 insertions, 67 deletions
diff --git a/tests/protocol/test_hostplugin.py b/tests/protocol/test_hostplugin.py index 1298fdc..ef91998 100644 --- a/tests/protocol/test_hostplugin.py +++ b/tests/protocol/test_hostplugin.py @@ -15,123 +15,314 @@ # Requires Python 2.4+ and Openssl 1.0+ # -import unittest +import base64 +import json +import sys + + + +if sys.version_info[0] == 3: + import http.client as httpclient + bytebuffer = memoryview +elif sys.version_info[0] == 2: + import httplib as httpclient + bytebuffer = buffer import azurelinuxagent.common.protocol.restapi as restapi import azurelinuxagent.common.protocol.wire as wire import azurelinuxagent.common.protocol.hostplugin as hostplugin + +from azurelinuxagent.common import event +from azurelinuxagent.common.protocol.hostplugin import API_VERSION +from azurelinuxagent.common.utils import restutil + from tests.protocol.mockwiredata import WireProtocolData, DATA_FILE from tests.tools import * -wireserver_url = "168.63.129.16" +hostplugin_status_url = "http://168.63.129.16:32526/status" sas_url = "http://sas_url" -testtype = 'BlockBlob' +wireserver_url = "168.63.129.16" + +block_blob_type = 'BlockBlob' +page_blob_type = 'PageBlob' + api_versions = '["2015-09-01"]' +storage_version = "2014-02-14" +faux_status = "{ 'dummy' : 'data' }" +faux_status_b64 = base64.b64encode(bytes(bytearray(faux_status, encoding='utf-8'))) +if PY_VERSION_MAJOR > 2: + faux_status_b64 = faux_status_b64.decode('utf-8') class TestHostPlugin(AgentTestCase): + + def _compare_data(self, actual, expected): + for k in iter(expected.keys()): + if k == 'content' or k == 'requestUri': + if actual[k] != expected[k]: + print("Mismatch: Actual '{0}'='{1}', " \ + "Expected '{0}'='{3}'".format( + k, actual[k], expected[k])) + return False + elif k == 'headers': + for h in expected['headers']: + if not (h in actual['headers']): + print("Missing Header: '{0}'".format(h)) + return False + else: + print("Unexpected Key: '{0}'".format(k)) + return False + return True + + def _hostplugin_data(self, blob_headers, content=None): + headers = [] + for name in iter(blob_headers.keys()): + headers.append({ + 'headerName': name, + 'headerValue': blob_headers[name] + }) + + data = { + 'requestUri': sas_url, + 'headers': headers + } + if not content is None: + s = base64.b64encode(bytes(content)) + if PY_VERSION_MAJOR > 2: + s = s.decode('utf-8') + data['content'] = s + return data + + def _hostplugin_headers(self, goal_state): + return { + 'x-ms-version': '2015-09-01', + 'Content-type': 'application/json', + 'x-ms-containerid': goal_state.container_id, + 'x-ms-host-config-name': goal_state.role_config_name + } + + def _validate_hostplugin_args(self, args, goal_state, exp_method, exp_url, exp_data): + args, kwargs = args + self.assertEqual(exp_method, args[0]) + self.assertEqual(exp_url, args[1]) + self.assertTrue(self._compare_data(json.loads(args[2]), exp_data)) + + headers = kwargs['headers'] + self.assertEqual(headers['x-ms-containerid'], goal_state.container_id) + self.assertEqual(headers['x-ms-host-config-name'], goal_state.role_config_name) + def test_fallback(self): """ - Validate fallback to upload status using HostGAPlugin is happening when status reporting via - default method is unsuccessful + Validate fallback to upload status using HostGAPlugin is happening when + status reporting via default method is unsuccessful """ test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state) - - with patch.object(wire.HostPluginProtocol, "put_vm_status") as patch_put: + status = restapi.VMStatus(status="Ready", message="Guest Agent is running") + with patch.object(wire.HostPluginProtocol, + "ensure_initialized", + return_value=True): with patch.object(wire.StatusBlob, "upload", return_value=False) as patch_upload: - wire_protocol_client = wire.WireProtocol(wireserver_url).client - wire_protocol_client.get_goal_state = Mock(return_value=test_goal_state) - wire_protocol_client.ext_conf = wire.ExtensionsConfig(None) - wire_protocol_client.ext_conf.status_upload_blob = sas_url - wire_protocol_client.upload_status_blob() - self.assertTrue(patch_put.call_count == 1, - "Fallback was not engaged") - self.assertTrue(patch_put.call_args[0][1] == sas_url) + with patch.object(wire.HostPluginProtocol, + "_put_page_blob_status") as patch_put: + wire_protocol_client = wire.WireProtocol(wireserver_url).client + wire_protocol_client.get_goal_state = Mock(return_value=test_goal_state) + wire_protocol_client.ext_conf = wire.ExtensionsConfig(None) + wire_protocol_client.ext_conf.status_upload_blob = sas_url + wire_protocol_client.status_blob.set_vm_status(status) + wire_protocol_client.upload_status_blob() + self.assertTrue(patch_put.call_count == 1, + "Fallback was not engaged") + self.assertTrue(patch_put.call_args[0][0] == sas_url) + self.assertTrue(wire.HostPluginProtocol.is_default_channel()) + wire.HostPluginProtocol.set_default_channel(False) + + def test_fallback_failure(self): + """ + Validate that when host plugin fails, the default channel is reset + """ + test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state) + status = restapi.VMStatus(status="Ready", + message="Guest Agent is running") + with patch.object(wire.HostPluginProtocol, + "ensure_initialized", + return_value=True): + with patch.object(wire.StatusBlob, + "upload", + return_value=False): + with patch.object(wire.HostPluginProtocol, + "_put_page_blob_status", + side_effect=wire.HttpError("put failure")) as patch_put: + client = wire.WireProtocol(wireserver_url).client + client.get_goal_state = Mock(return_value=test_goal_state) + client.ext_conf = wire.ExtensionsConfig(None) + client.ext_conf.status_upload_blob = sas_url + client.status_blob.set_vm_status(status) + client.upload_status_blob() + self.assertTrue(patch_put.call_count == 1, + "Fallback was not engaged") + self.assertFalse(wire.HostPluginProtocol.is_default_channel()) + + def test_put_status_error_reporting(self): + """ + Validate the telemetry when uploading status fails + """ + test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state) + status = restapi.VMStatus(status="Ready", + message="Guest Agent is running") + with patch.object(wire.StatusBlob, + "upload", + return_value=False): + wire_protocol_client = wire.WireProtocol(wireserver_url).client + wire_protocol_client.get_goal_state = Mock(return_value=test_goal_state) + wire_protocol_client.ext_conf = wire.ExtensionsConfig(None) + wire_protocol_client.ext_conf.status_upload_blob = sas_url + wire_protocol_client.status_blob.set_vm_status(status) + put_error = wire.HttpError("put status http error") + with patch.object(event, + "add_event") as patch_add_event: + with patch.object(restutil, + "http_put", + side_effect=put_error) as patch_http_put: + with patch.object(wire.HostPluginProtocol, + "ensure_initialized", return_value=True): + wire_protocol_client.upload_status_blob() + self.assertFalse(wire.HostPluginProtocol.is_default_channel()) + self.assertTrue(patch_add_event.call_count == 1) + def test_validate_http_request(self): """Validate correct set of data is sent to HostGAPlugin when reporting VM status""" - from azurelinuxagent.common.protocol.hostplugin import API_VERSION - from azurelinuxagent.common.utils import restutil - exp_method = 'PUT' - exp_url = 'http://{0}:32526/status'.format(wireserver_url) - exp_data = '{"content": "eyJkdW1teSI6ICJkYXRhIn0=", "headers": [{"headerName": ' \ - '"x-ms-version", "headerValue": "2014-02-14"}, ' \ - '{"headerName": "x-ms-blob-type", "headerValue": "BlockBlob"}], ' \ - '"requestUri": "http://sas_url"}' + + wire_protocol_client = wire.WireProtocol(wireserver_url).client test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state) + status_blob = wire_protocol_client.status_blob + status_blob.data = faux_status + status_blob.vm_status = restapi.VMStatus(message="Ready", status="Ready") + + exp_method = 'PUT' + exp_url = hostplugin_status_url + exp_data = self._hostplugin_data( + status_blob.get_block_blob_headers(len(faux_status)), + bytearray(faux_status, encoding='utf-8')) + with patch.object(restutil, "http_request") as patch_http: - wire_protocol_client = wire.WireProtocol(wireserver_url).client wire_protocol_client.get_goal_state = Mock(return_value=test_goal_state) plugin = wire_protocol_client.get_host_plugin() - blob = wire_protocol_client.status_blob - blob.vm_status = restapi.VMStatus(message="Ready", status="Ready") - blob.data = '{"dummy": "data"}' + with patch.object(plugin, 'get_api_versions') as patch_api: patch_api.return_value = API_VERSION - plugin.put_vm_status(blob, sas_url, testtype) - self.assertTrue(patch_http.call_count == 1) - self.assertTrue(patch_http.call_args[0][0] == exp_method) - self.assertTrue(patch_http.call_args[0][1] == exp_url) - self.assertTrue(patch_http.call_args[0][2] == exp_data) + plugin.put_vm_status(status_blob, sas_url, block_blob_type) - # Assert headers - headers = patch_http.call_args[1]['headers'] - self.assertEqual(headers['x-ms-containerid'], test_goal_state.container_id) - self.assertEqual(headers['x-ms-host-config-name'], test_goal_state.role_config_name) + self.assertTrue(patch_http.call_count == 1) + self._validate_hostplugin_args( + patch_http.call_args_list[0], + test_goal_state, + exp_method, exp_url, exp_data) def test_no_fallback(self): """ - Validate fallback to upload status using HostGAPlugin is not happening when status reporting via - default method is successful + Validate fallback to upload status using HostGAPlugin is not happening + when status reporting via default method is successful """ - with patch.object(wire.HostPluginProtocol, - "put_vm_status") as patch_put: + vmstatus = restapi.VMStatus(message="Ready", status="Ready") + with patch.object(wire.HostPluginProtocol, "put_vm_status") as patch_put: with patch.object(wire.StatusBlob, "upload") as patch_upload: patch_upload.return_value = True wire_protocol_client = wire.WireProtocol(wireserver_url).client wire_protocol_client.ext_conf = wire.ExtensionsConfig(None) wire_protocol_client.ext_conf.status_upload_blob = sas_url + wire_protocol_client.status_blob.vm_status = vmstatus wire_protocol_client.upload_status_blob() - self.assertTrue(patch_put.call_count == 0, - "Fallback was engaged") + self.assertTrue(patch_put.call_count == 0, "Fallback was engaged") - def test_validate_http_put(self): + def test_validate_block_blob(self): """Validate correct set of data is sent to HostGAPlugin when reporting VM status""" + wire_protocol_client = wire.WireProtocol(wireserver_url).client test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state) - expected_url = "http://168.63.129.16:32526/status" - expected_headers = {'x-ms-version': '2015-09-01', - "Content-type": "application/json", - "x-ms-containerid": test_goal_state.container_id, - "x-ms-host-config-name": test_goal_state.role_config_name} - expected_content = '{"content": "eyJkdW1teSI6ICJkYXRhIn0=", ' \ - '"headers": [{"headerName": "x-ms-version", ' \ - '"headerValue": "2014-02-14"}, ' \ - '{"headerName": "x-ms-blob-type", "headerValue": ' \ - '"BlockBlob"}], ' \ - '"requestUri": "http://sas_url"}' host_client = wire.HostPluginProtocol(wireserver_url, test_goal_state.container_id, test_goal_state.role_config_name) self.assertFalse(host_client.is_initialized) self.assertTrue(host_client.api_versions is None) - status_blob = wire.StatusBlob(None) + + status_blob = wire_protocol_client.status_blob + status_blob.data = faux_status + status_blob.type = block_blob_type status_blob.vm_status = restapi.VMStatus(message="Ready", status="Ready") - status_blob.data = '{"dummy": "data"}' - status_blob.type = "BlockBlob" - with patch.object(wire.HostPluginProtocol, + + exp_method = 'PUT' + exp_url = hostplugin_status_url + exp_data = self._hostplugin_data( + status_blob.get_block_blob_headers(len(faux_status)), + bytearray(faux_status, encoding='utf-8')) + + with patch.object(restutil, "http_request") as patch_http: + with patch.object(wire.HostPluginProtocol, "get_api_versions") as patch_get: - patch_get.return_value = api_versions - with patch.object(restapi.restutil, "http_put") as patch_put: - patch_put.return_value = MagicMock() + patch_get.return_value = api_versions + host_client.put_vm_status(status_blob, sas_url) + + self.assertTrue(patch_http.call_count == 1) + self._validate_hostplugin_args( + patch_http.call_args_list[0], + test_goal_state, + exp_method, exp_url, exp_data) + + def test_validate_page_blobs(self): + """Validate correct set of data is sent for page blobs""" + wire_protocol_client = wire.WireProtocol(wireserver_url).client + test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state) + + host_client = wire.HostPluginProtocol(wireserver_url, + test_goal_state.container_id, + test_goal_state.role_config_name) + + self.assertFalse(host_client.is_initialized) + self.assertTrue(host_client.api_versions is None) + + status_blob = wire_protocol_client.status_blob + status_blob.data = faux_status + status_blob.type = page_blob_type + status_blob.vm_status = restapi.VMStatus(message="Ready", status="Ready") + + exp_method = 'PUT' + exp_url = hostplugin_status_url + + page_status = bytearray(status_blob.data, encoding='utf-8') + page_size = int((len(page_status) + 511) / 512) * 512 + page_status = bytearray(status_blob.data.ljust(page_size), encoding='utf-8') + page = bytearray(page_size) + page[0: page_size] = page_status[0: len(page_status)] + mock_response = MockResponse('', httpclient.OK) + + with patch.object(restutil, "http_request", + return_value=mock_response) as patch_http: + with patch.object(wire.HostPluginProtocol, + "get_api_versions") as patch_get: + patch_get.return_value = api_versions host_client.put_vm_status(status_blob, sas_url) - self.assertTrue(host_client.is_initialized) - self.assertFalse(host_client.api_versions is None) - self.assertTrue(patch_put.call_count == 1) - self.assertTrue(patch_put.call_args[0][0] == expected_url) - self.assertTrue(patch_put.call_args[1]['data'] == expected_content) - self.assertTrue(patch_put.call_args[1]['headers'] == expected_headers) + + self.assertTrue(patch_http.call_count == 2) + + exp_data = self._hostplugin_data( + status_blob.get_page_blob_create_headers( + page_size)) + self._validate_hostplugin_args( + patch_http.call_args_list[0], + test_goal_state, + exp_method, exp_url, exp_data) + + exp_data = self._hostplugin_data( + status_blob.get_page_blob_page_headers( + 0, page_size), + page) + exp_data['requestUri'] += "?comp=page" + self._validate_hostplugin_args( + patch_http.call_args_list[1], + test_goal_state, + exp_method, exp_url, exp_data) def test_validate_get_extension_artifacts(self): test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state) @@ -155,6 +346,7 @@ class TestHostPlugin(AgentTestCase): for k in expected_headers: self.assertTrue(k in actual_headers) self.assertEqual(expected_headers[k], actual_headers[k]) + class MockResponse: def __init__(self, body, status_code): |