From 285aeff238fc4c2c82278fcaab2e34875daaac28 Mon Sep 17 00:00:00 2001 From: Brendan Dixon Date: Fri, 11 Aug 2017 13:20:53 -0700 Subject: [PATCH] [#783] -- The agent fails to use the standard Linux environment variables for HTTP proxy [#830] -- Ensure VM identifier is properly ordered Signed-off-by: Brendan Dixon --- README.md | 48 ++++++++-------- azurelinuxagent/common/osutil/default.py | 50 +++++++++++++++-- azurelinuxagent/common/protocol/restapi.py | 2 +- azurelinuxagent/common/protocol/wire.py | 14 ++--- azurelinuxagent/common/utils/restutil.py | 65 +++++++++++++++------- azurelinuxagent/common/utils/textutil.py | 11 ++++ azurelinuxagent/ga/update.py | 6 +- azurelinuxagent/pa/provision/default.py | 2 +- tests/common/osutil/test_default.py | 63 +++++++++++++++++++-- tests/ga/test_update.py | 6 +- tests/pa/test_provision.py | 9 ++- tests/protocol/test_wire.py | 16 +++--- tests/utils/test_rest_util.py | 65 ++++++++++++++++++++++ tests/utils/test_text_util.py | 31 +++++++++++ 14 files changed, 308 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 2ed8799..88ad37a 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ The information flow from the platform to the agent occurs via two channels: * A TCP endpoint exposing a REST API used to obtain deployment and topology configuration. +The agent will use an HTTP proxy if provided via the `http_proxy` (for `http` requests) or +`https_proxy` (for `https` requests) environment variables. The `HttpProxy.Host` and +`HttpProxy.Port` configuration variables (see below), if used, will override the environment +settings. Due to limitations of Python, the agent *does not* support HTTP proxies requiring +authentication. + ### REQUIREMENTS @@ -58,21 +64,6 @@ Linux Agent. Please note that this list may differ from the official list of supported systems on the Microsoft Azure Platform as described here: http://support.microsoft.com/kb/2805216 -Supported Linux Distributions: - * Archlinux - * CoreOS - * CentOS 6.2+ - * Red Hat Enterprise Linux 6.7+ - * Debian 7.0+ - * Ubuntu 12.04+ - * openSUSE 12.3+ - * SLES 11 SP2+ - * Oracle Linux 6.4+ - -Other Supported Systems: - * FreeBSD 10+ (Azure Linux Agent v2.0.10+) - * OpenBSD 6+ (Azure Linux Agent v2.2.11+) - Waagent depends on some system packages in order to function properly: * Python 2.6+ @@ -192,6 +183,7 @@ ResourceDisk.EnableSwap=n ResourceDisk.SwapSizeMB=0 LBProbeResponder=y Logs.Verbose=n +OS.AllowHTTP=n OS.RootDeviceScsiTimeout=300 OS.EnableFIPS=n OS.OpensslPath=None @@ -351,6 +343,16 @@ _Default: n_ If set, log verbosity is boosted. Waagent logs to /var/log/waagent.log and leverages the system logrotate functionality to rotate logs. +* __OS.AllowHTTP__ +_Type: Boolean_ +_Default: n_ + +If set to `y` and SSL support is not compiled into Python, the agent will fall-back to +use HTTP. Otherwise, if SSL support is not compiled into Python, the agent will fail +all HTTPS requests. + +Note: Allowing HTTP may unintentionally expose secure data. + * __OS.EnableRDMA__ _Type: Boolean_ _Default: n_ @@ -358,9 +360,9 @@ _Default: n_ If set, the agent will attempt to install and then load an RDMA kernel driver that matches the version of the firmware on the underlying hardware. -* __OS.EnableFIPS__ -_Type: Boolean_ -_Default: n_ +* __OS.EnableFIPS__ +_Type: Boolean_ +_Default: n_ If set, the agent will emit into the environment "OPENSSL_FIPS=1" when executing OpenSSL commands. This signals OpenSSL to use any installed FIPS-compliant libraries. @@ -381,9 +383,9 @@ _Default: None_ This can be used to specify an alternate path for the openssl binary to use for cryptographic operations. -* __OS.SshDir__ -_Type: String_ -_Default: "/etc/ssh"_ +* __OS.SshDir__ +_Type: String_ +_Default: `/etc/ssh`_ This option can be used to override the normal location of the SSH configuration directory. @@ -392,7 +394,9 @@ directory. _Type: String_ _Default: None_ -If set, the agent will use this proxy server to access the internet. +If set, the agent will use this proxy server to access the internet. These values +*will* override the `http_proxy` or `https_proxy` environment variables. Lastly, +`HttpProxy.Host` is required (if to be used) and `HttpProxy.Port` is optional. ### APPENDIX diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py index 61367d3..ead1385 100644 --- a/azurelinuxagent/common/osutil/default.py +++ b/azurelinuxagent/common/osutil/default.py @@ -53,7 +53,7 @@ if needed. DMIDECODE_CMD = 'dmidecode --string system-uuid' PRODUCT_ID_FILE = '/sys/class/dmi/id/product_uuid' UUID_PATTERN = re.compile( - '^\s*[A-F0-9]{8}(?:\-[A-F0-9]{4}){3}\-[A-F0-9]{12}\s*$', + r'^\s*[A-F0-9]{8}(?:\-[A-F0-9]{4}){3}\-[A-F0-9]{12}\s*$', re.IGNORECASE) class DefaultOSUtil(object): @@ -63,6 +63,43 @@ class DefaultOSUtil(object): self.selinux = None self.disable_route_warning = False + def _correct_instance_id(self, id): + ''' + Azure stores the instance ID with an incorrect byte ordering for the + first parts. For example, the ID returned by the metadata service: + + D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8 + + will be found as: + + 544CDFD0-CB4E-4B4A-9954-5BDF3ED5C3B8 + + This code corrects the byte order such that it is consistent with + that returned by the metadata service. + ''' + + if not UUID_PATTERN.match(id): + return id + + parts = id.split('-') + return '-'.join([ + textutil.swap_hexstring(parts[0], width=2), + textutil.swap_hexstring(parts[1], width=2), + textutil.swap_hexstring(parts[2], width=2), + parts[3], + parts[4] + ]) + + def is_current_instance_id(self, id_that): + ''' + Compare two instance IDs for equality, but allow that some IDs + may have been persisted using the incorrect byte ordering. + ''' + id_this = self.get_instance_id() + return id_that == id_this or \ + id_that == self._correct_instance_id(id_this) + + def get_agent_conf_file_path(self): return self.agent_conf_file_path @@ -74,13 +111,14 @@ class DefaultOSUtil(object): If nothing works (for old VMs), return the empty string ''' if os.path.isfile(PRODUCT_ID_FILE): - return fileutil.read_file(PRODUCT_ID_FILE).strip() + s = fileutil.read_file(PRODUCT_ID_FILE).strip() - rc, s = shellutil.run_get_output(DMIDECODE_CMD) - if rc != 0 or UUID_PATTERN.match(s) is None: - return "" + else: + rc, s = shellutil.run_get_output(DMIDECODE_CMD) + if rc != 0 or UUID_PATTERN.match(s) is None: + return "" - return s.strip() + return self._correct_instance_id(s.strip()) def get_userentry(self, username): try: diff --git a/azurelinuxagent/common/protocol/restapi.py b/azurelinuxagent/common/protocol/restapi.py index dbacfd9..1ec3e21 100644 --- a/azurelinuxagent/common/protocol/restapi.py +++ b/azurelinuxagent/common/protocol/restapi.py @@ -317,7 +317,7 @@ class Protocol(DataContract): def download_ext_handler_pkg(self, uri, headers=None): try: - resp = restutil.http_get(uri, chk_proxy=True, headers=headers) + resp = restutil.http_get(uri, use_proxy=True, headers=headers) if restutil.request_succeeded(resp): return resp.read() except Exception as e: diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py index 4b9ef56..0142c89 100644 --- a/azurelinuxagent/common/protocol/wire.py +++ b/azurelinuxagent/common/protocol/wire.py @@ -541,7 +541,7 @@ class WireClient(object): try: # Never use the HTTP proxy for wireserver - kwargs['chk_proxy'] = False + kwargs['use_proxy'] = False resp = http_req(*args, **kwargs) except Exception as e: raise ProtocolError("[Wireserver Exception] {0}".format( @@ -591,8 +591,8 @@ class WireClient(object): @staticmethod def call_storage_service(http_req, *args, **kwargs): # Default to use the configured HTTP proxy - if not 'chk_proxy' in kwargs or kwargs['chk_proxy'] is None: - kwargs['chk_proxy'] = True + if not 'use_proxy' in kwargs or kwargs['use_proxy'] is None: + kwargs['use_proxy'] = True return http_req(*args, **kwargs) @@ -613,7 +613,7 @@ class WireClient(object): try: host = self.get_host_plugin() uri, headers = host.get_artifact_request(version.uri) - response = self.fetch(uri, headers, chk_proxy=False) + response = self.fetch(uri, headers, use_proxy=False) # If the HostPlugin rejects the request, # let the error continue, but set to use the HostPlugin @@ -632,14 +632,14 @@ class WireClient(object): raise ProtocolError("Failed to fetch manifest from all sources") - def fetch(self, uri, headers=None, chk_proxy=None): + def fetch(self, uri, headers=None, use_proxy=None): logger.verbose("Fetch [{0}] with headers [{1}]", uri, headers) try: resp = self.call_storage_service( restutil.http_get, uri, headers=headers, - chk_proxy=chk_proxy) + use_proxy=use_proxy) if restutil.request_failed(resp): msg = "[Storage Failed] URI {0} ".format(uri) @@ -1054,7 +1054,7 @@ class WireClient(object): host = self.get_host_plugin() uri, headers = host.get_artifact_request(blob) - config = self.fetch(uri, headers, chk_proxy=False) + config = self.fetch(uri, headers, use_proxy=False) profile = self.decode_config(config) if not textutil.is_str_none_or_whitespace(profile): diff --git a/azurelinuxagent/common/utils/restutil.py b/azurelinuxagent/common/utils/restutil.py index 7214e1a..b671c31 100644 --- a/azurelinuxagent/common/utils/restutil.py +++ b/azurelinuxagent/common/utils/restutil.py @@ -17,6 +17,7 @@ # Requires Python 2.4+ and Openssl 1.0+ # +import os import time import traceback @@ -83,6 +84,9 @@ RETRY_IOERRORS = [ 112 # EHOSTDOWN -- Host is down ] +HTTP_PROXY_ENV = "http_proxy" +HTTPS_PROXY_ENV = "https_proxy" + def _is_retry_status(status, retry_codes=RETRY_CODES): return status in retry_codes @@ -109,9 +113,25 @@ def _parse_url(url): return o.hostname, o.port, secure, rel_uri -def _get_http_proxy(): +def _get_http_proxy(secure=False): + # Prefer the configuration settings over environment variables host = conf.get_httpproxy_host() - port = conf.get_httpproxy_port() + port = None + + if not host is None: + port = conf.get_httpproxy_port() + + else: + http_proxy_env = HTTPS_PROXY_ENV if secure else HTTP_PROXY_ENV + http_proxy_url = None + for v in [http_proxy_env, http_proxy_env.upper()]: + if v in os.environ: + http_proxy_url = os.environ[v] + break + + if not http_proxy_url is None: + host, port, _, _ = _parse_url(http_proxy_url) + return host, port @@ -160,7 +180,7 @@ def _http_request(method, host, rel_uri, port=None, data=None, secure=False, def http_request(method, url, data, headers=None, - chk_proxy=False, + use_proxy=False, max_retry=DEFAULT_RETRIES, retry_codes=RETRY_CODES, retry_delay=SHORT_DELAY_IN_SECONDS): @@ -169,10 +189,13 @@ def http_request(method, host, port, secure, rel_uri = _parse_url(url) - # Check proxy + # Use the HTTP(S) proxy proxy_host, proxy_port = (None, None) - if chk_proxy: - proxy_host, proxy_port = _get_http_proxy() + if use_proxy: + proxy_host, proxy_port = _get_http_proxy(secure=secure) + + if proxy_host or proxy_port: + logger.verbose("HTTP proxy: [{0}:{1}]", proxy_host, proxy_port) # If httplib module is not built with ssl support, # fallback to HTTP if allowed @@ -187,8 +210,11 @@ def http_request(method, # If httplib module doesn't support HTTPS tunnelling, # fallback to HTTP if allowed - if secure and proxy_host is not None and proxy_port is not None \ - and not hasattr(httpclient.HTTPSConnection, "set_tunnel"): + if secure and \ + proxy_host is not None and \ + proxy_port is not None \ + and not hasattr(httpclient.HTTPSConnection, "set_tunnel"): + if not conf.get_allow_http(): raise HttpError("HTTPS tunnelling is unavailable and required") @@ -197,9 +223,6 @@ def http_request(method, logger.warn("Python does not support HTTPS tunnelling") SECURE_WARNING_EMITTED = True - if proxy_host or proxy_port: - logger.verbose("HTTP proxy: [{0}:{1}]", proxy_host, proxy_port) - msg = '' attempt = 0 delay = retry_delay @@ -258,61 +281,61 @@ def http_request(method, raise HttpError(msg) -def http_get(url, headers=None, chk_proxy=False, +def http_get(url, headers=None, use_proxy=False, max_retry=DEFAULT_RETRIES, retry_codes=RETRY_CODES, retry_delay=SHORT_DELAY_IN_SECONDS): return http_request("GET", url, None, headers=headers, - chk_proxy=chk_proxy, + use_proxy=use_proxy, max_retry=max_retry, retry_codes=retry_codes, retry_delay=retry_delay) -def http_head(url, headers=None, chk_proxy=False, +def http_head(url, headers=None, use_proxy=False, max_retry=DEFAULT_RETRIES, retry_codes=RETRY_CODES, retry_delay=SHORT_DELAY_IN_SECONDS): return http_request("HEAD", url, None, headers=headers, - chk_proxy=chk_proxy, + use_proxy=use_proxy, max_retry=max_retry, retry_codes=retry_codes, retry_delay=retry_delay) -def http_post(url, data, headers=None, chk_proxy=False, +def http_post(url, data, headers=None, use_proxy=False, max_retry=DEFAULT_RETRIES, retry_codes=RETRY_CODES, retry_delay=SHORT_DELAY_IN_SECONDS): return http_request("POST", url, data, headers=headers, - chk_proxy=chk_proxy, + use_proxy=use_proxy, max_retry=max_retry, retry_codes=retry_codes, retry_delay=retry_delay) -def http_put(url, data, headers=None, chk_proxy=False, +def http_put(url, data, headers=None, use_proxy=False, max_retry=DEFAULT_RETRIES, retry_codes=RETRY_CODES, retry_delay=SHORT_DELAY_IN_SECONDS): return http_request("PUT", url, data, headers=headers, - chk_proxy=chk_proxy, + use_proxy=use_proxy, max_retry=max_retry, retry_codes=retry_codes, retry_delay=retry_delay) -def http_delete(url, headers=None, chk_proxy=False, +def http_delete(url, headers=None, use_proxy=False, max_retry=DEFAULT_RETRIES, retry_codes=RETRY_CODES, retry_delay=SHORT_DELAY_IN_SECONDS): return http_request("DELETE", url, None, headers=headers, - chk_proxy=chk_proxy, + use_proxy=use_proxy, max_retry=max_retry, retry_codes=retry_codes, retry_delay=retry_delay) diff --git a/azurelinuxagent/common/utils/textutil.py b/azurelinuxagent/common/utils/textutil.py index d552ce0..7e244fc 100644 --- a/azurelinuxagent/common/utils/textutil.py +++ b/azurelinuxagent/common/utils/textutil.py @@ -19,6 +19,7 @@ import base64 import crypt import random +import re import string import struct import sys @@ -322,6 +323,16 @@ def safe_shlex_split(s): return shlex.split(s.encode('utf-8')) return shlex.split(s) +def swap_hexstring(s, width=2): + r = len(s) % width + if r != 0: + s = ('0' * (width - (len(s) % width))) + s + + return ''.join(reversed( + re.findall( + r'[a-f0-9]{{{0}}}'.format(width), + s, + re.IGNORECASE))) def parse_json(json_str): """ diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py index 9ad016f..13ec99c 100644 --- a/azurelinuxagent/ga/update.py +++ b/azurelinuxagent/ga/update.py @@ -810,7 +810,7 @@ class GuestAgent(object): uri, headers = self.host.get_artifact_request(uri.uri, self.host.manifest_uri) try: - if self._fetch(uri, headers=headers, chk_proxy=False): + if self._fetch(uri, headers=headers, use_proxy=False): if not HostPluginProtocol.is_default_channel(): logger.verbose("Setting host plugin as default channel") HostPluginProtocol.set_default_channel(True) @@ -839,10 +839,10 @@ class GuestAgent(object): return - def _fetch(self, uri, headers=None, chk_proxy=True): + def _fetch(self, uri, headers=None, use_proxy=True): package = None try: - resp = restutil.http_get(uri, chk_proxy=chk_proxy, headers=headers) + resp = restutil.http_get(uri, use_proxy=use_proxy, headers=headers) if restutil.request_succeeded(resp): package = resp.read() fileutil.write_file(self.get_agent_pkg_path(), diff --git a/azurelinuxagent/pa/provision/default.py b/azurelinuxagent/pa/provision/default.py index 6764837..70720a5 100644 --- a/azurelinuxagent/pa/provision/default.py +++ b/azurelinuxagent/pa/provision/default.py @@ -170,7 +170,7 @@ class ProvisionHandler(object): return False s = fileutil.read_file(self.provisioned_file_path()).strip() - if s != self.osutil.get_instance_id(): + if not self.osutil.is_current_instance_id(s): if len(s) > 0: logger.warn("VM is provisioned, " "but the VM unique identifier has changed -- " diff --git a/tests/common/osutil/test_default.py b/tests/common/osutil/test_default.py index 9fa2324..9a2628e 100644 --- a/tests/common/osutil/test_default.py +++ b/tests/common/osutil/test_default.py @@ -377,23 +377,50 @@ Match host 192.168.1.2\n\ conf.get_sshd_conf_file_path(), expected_output) + def test_correct_instance_id(self): + util = osutil.DefaultOSUtil() + self.assertEqual( + "12345678-1234-1234-1234-123456789012", + util._correct_instance_id("78563412-3412-3412-1234-123456789012")) + self.assertEqual( + "D0DF4C54-4ECB-4A4B-9954-5BDF3ED5C3B8", + util._correct_instance_id("544CDFD0-CB4E-4B4A-9954-5BDF3ED5C3B8")) + @patch('os.path.isfile', return_value=True) @patch('azurelinuxagent.common.utils.fileutil.read_file', - return_value="B9F3C233-9913-9F42-8EB3-BA656DF32502") + return_value="33C2F3B9-1399-429F-8EB3-BA656DF32502") def test_get_instance_id_from_file(self, mock_read, mock_isfile): util = osutil.DefaultOSUtil() self.assertEqual( - "B9F3C233-9913-9F42-8EB3-BA656DF32502", + util.get_instance_id(), + "B9F3C233-9913-9F42-8EB3-BA656DF32502") + + @patch('os.path.isfile', return_value=True) + @patch('azurelinuxagent.common.utils.fileutil.read_file', + return_value="") + def test_get_instance_id_empty_from_file(self, mock_read, mock_isfile): + util = osutil.DefaultOSUtil() + self.assertEqual( + "", + util.get_instance_id()) + + @patch('os.path.isfile', return_value=True) + @patch('azurelinuxagent.common.utils.fileutil.read_file', + return_value="Value") + def test_get_instance_id_malformed_from_file(self, mock_read, mock_isfile): + util = osutil.DefaultOSUtil() + self.assertEqual( + "Value", util.get_instance_id()) @patch('os.path.isfile', return_value=False) @patch('azurelinuxagent.common.utils.shellutil.run_get_output', - return_value=[0, 'B9F3C233-9913-9F42-8EB3-BA656DF32502']) + return_value=[0, '33C2F3B9-1399-429F-8EB3-BA656DF32502']) def test_get_instance_id_from_dmidecode(self, mock_shell, mock_isfile): util = osutil.DefaultOSUtil() self.assertEqual( - "B9F3C233-9913-9F42-8EB3-BA656DF32502", - util.get_instance_id()) + util.get_instance_id(), + "B9F3C233-9913-9F42-8EB3-BA656DF32502") @patch('os.path.isfile', return_value=False) @patch('azurelinuxagent.common.utils.shellutil.run_get_output', @@ -409,6 +436,32 @@ Match host 192.168.1.2\n\ util = osutil.DefaultOSUtil() self.assertEqual("", util.get_instance_id()) + @patch('os.path.isfile', return_value=True) + @patch('azurelinuxagent.common.utils.fileutil.read_file') + def test_is_current_instance_id_from_file(self, mock_read, mock_isfile): + util = osutil.DefaultOSUtil() + + mock_read.return_value = "B9F3C233-9913-9F42-8EB3-BA656DF32502" + self.assertTrue(util.is_current_instance_id( + "B9F3C233-9913-9F42-8EB3-BA656DF32502")) + + mock_read.return_value = "33C2F3B9-1399-429F-8EB3-BA656DF32502" + self.assertTrue(util.is_current_instance_id( + "B9F3C233-9913-9F42-8EB3-BA656DF32502")) + + @patch('os.path.isfile', return_value=False) + @patch('azurelinuxagent.common.utils.shellutil.run_get_output') + def test_is_current_instance_id_from_dmidecode(self, mock_shell, mock_isfile): + util = osutil.DefaultOSUtil() + + mock_shell.return_value = [0, 'B9F3C233-9913-9F42-8EB3-BA656DF32502'] + self.assertTrue(util.is_current_instance_id( + "B9F3C233-9913-9F42-8EB3-BA656DF32502")) + + mock_shell.return_value = [0, '33C2F3B9-1399-429F-8EB3-BA656DF32502'] + self.assertTrue(util.is_current_instance_id( + "B9F3C233-9913-9F42-8EB3-BA656DF32502")) + @patch('azurelinuxagent.common.conf.get_sudoers_dir') def test_conf_sudoer(self, mock_dir): tmp_dir = tempfile.mkdtemp() diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py index 5473a7e..a9701d4 100644 --- a/tests/ga/test_update.py +++ b/tests/ga/test_update.py @@ -688,7 +688,7 @@ class TestGuestAgent(UpdateTestCase): self.assertEqual(mock_http_get.call_args_list[3][0][0], art_uri) a, k = mock_http_get.call_args_list[3] - self.assertEqual(False, k['chk_proxy']) + self.assertEqual(False, k['use_proxy']) # ensure fallback works as expected with patch.object(HostPluginProtocol, @@ -698,14 +698,14 @@ class TestGuestAgent(UpdateTestCase): self.assertEqual(mock_http_get.call_count, 6) a, k = mock_http_get.call_args_list[3] - self.assertEqual(False, k['chk_proxy']) + self.assertEqual(False, k['use_proxy']) self.assertEqual(mock_http_get.call_args_list[4][0][0], ext_uri) a, k = mock_http_get.call_args_list[4] self.assertEqual(mock_http_get.call_args_list[5][0][0], art_uri) a, k = mock_http_get.call_args_list[5] - self.assertEqual(False, k['chk_proxy']) + self.assertEqual(False, k['use_proxy']) @patch("azurelinuxagent.ga.update.restutil.http_get") def test_ensure_downloaded(self, mock_http_get): diff --git a/tests/pa/test_provision.py b/tests/pa/test_provision.py index 6b24fc2..b88dc1f 100644 --- a/tests/pa/test_provision.py +++ b/tests/pa/test_provision.py @@ -64,27 +64,29 @@ class TestProvision(AgentTestCase): @patch('azurelinuxagent.pa.deprovision.get_deprovision_handler') def test_is_provisioned_is_provisioned(self, mock_deprovision, mock_read, mock_isfile): + ph = ProvisionHandler() ph.osutil = Mock() - ph.osutil.get_instance_id = \ - Mock(return_value="B9F3C233-9913-9F42-8EB3-BA656DF32502") + ph.osutil.is_current_instance_id = Mock(return_value=True) ph.write_provisioned = Mock() deprovision_handler = Mock() mock_deprovision.return_value = deprovision_handler self.assertTrue(ph.is_provisioned()) + ph.osutil.is_current_instance_id.assert_called_once() deprovision_handler.run_changed_unique_id.assert_not_called() @patch('os.path.isfile', return_value=True) @patch('azurelinuxagent.common.utils.fileutil.read_file', - side_effect=["Value"]) + return_value="B9F3C233-9913-9F42-8EB3-BA656DF32502") @patch('azurelinuxagent.pa.deprovision.get_deprovision_handler') def test_is_provisioned_not_deprovisioned(self, mock_deprovision, mock_read, mock_isfile): ph = ProvisionHandler() ph.osutil = Mock() + ph.osutil.is_current_instance_id = Mock(return_value=False) ph.report_ready = Mock() ph.write_provisioned = Mock() @@ -92,6 +94,7 @@ class TestProvision(AgentTestCase): mock_deprovision.return_value = deprovision_handler self.assertTrue(ph.is_provisioned()) + ph.osutil.is_current_instance_id.assert_called_once() deprovision_handler.run_changed_unique_id.assert_called_once() if __name__ == '__main__': diff --git a/tests/protocol/test_wire.py b/tests/protocol/test_wire.py index 4efdc5e..d19bab1 100644 --- a/tests/protocol/test_wire.py +++ b/tests/protocol/test_wire.py @@ -100,32 +100,32 @@ class TestWireProtocolGetters(AgentTestCase): # no kwargs -- Default to True WireClient.call_storage_service(http_req) - # kwargs, no chk_proxy -- Default to True + # kwargs, no use_proxy -- Default to True WireClient.call_storage_service(http_req, url, headers) - # kwargs, chk_proxy None -- Default to True + # kwargs, use_proxy None -- Default to True WireClient.call_storage_service(http_req, url, headers, - chk_proxy=None) + use_proxy=None) - # kwargs, chk_proxy False -- Keep False + # kwargs, use_proxy False -- Keep False WireClient.call_storage_service(http_req, url, headers, - chk_proxy=False) + use_proxy=False) - # kwargs, chk_proxy True -- Keep True + # kwargs, use_proxy True -- Keep True WireClient.call_storage_service(http_req, url, headers, - chk_proxy=True) + use_proxy=True) # assert self.assertTrue(http_patch.call_count == 5) for i in range(0,5): - c = http_patch.call_args_list[i][-1]['chk_proxy'] + c = http_patch.call_args_list[i][-1]['use_proxy'] self.assertTrue(c == (True if i != 3 else False)) def test_status_blob_parsing(self, *args): diff --git a/tests/utils/test_rest_util.py b/tests/utils/test_rest_util.py index ee56bd5..ae1d135 100644 --- a/tests/utils/test_rest_util.py +++ b/tests/utils/test_rest_util.py @@ -15,6 +15,7 @@ # Requires Python 2.4+ and Openssl 1.0+ # +import os import unittest from azurelinuxagent.common.exception import BadRequestError, \ @@ -54,6 +55,70 @@ class TestHttpOperations(AgentTestCase): self.assertEquals(None, host) self.assertEquals(rel_uri, "None") + @patch('azurelinuxagent.common.conf.get_httpproxy_port') + @patch('azurelinuxagent.common.conf.get_httpproxy_host') + def test_get_http_proxy_none_is_default(self, mock_host, mock_port): + mock_host.return_value = None + mock_port.return_value = None + h, p = restutil._get_http_proxy() + self.assertEqual(None, h) + self.assertEqual(None, p) + + @patch('azurelinuxagent.common.conf.get_httpproxy_port') + @patch('azurelinuxagent.common.conf.get_httpproxy_host') + def test_get_http_proxy_configuration_overrides_env(self, mock_host, mock_port): + mock_host.return_value = "host" + mock_port.return_value = None + h, p = restutil._get_http_proxy() + self.assertEqual("host", h) + self.assertEqual(None, p) + mock_host.assert_called_once() + mock_port.assert_called_once() + + @patch('azurelinuxagent.common.conf.get_httpproxy_port') + @patch('azurelinuxagent.common.conf.get_httpproxy_host') + def test_get_http_proxy_configuration_requires_host(self, mock_host, mock_port): + mock_host.return_value = None + mock_port.return_value = None + h, p = restutil._get_http_proxy() + self.assertEqual(None, h) + self.assertEqual(None, p) + mock_host.assert_called_once() + mock_port.assert_not_called() + + @patch('azurelinuxagent.common.conf.get_httpproxy_host') + def test_get_http_proxy_http_uses_httpproxy(self, mock_host): + mock_host.return_value = None + with patch.dict(os.environ, { + 'http_proxy' : 'http://foo.com:80', + 'https_proxy' : 'https://bar.com:443' + }): + h, p = restutil._get_http_proxy() + self.assertEqual("foo.com", h) + self.assertEqual(80, p) + + @patch('azurelinuxagent.common.conf.get_httpproxy_host') + def test_get_http_proxy_https_uses_httpsproxy(self, mock_host): + mock_host.return_value = None + with patch.dict(os.environ, { + 'http_proxy' : 'http://foo.com:80', + 'https_proxy' : 'https://bar.com:443' + }): + h, p = restutil._get_http_proxy(secure=True) + self.assertEqual("bar.com", h) + self.assertEqual(443, p) + + @patch('azurelinuxagent.common.conf.get_httpproxy_host') + def test_get_http_proxy_ignores_user_in_httpproxy(self, mock_host): + mock_host.return_value = None + with patch.dict(os.environ, { + 'http_proxy' : 'http://user:pw@foo.com:80' + }): + h, p = restutil._get_http_proxy() + self.assertEqual("foo.com", h) + self.assertEqual(80, p) + + @patch("azurelinuxagent.common.future.httpclient.HTTPSConnection") @patch("azurelinuxagent.common.future.httpclient.HTTPConnection") def test_http_request(self, HTTPConnection, HTTPSConnection): diff --git a/tests/utils/test_text_util.py b/tests/utils/test_text_util.py index 8ae4d14..d182a67 100644 --- a/tests/utils/test_text_util.py +++ b/tests/utils/test_text_util.py @@ -107,6 +107,37 @@ class TestTextUtil(AgentTestCase): "-----END PRIVATE Key-----\n") base64_bytes = textutil.get_bytes_from_pem(content) self.assertEquals("private key", base64_bytes) + + def test_swap_hexstring(self): + data = [ + ['12', 1, '21'], + ['12', 2, '12'], + ['12', 3, '012'], + ['12', 4, '0012'], + + ['123', 1, '321'], + ['123', 2, '2301'], + ['123', 3, '123'], + ['123', 4, '0123'], + + ['1234', 1, '4321'], + ['1234', 2, '3412'], + ['1234', 3, '234001'], + ['1234', 4, '1234'], + + ['abcdef12', 1, '21fedcba'], + ['abcdef12', 2, '12efcdab'], + ['abcdef12', 3, 'f12cde0ab'], + ['abcdef12', 4, 'ef12abcd'], + + ['aBcdEf12', 1, '21fEdcBa'], + ['aBcdEf12', 2, '12EfcdaB'], + ['aBcdEf12', 3, 'f12cdE0aB'], + ['aBcdEf12', 4, 'Ef12aBcd'] + ] + + for t in data: + self.assertEqual(t[2], textutil.swap_hexstring(t[0], width=t[1])) if __name__ == '__main__': unittest.main()