diff options
Diffstat (limited to 'tests/unittests/sources/azure/test_imds.py')
-rw-r--r-- | tests/unittests/sources/azure/test_imds.py | 282 |
1 files changed, 214 insertions, 68 deletions
diff --git a/tests/unittests/sources/azure/test_imds.py b/tests/unittests/sources/azure/test_imds.py index b5a72645..03f66502 100644 --- a/tests/unittests/sources/azure/test_imds.py +++ b/tests/unittests/sources/azure/test_imds.py @@ -3,20 +3,30 @@ import json import logging import math +import re from unittest import mock import pytest import requests from cloudinit.sources.azure import imds -from cloudinit.url_helper import UrlError +from cloudinit.url_helper import UrlError, readurl -MOCKPATH = "cloudinit.sources.azure.imds." +LOG_PATH = "cloudinit.sources.azure.imds" +MOCK_PATH = "cloudinit.sources.azure.imds." + + +class StringMatch: + def __init__(self, regex) -> None: + self.regex = regex + + def __eq__(self, other) -> bool: + return bool(re.match("^" + self.regex + "$", other)) @pytest.fixture -def mock_readurl(): - with mock.patch(MOCKPATH + "readurl", autospec=True) as m: +def wrapped_readurl(): + with mock.patch.object(imds, "readurl", wraps=readurl) as m: yield m @@ -56,54 +66,63 @@ class TestFetchMetadataWithApiFallback: def test_basic( self, caplog, - mock_readurl, + mock_requests_session_request, + wrapped_readurl, ): fake_md = {"foo": {"bar": []}} - mock_readurl.side_effect = [ - mock.Mock(contents=json.dumps(fake_md).encode()), + mock_requests_session_request.side_effect = [ + mock.Mock(content=json.dumps(fake_md)), ] md = imds.fetch_metadata_with_api_fallback() assert md == fake_md - assert mock_readurl.mock_calls == [ + assert wrapped_readurl.mock_calls == [ mock.call( self.default_url, timeout=self.timeout, headers=self.headers, retries=self.retries, - exception_cb=imds._readurl_exception_callback, + exception_cb=mock.ANY, infinite=False, log_req_resp=True, - ), + ) ] - - warnings = [ - x.message for x in caplog.records if x.levelno == logging.WARNING + assert caplog.record_tuples == [ + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[0/11\] open.*"), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch("Read from.*"), + ), ] - assert warnings == [] def test_basic_fallback( self, caplog, - mock_readurl, + mock_requests_session_request, + wrapped_readurl, ): fake_md = {"foo": {"bar": []}} - mock_readurl.side_effect = [ + mock_requests_session_request.side_effect = [ UrlError("No IMDS version", code=400), - mock.Mock(contents=json.dumps(fake_md).encode()), + mock.Mock(content=json.dumps(fake_md)), ] md = imds.fetch_metadata_with_api_fallback() assert md == fake_md - assert mock_readurl.mock_calls == [ + assert wrapped_readurl.mock_calls == [ mock.call( self.default_url, timeout=self.timeout, headers=self.headers, retries=self.retries, - exception_cb=imds._readurl_exception_callback, + exception_cb=mock.ANY, infinite=False, log_req_resp=True, ), @@ -112,18 +131,38 @@ class TestFetchMetadataWithApiFallback: timeout=self.timeout, headers=self.headers, retries=self.retries, - exception_cb=imds._readurl_exception_callback, + exception_cb=mock.ANY, infinite=False, log_req_resp=True, ), ] - warnings = [ - x.message for x in caplog.records if x.levelno == logging.WARNING - ] - assert warnings == [ - "Failed to fetch metadata from IMDS: No IMDS version", - "Falling back to IMDS api-version: 2019-06-01", + assert caplog.record_tuples == [ + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[0/11\] open.*"), + ), + ( + LOG_PATH, + logging.WARNING, + "Failed to fetch metadata from IMDS: No IMDS version", + ), + ( + LOG_PATH, + logging.WARNING, + "Falling back to IMDS api-version: 2019-06-01", + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[0/11\] open.*"), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch("Read from.*"), + ), ] @pytest.mark.parametrize( @@ -155,11 +194,36 @@ class TestFetchMetadataWithApiFallback: assert md == fake_md assert len(mock_requests_session_request.mock_calls) == 2 assert mock_url_helper_time_sleep.mock_calls == [mock.call(1)] - - warnings = [ - x.message for x in caplog.records if x.levelno == logging.WARNING + assert caplog.record_tuples == [ + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[0/11\] open.*"), + ), + ( + LOG_PATH, + logging.INFO, + StringMatch( + "Polling IMDS failed attempt 1 with exception:" + f".*{error!s}.*" + ), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch("Please wait 1 second.*"), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[1/11\] open.*"), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch("Read from.*"), + ), ] - assert warnings == [] def test_will_retry_errors_on_fallback( self, @@ -180,13 +244,58 @@ class TestFetchMetadataWithApiFallback: assert md == fake_md assert len(mock_requests_session_request.mock_calls) == 3 assert mock_url_helper_time_sleep.mock_calls == [mock.call(1)] - - warnings = [ - x.message for x in caplog.records if x.levelno == logging.WARNING - ] - assert warnings == [ - "Failed to fetch metadata from IMDS: fake error", - "Falling back to IMDS api-version: 2019-06-01", + assert caplog.record_tuples == [ + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[0/11\] open.*"), + ), + ( + LOG_PATH, + logging.INFO, + StringMatch( + "Polling IMDS failed attempt 1 with exception:" + f".*{error!s}.*" + ), + ), + ( + LOG_PATH, + logging.WARNING, + "Failed to fetch metadata from IMDS: fake error", + ), + ( + LOG_PATH, + logging.WARNING, + "Falling back to IMDS api-version: 2019-06-01", + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[0/11\] open.*"), + ), + ( + LOG_PATH, + logging.INFO, + StringMatch( + "Polling IMDS failed attempt 1 with exception:" + f".*{error!s}.*" + ), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch("Please wait 1 second.*"), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[1/11\] open.*"), + ), + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch("Read from.*"), + ), ] @pytest.mark.parametrize( @@ -221,10 +330,24 @@ class TestFetchMetadataWithApiFallback: == [mock.call(1)] * self.retries ) - warnings = [ - x.message for x in caplog.records if x.levelno == logging.WARNING + logs = [x for x in caplog.record_tuples if x[0] == LOG_PATH] + assert logs == [ + ( + LOG_PATH, + logging.INFO, + StringMatch( + f"Polling IMDS failed attempt {i} with exception:" + f".*{error!s}.*" + ), + ) + for i in range(1, 12) + ] + [ + ( + LOG_PATH, + logging.WARNING, + f"Failed to fetch metadata from IMDS: {error!s}", + ) ] - assert warnings == [f"Failed to fetch metadata from IMDS: {error!s}"] @pytest.mark.parametrize( "error", @@ -253,30 +376,47 @@ class TestFetchMetadataWithApiFallback: assert len(mock_requests_session_request.mock_calls) == 1 assert mock_url_helper_time_sleep.mock_calls == [] - warnings = [ - x.message for x in caplog.records if x.levelno == logging.WARNING + assert caplog.record_tuples == [ + ( + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"\[0/11\] open.*"), + ), + ( + LOG_PATH, + logging.INFO, + StringMatch( + "Polling IMDS failed attempt 1 with exception:" + f".*{error!s}.*" + ), + ), + ( + LOG_PATH, + logging.WARNING, + f"Failed to fetch metadata from IMDS: {error!s}", + ), ] - assert warnings == [f"Failed to fetch metadata from IMDS: {error!s}"] def test_non_json_repsonse( self, caplog, - mock_readurl, + mock_requests_session_request, + wrapped_readurl, ): - mock_readurl.side_effect = [ - mock.Mock(contents=b"bad data"), + mock_requests_session_request.side_effect = [ + mock.Mock(content=b"bad data") ] with pytest.raises(ValueError): imds.fetch_metadata_with_api_fallback() - assert mock_readurl.mock_calls == [ + assert wrapped_readurl.mock_calls == [ mock.call( self.default_url, timeout=self.timeout, headers=self.headers, retries=self.retries, - exception_cb=imds._readurl_exception_callback, + exception_cb=mock.ANY, infinite=False, log_req_resp=True, ), @@ -304,17 +444,18 @@ class TestFetchReprovisionData: def test_basic( self, caplog, - mock_readurl, + mock_requests_session_request, + wrapped_readurl, ): content = b"ovf content" - mock_readurl.side_effect = [ - mock.Mock(contents=content), + mock_requests_session_request.side_effect = [ + mock.Mock(content=content), ] ovf = imds.fetch_reprovision_data() assert ovf == content - assert mock_readurl.mock_calls == [ + assert wrapped_readurl.mock_calls == [ mock.call( self.url, timeout=self.timeout, @@ -327,10 +468,15 @@ class TestFetchReprovisionData: assert caplog.record_tuples == [ ( - "cloudinit.sources.azure.imds", + "cloudinit.url_helper", + logging.DEBUG, + StringMatch(r"Read from.*"), + ), + ( + LOG_PATH, logging.DEBUG, "Polled IMDS 1 time(s)", - ) + ), ] @pytest.mark.parametrize( @@ -370,10 +516,10 @@ class TestFetchReprovisionData: ) backoff_logs = [ ( - "cloudinit.sources.azure.imds", + LOG_PATH, logging.INFO, - "Polling IMDS failed with exception: " - f"{wrapped_error!r} count: {i}", + f"Polling IMDS failed attempt {i} with exception: " + f"{wrapped_error!r}", ) for i in range(1, failures + 1) if i == 1 or math.log2(i).is_integer() @@ -382,10 +528,10 @@ class TestFetchReprovisionData: ( "cloudinit.url_helper", logging.DEBUG, - mock.ANY, + StringMatch(r"Read from.*"), ), ( - "cloudinit.sources.azure.imds", + LOG_PATH, logging.DEBUG, f"Polled IMDS {failures+1} time(s)", ), @@ -437,20 +583,20 @@ class TestFetchReprovisionData: backoff_logs = [ ( - "cloudinit.sources.azure.imds", + LOG_PATH, logging.INFO, - "Polling IMDS failed with exception: " - f"{wrapped_error!r} count: {i}", + f"Polling IMDS failed attempt {i} with exception: " + f"{wrapped_error!r}", ) for i in range(1, failures + 1) if i == 1 or math.log2(i).is_integer() ] assert caplog.record_tuples == backoff_logs + [ ( - "cloudinit.sources.azure.imds", + LOG_PATH, logging.INFO, - "Polling IMDS failed with exception: " - f"{exc_info.value!r} count: {failures+1}", + f"Polling IMDS failed attempt {failures+1} with exception: " + f"{exc_info.value!r}", ), ] @@ -483,9 +629,9 @@ class TestFetchReprovisionData: assert caplog.record_tuples == [ ( - "cloudinit.sources.azure.imds", + LOG_PATH, logging.INFO, - "Polling IMDS failed with exception: " - f"{exc_info.value!r} count: 1", + "Polling IMDS failed attempt 1 with exception: " + f"{exc_info.value!r}", ), ] |