import json import stat from collections import defaultdict from datetime import datetime, timezone, timedelta try: from importlib import reload except ImportError: from imp import reload from io import BytesIO from unittest.mock import patch from urllib.error import HTTPError, URLError import pytest from check_docker import check_docker as cd __author__ = 'Tim Laurence' class FakeHttpResponse(BytesIO): def __init__(self, content=b'', http_code=200, headers=None, method='GET'): self.status = http_code self.code = http_code self.headers = headers if headers else {} self.method = method super(FakeHttpResponse, self).__init__(content) def getheader(self, header, default): return self.headers.get(header, default) @pytest.fixture() def check_docker_fresh(): """ This is used for tests that have issues with cross test interaction :return: """ reload(cd) return cd @pytest.fixture() def check_docker(): cd.rc = -1 check_docker.no_ok = False check_docker.no_performance = False cd.timeout = 1 cd.messages = [] cd.performance_data = [] cd.daemon = 'socket:///notreal' cd.get_url.cache_clear() cd.DISABLE_THREADING = True cd.Oauth2TokenAuthHandler.auth_failure_tracker = defaultdict(int) def fake_exit(_=None): pass cd.exit = fake_exit return cd @pytest.fixture def check_docker_with_units(check_docker): check_docker.unit_adjustments = {key: 1024 ** value for key, value in check_docker.UNIT_ADJUSTMENTS_TEMPLATE.items()} return check_docker def test_get_url(check_docker): obj = {'foo': 'bar'} encoded = json.dumps(obj=obj).encode('utf-8') expected_response = FakeHttpResponse(content=encoded, http_code=200) def mock_open(*args, **kwargs): return expected_response with patch('check_docker.check_docker.better_urllib_get.open', side_effect=mock_open): response, _ = check_docker.get_url(url='/test') assert response == obj def test_get_url_with_oauth2(check_docker): headers1 = { 'www-authenticate': 'Bearer realm="https://docker-auth.example.com/auth",service="token-service",scope="repository:something/something_else:pull"'} mock_response1 = FakeHttpResponse(method='GET', content=b'', http_code=401, headers=headers1) mock_response2 = FakeHttpResponse(method='GET', content=b'{"test_key":"test_value"}', http_code=200, headers={'test': 'test'}) with patch('check_docker.check_docker.HTTPSHandler.https_open', side_effect=[mock_response1, mock_response2]), \ patch('check_docker.check_docker.Oauth2TokenAuthHandler._get_outh2_token', return_value='test_token') as get_token: response = check_docker.get_url(url='https://example.com/test') assert response == ({"test_key": "test_value"}, 200) assert get_token.call_count == 1 def test_get_url_with_oauth2_loop(check_docker): headers = { 'www-authenticate': 'Bearer realm="https://docker-auth.example.com/auth",service="token-service",scope="repository:something/something_else:pull"'} mock_response = FakeHttpResponse(method='GET', http_code=401, headers=headers) def mock_open(*args, **kwargs): return mock_response with patch('check_docker.check_docker.HTTPSHandler.https_open', side_effect=mock_open), \ patch('check_docker.check_docker.Oauth2TokenAuthHandler._get_outh2_token', return_value='test_token') as get_token: with pytest.raises(HTTPError): check_docker.get_url(url='https://example.com/test') def test_get_url_500(check_docker): expected_exception = HTTPError(code=500, fp=None, url='url', msg='msg', hdrs=[]) with patch('check_docker.check_docker.HTTPSHandler.https_open', side_effect=expected_exception), \ pytest.raises(HTTPError): check_docker.get_url(url='https://example.com/test') @pytest.mark.parametrize("func", [ 'get_stats', 'get_state', 'get_image_info' ]) def test_get_url_calls(check_docker, func): # TODO with patch('check_docker.check_docker.get_url', return_value=({'State': 'State'}, 200)) as patched: getattr(check_docker, func)('container') assert patched.call_count == 1 @pytest.mark.parametrize("value, expected", [ (1, ["1s"]), (61, ["1min", "1s"]), (3661, ["1h", "1min", "1s"]), (86401, ["1d", "1s"]) ]) def test_pretty_time(check_docker, value, expected): assert check_docker.pretty_time(value) == expected @pytest.mark.parametrize("value, rc, messages, perf_data", [ (1, cd.OK_RC, ['OK: container metric is 1B'], ['container_met=1B;2;3;0;10']), (2, cd.WARNING_RC, ['WARNING: container metric is 2B'], ['container_met=2B;2;3;0;10']), (3, cd.CRITICAL_RC, ['CRITICAL: container metric is 3B'], ['container_met=3B;2;3;0;10']) ]) def test_evaluate_numeric_thresholds(check_docker, value, rc, messages, perf_data): thresholds = cd.ThresholdSpec(warn=2, crit=3, units='B') check_docker.evaluate_numeric_thresholds(container='container', value=value, name='metric', short_name='met', min=0, max=10, thresholds=thresholds ) assert check_docker.rc == rc assert check_docker.messages == messages assert check_docker.performance_data == perf_data @pytest.mark.parametrize('func,arg,rc,messages', ( ('ok', "OK test", cd.OK_RC, ['OK: OK test']), ('warning', "WARN test", cd.WARNING_RC, ['WARNING: WARN test']), ('critical', "CRIT test", cd.CRITICAL_RC, ['CRITICAL: CRIT test']), ('unknown', "UNKNOWN test", cd.UNKNOWN_RC, ['UNKNOWN: UNKNOWN test']), )) def test_status_update(check_docker, func, arg, rc, messages): getattr(check_docker, func)(arg) assert check_docker.rc == rc assert check_docker.messages == messages @pytest.mark.parametrize('input, units_required, expected', ( ('1:2:3', True, cd.ThresholdSpec(warn=1, crit=2, units='3')), ('1:2', False, cd.ThresholdSpec(warn=1, crit=2, units='')), ('1:2:3', False, cd.ThresholdSpec(warn=1, crit=2, units='3')), )) def test_parse_thresholds(check_docker, input, units_required, expected): result = check_docker.parse_thresholds(input, units_required=units_required) assert expected == result @pytest.mark.parametrize('spec, kwargs, exception', ( ('1:2', {}, ValueError), ('1:2:b', {'include_units': False}, ValueError), ('1:2', {'include_units': True}, ValueError), ("1", {}, IndexError), (":1", {}, ValueError), (":1:c", {}, ValueError), ("1:", {}, ValueError), ("1::c", {}, ValueError), ('1:2:', {'units_required': True}, ValueError), ("a:1:c", {}, ValueError), ("1:b:c", {}, ValueError), ) ) def test_parse_thresholds_exceptions(check_docker, spec, kwargs, exception): with pytest.raises(exception): check_docker.parse_thresholds(spec, **kwargs) def test_set_rc(check_docker): # Can I do a basic set check_docker.set_rc(check_docker.OK_RC) assert check_docker.rc == check_docker.OK_RC # Does it prevent downgrades of rc check_docker.set_rc(check_docker.WARNING_RC) assert check_docker.rc == check_docker.WARNING_RC check_docker.set_rc(check_docker.OK_RC) assert check_docker.rc == check_docker.WARNING_RC @pytest.mark.parametrize('response, expected_status', ( ({'State': {'Running': True, "Restarting": False, "Paused": False, "Dead": False}}, cd.OK_RC), ({'State': {'Running': True, "Restarting": True, "Paused": False, "Dead": False}}, cd.CRITICAL_RC), ({'State': {'Status': 'stopped'}}, cd.CRITICAL_RC), ({'State': {'Running': False, "Restarting": False, "Paused": False, "Dead": False}}, cd.CRITICAL_RC), )) def test_check_status(check_docker, response, expected_status): def mock_response(*args, **kwargs): encoded = json.dumps(obj=response).encode('utf-8') return FakeHttpResponse(encoded, 200) with patch('check_docker.check_docker.better_urllib_get.open', side_effect=mock_response): check_docker.check_status(container='container', desired_state='running') assert check_docker.rc == expected_status @pytest.mark.parametrize('response, expected_status', ( ({'State': {'Health': {'Status': 'healthy'}, 'Running': True, "Restarting": False, "Paused": False, "Dead": False}}, cd.OK_RC), ({'State': {'Health': {'Status': 'unhealthy'}, 'Running': True, "Restarting": False, "Paused": False, "Dead": False}}, cd.CRITICAL_RC), ({'State': {'Running': True, "Restarting": False, "Paused": False, "Dead": False}}, cd.UNKNOWN_RC), ( {'State': {'Health': {}, 'Running': True, "Restarting": False, "Paused": False, "Dead": False}}, cd.UNKNOWN_RC), ({'State': {'Health': {'Status': 'starting'}, 'Running': True, "Restarting": False, "Paused": False, "Dead": False}}, cd.UNKNOWN_RC) )) def test_check_health(check_docker, response, expected_status): def mock_response(*args, **kwargs): encoded = json.dumps(obj=response).encode('utf-8') return FakeHttpResponse(encoded, 200) with patch('check_docker.check_docker.better_urllib_get.open', side_effect=mock_response): check_docker.check_health(container='container') assert check_docker.rc == expected_status @pytest.mark.parametrize('memory_stats, warn, crit, units, expected_status', ( ({'limit': 10, 'usage': 1, 'stats': {'total_cache': 1}}, 1, 2, 'B', cd.OK_RC), ({'limit': 10, 'usage': 2, 'stats': {'total_cache': 1}}, 1, 2, 'B', cd.WARNING_RC), ({'limit': 10, 'usage': 3, 'stats': {'total_cache': 1}}, 1, 2, 'B', cd.CRITICAL_RC), ({'limit': 10, 'usage': 1, 'stats': {'total_cache': 1}}, 20, 30, '%', cd.OK_RC), ({'limit': 10, 'usage': 3, 'stats': {'total_cache': 1}}, 20, 30, '%', cd.WARNING_RC), ({'limit': 10, 'usage': 4, 'stats': {'total_cache': 1}}, 20, 30, '%', cd.CRITICAL_RC), ({'limit': 10, 'usage': 4, 'stats': {'total_cache': 1}}, 20, 30, 'BAD_UNITS', cd.UNKNOWN_RC), )) def test_check_memory(check_docker_with_units, memory_stats, warn, crit, units, expected_status): response = { 'memory_stats': memory_stats, 'State': {'Running': True, "Restarting": False, "Paused": False, "Dead": False} } def mock_response(*args, **kwargs): encoded = json.dumps(obj=response).encode('utf-8') return FakeHttpResponse(encoded, 200) with patch('check_docker.check_docker.better_urllib_get.open', side_effect=mock_response): thresholds = cd.ThresholdSpec(warn=warn, crit=crit, units=units) check_docker_with_units.check_memory(container='container', thresholds=thresholds) assert check_docker_with_units.rc == expected_status cpu_param_fields = 'host_config, cpu_stats, precpu_stats, warn, crit, expected_status, expected_percent' cpu_parm_tests = (({"NanoCpus": 1000000000, "CpuPeriod": 0, "CpuQuota": 0}, {'cpu_usage': {'percpu_usage': [15], 'total_usage': 15}, 'online_cpus': 1, 'system_cpu_usage': 100}, {'cpu_usage': {'percpu_usage': [10], 'total_usage': 10}, 'online_cpus': 1, 'system_cpu_usage': 0}, 10, 20, cd.OK_RC, 5), ({"NanoCpus": 1000000000, "CpuPeriod": 0, "CpuQuota": 0}, {'cpu_usage': {'percpu_usage': [25], 'total_usage': 25}, 'online_cpus': 1, 'system_cpu_usage': 100}, {'cpu_usage': {'percpu_usage': [10], 'total_usage': 10}, 'online_cpus': 1, 'system_cpu_usage': 0}, 10, 20, cd.WARNING_RC, 15), ({"NanoCpus": 1000000000, "CpuPeriod": 0, "CpuQuota": 0}, {'cpu_usage': {'percpu_usage': [35], 'total_usage': 35}, 'online_cpus': 1, 'system_cpu_usage': 100}, {'cpu_usage': {'percpu_usage': [10], 'total_usage': 10}, 'online_cpus': 1, 'system_cpu_usage': 0}, 10, 20, cd.CRITICAL_RC, 25), ({"NanoCpus": 0, "CpuPeriod": 0, "CpuQuota": 10000}, {'cpu_usage': {'percpu_usage': [15], 'total_usage': 15}, 'online_cpus': 1, 'system_cpu_usage': 100}, {'cpu_usage': {'percpu_usage': [10], 'total_usage': 10}, 'online_cpus': 1, 'system_cpu_usage': 0}, 10, 20, cd.CRITICAL_RC, 50), ({"NanoCpus": 0, "CpuPeriod": 0, "CpuQuota": 0}, {'cpu_usage': {'percpu_usage': [35], 'total_usage': 35}, 'online_cpus': 1, 'system_cpu_usage': 100}, {'cpu_usage': {'percpu_usage': [10], 'total_usage': 10}, 'online_cpus': 1, 'system_cpu_usage': 0}, 10, 20, cd.CRITICAL_RC, 25), ({"NanoCpus": 0, "CpuPeriod": 1, "CpuQuota": 2}, {'cpu_usage': {'percpu_usage': [35], 'total_usage': 35}, 'online_cpus': 1, 'system_cpu_usage': 100}, {'cpu_usage': {'percpu_usage': [10], 'total_usage': 10}, 'system_cpu_usage': 0}, 10, 20, cd.CRITICAL_RC, 25), ({"NanoCpus": 0, "CpuPeriod": 0, "CpuQuota": 0}, {'cpu_usage': {'total_usage': 36}, 'online_cpus': 2, 'system_cpu_usage': 200}, {'cpu_usage': {'total_usage': 10}, 'system_cpu_usage': 0}, 10, 20, cd.WARNING_RC, 13), ({"NanoCpus": 0, "CpuPeriod": 0, "CpuQuota": 0}, {'cpu_usage': {'percpu_usage': [35, 1], 'total_usage': 36}, 'system_cpu_usage': 200}, {'cpu_usage': {'total_usage': 10}, 'system_cpu_usage': 0}, 10, 20, cd.WARNING_RC, 13 ) ) @pytest.mark.parametrize(cpu_param_fields, cpu_parm_tests) def test_check_cpu(check_docker, host_config, cpu_stats, precpu_stats, warn, crit, expected_status, expected_percent): container_stats = { 'cpu_stats': cpu_stats, 'precpu_stats': precpu_stats } container_info = { 'State': {'Running': True, "Restarting": False, "Paused": False, "Dead": False}, "HostConfig": host_config } def mock_stats_response(*args, **kwargs): return container_stats def mock_info_response(*args, **kwargs): return container_info with patch('check_docker.check_docker.get_stats', side_effect=mock_stats_response), \ patch('check_docker.check_docker.get_container_info', side_effect=mock_info_response): thresholds = cd.ThresholdSpec(warn=warn, crit=crit, units=None) check_docker.check_cpu(container='container', thresholds=thresholds) assert check_docker.rc == expected_status @pytest.mark.parametrize(cpu_param_fields, cpu_parm_tests) def test_calculate_cpu(check_docker, host_config, cpu_stats, precpu_stats, warn, crit, expected_status, expected_percent): container_stats = { 'cpu_stats': cpu_stats, 'precpu_stats': precpu_stats } container_info = { 'State': {'Running': True, "Restarting": False, "Paused": False, "Dead": False}, "HostConfig": host_config } pecentage = check_docker.calculate_cpu_capacity_precentage(info=container_info, stats=container_stats) assert pecentage == expected_percent def test_require_running(check_docker): """ This confirms the 'require_running decorator is working properly with a stopped container""" container_info = {'RestartCount': 0, 'State': {'Running': False, "Restarting": True}} def mock_info_response(*args, **kwargs): return container_info with patch('check_docker.check_docker.get_container_info', side_effect=mock_info_response): thresholds = cd.ThresholdSpec(warn=1, crit=2, units='') check_docker.check_restarts(container='container', thresholds=thresholds) assert check_docker.rc == check_docker.CRITICAL_RC @pytest.mark.parametrize("restarts, expected_status", ( (0, cd.OK_RC), (1, cd.WARNING_RC), (3, cd.CRITICAL_RC), )) def test_restarts(check_docker, restarts, expected_status): container_info = {'RestartCount': restarts, 'State': {'Running': True, "Restarting": False, "Paused": False, "Dead": False}} def mock_info_response(*args, **kwargs): return container_info with patch('check_docker.check_docker.get_container_info', side_effect=mock_info_response): thresholds = cd.ThresholdSpec(warn=1, crit=2, units='') check_docker.check_restarts(container='container', thresholds=thresholds) assert check_docker.rc == expected_status @pytest.mark.parametrize("uptime, warn, crit, expected_status", ( (timedelta(seconds=0), 10, 5, cd.CRITICAL_RC), (timedelta(seconds=9), 10, 1, cd.WARNING_RC), (timedelta(seconds=10), 2, 1, cd.OK_RC), (timedelta(days=1, seconds=0), 2, 1, cd.OK_RC) )) def test_check_uptime1(check_docker, uptime, warn, crit, expected_status): time = datetime.now(tz=timezone.utc) - uptime time_str = time.strftime("%Y-%m-%dT%H:%M:%S.0000000000Z") json_results = { 'State': {'StartedAt': time_str, 'Running': True, "Restarting": False, "Paused": False, "Dead": False}, } def mock_response(*args, **kwargs): encoded = json.dumps(obj=json_results).encode('utf-8') return FakeHttpResponse(encoded, 200) with patch('check_docker.check_docker.better_urllib_get.open', side_effect=mock_response): thresholds = cd.ThresholdSpec(warn=warn, crit=crit, units='') check_docker.check_uptime(container='container', thresholds=thresholds) assert check_docker.rc == expected_status @pytest.mark.parametrize("image_age, warn, crit, expected_status", ( (timedelta(days=20), 10, 20, cd.CRITICAL_RC), (timedelta(days=15), 10, 20, cd.WARNING_RC), (timedelta(days=5), 10, 20, cd.OK_RC), )) def test_check_image_age(check_docker, image_age, warn, crit, expected_status): time = datetime.now(tz=timezone.utc) - image_age time_str = time.strftime("%Y-%m-%dT%H:%M:%S.0000000000Z") container_response = {'Image': 'test'} image_response = {'Created': time_str} def mock_response(*args, **kwargs): encoded = json.dumps(obj=image_response).encode('utf-8') return FakeHttpResponse(encoded, 200) with patch('check_docker.check_docker.get_container_info', return_value=container_response), \ patch('check_docker.check_docker.get_image_info', return_value=image_response): thresholds = cd.ThresholdSpec(warn=warn, crit=crit, units='') check_docker.check_image_age(container='container', thresholds=thresholds) assert check_docker.rc == expected_status sample_containers = [ {'Names': ['/name1']}, {'Names': ['/name2']}] @pytest.fixture def sample_containers_json(): return sample_containers @pytest.fixture def mock_get_container_info(): def mock(id): return {'Name': sample_containers[id]} return mock def test_args_help(check_docker, capsys): args = tuple() check_docker.process_args(args=args) out, err = capsys.readouterr() assert 'usage: ' in out @pytest.mark.parametrize("args, expected_value, default_value", ( (('--timeout', '9999'), 9999, cd.DEFAULT_TIMEOUT), (('--containers', 'foo', 'bar'), ['foo', 'bar'], ['all']), (('--present',), True, False), (('--threads', '23'), 23, cd.DISABLE_THREADING), (('--cpu', 'non-default'), 'non-default', None), (('--memory', 'non-default'), 'non-default', None), (('--status', 'non-default'), 'non-default', None), (('--health',), True, None), (('--uptime', 'non-default'), 'non-default', None), (('--version',), True, None), (('--insecure-registries', 'non-default'), ['non-default'], None), (('--restarts', 'non-default'), 'non-default', None), (('--no-ok',), True, False), (('--no-performance',), True, False), )) def test_args(check_docker, args, expected_value, default_value): attrib_name = args[0][2:].replace('-', '_') # Strip the -- off the first arg if default_value: default_result = check_docker.process_args(args=[]) assert getattr(default_result, attrib_name) == default_value result = check_docker.process_args(args=args) assert getattr(result, attrib_name) == expected_value def test_args_containers_blank(check_docker): args = ('--containers',) with pytest.raises(SystemExit): check_docker.process_args(args=args) def test_args_connection(check_docker): args = ('--connection', '/foo') result = check_docker.process_args(args=args) assert result.connection == '/foo' assert check_docker.daemon == 'socket:///foo:' args = ('--connection', 'foo.com/bar') result = check_docker.process_args(args=args) assert result.connection == 'foo.com/bar' assert check_docker.daemon == 'http://foo.com/bar' def test_args_secure_connection(check_docker): check_docker.rc = -1 args = ('--secure-connection', 'non-default') result = check_docker.process_args(args=args) assert result.secure_connection == 'non-default' args = ('--secure-connection', 'foo.com/bar') result = check_docker.process_args(args=args) assert result.secure_connection == 'foo.com/bar' assert check_docker.daemon == 'https://foo.com/bar' @pytest.mark.parametrize('args', ( ('--connection', 'non-default', '--secure-connection', 'non-default'), ('--binary_units', '--decimal_units') )) def test_exclusive_args(check_docker, args): with pytest.raises(SystemExit): check_docker.process_args(args) def test_units_base_uninitialized(check_docker_fresh): # Assert value is driven by argprase results, i.e. there is no default value assert check_docker_fresh.unit_adjustments is None, "unit_adjustments has no sensible default without knowing the base" def test_units_base_initialized(check_docker_fresh): # Confirm default value is set parsed_args = check_docker_fresh.process_args([]) assert parsed_args.units_base == 1024, "units_base should default to 1024" @pytest.mark.parametrize('arg, one_kb', ( ('--binary_units', 1024), ('--decimal_units', 1000) )) def test_units_base(check_docker, fs, arg, one_kb): # Confirm value is updated by argparse flags parsed_args = check_docker.process_args([arg]) assert parsed_args.units_base == one_kb, "units_base should be influenced by units flags" fs.create_file(check_docker.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666)) with patch('check_docker.check_docker.get_containers', return_value=['test']), \ patch('check_docker.check_docker.get_stats', return_value={'memory_stats': {'limit': one_kb, 'usage': one_kb, 'stats': {'total_cache': 0}}}), \ patch('check_docker.check_docker.get_state', return_value={'Running': True, "Restarting": False, "Paused": False, "Dead": False}): check_docker.perform_checks(['--memory', '0:0:KB', arg]) # Confirm unit adjustment table was updated by argument assert check_docker.unit_adjustments['KB'] == one_kb # Confirm output shows unit conversion specified by arg assert check_docker.performance_data == ['test_mem=1.0KB;0;0;0;1.0'] def test_missing_check(check_docker): check_docker.rc = -1 args = tuple() result = check_docker.process_args(args=args) assert check_docker.no_checks_present(result) def test_present_check(check_docker): check_docker.rc = -1 args = ('--status', 'running') result = check_docker.process_args(args=args) assert not check_docker.no_checks_present(result) def test_disallow_present_without_containers(check_docker): args = ('--cpu', '0:0', '--present') with patch('check_docker.check_docker.get_containers') as patched_get_containers: with patch('check_docker.check_docker.unknown') as patched_unknown: check_docker.perform_checks(args) assert patched_unknown.call_count == 1 assert patched_get_containers.call_count == 0 def test_get_containers_1(check_docker, sample_containers_json, mock_get_container_info): with patch('check_docker.check_docker.get_url', return_value=(sample_containers_json, 200)), \ patch('check_docker.check_docker.get_container_info', side_effect=mock_get_container_info): container_list = check_docker.get_containers('all', False) assert container_list == {'name1', 'name2'} def test_get_containers_2(check_docker, sample_containers_json, mock_get_container_info): with patch('check_docker.check_docker.get_url', return_value=(sample_containers_json, 200)): with patch('check_docker.check_docker.get_container_info', side_effect=mock_get_container_info): container_list = check_docker.get_containers(['name.*'], False) assert container_list == {'name1', 'name2'} def test_get_containers_3(check_docker, sample_containers_json, mock_get_container_info): check_docker.rc = -1 with patch('check_docker.check_docker.get_url', return_value=(sample_containers_json, 200)), \ patch('check_docker.check_docker.unknown') as patched, \ patch('check_docker.check_docker.get_container_info', side_effect=mock_get_container_info): container_list = check_docker.get_containers({'foo'}, False) assert container_list == set() assert patched.call_count == 0 def test_get_containers_4(check_docker, sample_containers_json, mock_get_container_info): check_docker.rc = -1 with patch('check_docker.check_docker.get_url', return_value=(sample_containers_json, 200)): with patch('check_docker.check_docker.critical') as patched, \ patch('check_docker.check_docker.get_container_info', side_effect=mock_get_container_info): container_list = check_docker.get_containers({'foo'}, True) assert container_list == set() assert patched.call_count == 1 def test_socketfile_failure_false(check_docker, fs): fs.create_file('/tmp/socket', contents='', st_mode=(stat.S_IFSOCK | 0o666)) args = ('--status', 'running', '--connection', '/tmp/socket') result = check_docker.process_args(args=args) assert not check_docker.socketfile_permissions_failure(parsed_args=result) def test_socketfile_failure_result(check_docker): # Confirm bad socket results in uknown status args = ('--cpu', '0:0', '--connection', '/tmp/missing') with patch('check_docker.check_docker.get_url', return_value=(['thing1'], 200)): with patch('check_docker.check_docker.unknown') as patched: check_docker.perform_checks(args) assert patched.call_count == 1 def test_socketfile_failure_filetype(check_docker, fs): fs.create_file('/tmp/not_socket', contents='testing') args = ('--status', 'running', '--connection', '/tmp/not_socket') result = check_docker.process_args(args=args) assert check_docker.socketfile_permissions_failure(parsed_args=result) def test_socketfile_failure_missing(check_docker, fs): args = ('--status', 'running', '--connection', '/tmp/missing') result = check_docker.process_args(args=args) assert check_docker.socketfile_permissions_failure(parsed_args=result) def test_socketfile_failure_unwriteable(check_docker, fs): fs.create_file('/tmp/unwritable', contents='', st_mode=(stat.S_IFSOCK | 0o000)) args = ('--status', 'running', '--connection', '/tmp/unwritable') result = check_docker.process_args(args=args) assert check_docker.socketfile_permissions_failure(parsed_args=result) def test_socketfile_failure_unreadable(check_docker, fs): fs.create_file('/tmp/unreadable', contents='', st_mode=(stat.S_IFSOCK | 0o000)) args = ('--status', 'running', '--connection', '/tmp/unreadable') result = check_docker.process_args(args=args) assert check_docker.socketfile_permissions_failure(parsed_args=result) def test_socketfile_failure_http(check_docker, fs): fs.create_file('/tmp/http', contents='', st_mode=(stat.S_IFSOCK | 0o000)) args = ('--status', 'running', '--connection', 'http://127.0.0.1') result = check_docker.process_args(args=args) assert not check_docker.socketfile_permissions_failure(parsed_args=result) def test_perform_with_no_containers(check_docker, fs): fs.create_file(check_docker.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666)) args = ['--cpu', '0:0'] with patch('check_docker.check_docker.get_url', return_value=([], 200)): with patch('check_docker.check_docker.unknown') as patched: check_docker.perform_checks(args) assert patched.call_count == 1 def test_perform_with_uncaught_exception(check_docker, fs): fs.create_file(check_docker.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666)) with patch('check_docker.check_docker.get_url', return_value=([{'Names': ('/thing1',)}], 200)), \ patch('check_docker.check_docker.check_cpu', side_effect=Exception("Oh no!")), \ patch('check_docker.check_docker.argv', side_effect=['', '--cpu', '0:0']), \ patch('check_docker.check_docker.unknown') as patched: check_docker.main() assert patched.call_count == 1 @pytest.mark.parametrize("args, called", ( (['--cpu', '0:0'], 'check_cpu'), (['--memory', '0:0'], 'check_memory'), (['--health'], 'check_health'), (['--restarts', '1:1'], 'check_restarts'), (['--status', 'running'], 'check_status'), (['--uptime', '0:0'], 'check_uptime'), (['--version'], 'check_version'), ([], 'unknown') )) def test_perform(check_docker, fs, args, called): fs.create_file(check_docker.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666)) with patch('check_docker.check_docker.get_containers', return_value=['thing1']): with patch('check_docker.check_docker.' + called) as patched: check_docker.perform_checks(args) assert patched.call_count == 1 @pytest.mark.parametrize("messages, perf_data, expected", ( (['TEST'], [], 'TEST'), (['FOO', 'BAR'], [], 'FOO; BAR'), (['FOO', 'BAR'], ['1;2;3;4;'], 'FOO; BAR|1;2;3;4;') )) def test_print_results(check_docker, capsys, messages, perf_data, expected): # These sometimes get set to true when using random-order plugin, for example --random-order-seed=620808 check_docker.no_ok = False check_docker.no_performance = False check_docker.messages = messages check_docker.performance_data = perf_data check_docker.print_results() out, err = capsys.readouterr() assert out.strip() == expected @pytest.mark.parametrize("messages, perf_data, no_ok, no_performance, expected", ( ([], [], False, False, ''), (['TEST'], [], False, False, 'TEST'), (['FOO', 'BAR'], [], False, False, 'FOO; BAR'), (['FOO', 'BAR'], ['1;2;3;4;'], False, False, 'FOO; BAR|1;2;3;4;'), ([], [], True, False, 'OK'), (['OK: TEST'], [], True, False, 'OK'), (['OK: FOO', 'OK: BAR'], [], True, False, 'OK'), (['OK: FOO', 'BAR'], ['1;2;3;4;'], True, False, 'BAR|1;2;3;4;'), ([], [], False, True, ''), (['OK: TEST'], [], False, True, 'OK: TEST'), (['OK: TEST'], ['1;2;3;4;'], False, True, 'OK: TEST'), (['OK: FOO', 'OK: BAR'], ['1;2;3;4;'], True, True, 'OK'), )) def test_print_results_no_ok(check_docker, capsys, messages, perf_data, no_ok, no_performance, expected): check_docker.messages = messages check_docker.performance_data = perf_data check_docker.no_ok = no_ok check_docker.no_performance = no_performance check_docker.print_results() out, err = capsys.readouterr() assert out.strip() == expected @pytest.mark.parametrize('url, expected', ( ("short", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="library/short", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/library/short:latest")), ("simple/name", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="simple/name", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/simple/name:latest")), ("library/ubuntu", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="library/ubuntu", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/library/ubuntu:latest")), ("docker/stevvooe/app", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="docker/stevvooe/app", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/docker/stevvooe/app:latest")), ("aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb:latest")), ("aa/aa/bb/bb/bb", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="aa/aa/bb/bb/bb", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/aa/aa/bb/bb/bb:latest")), ("a/a/a/a", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="a/a/a/a", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/a/a/a/a:latest")), ("a", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="library/a", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/library/a:latest")), ("a/aa", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="a/aa", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/a/aa:latest")), ("a/aa/a", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="a/aa/a", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/a/aa/a:latest")), ("foo.com", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="library/foo.com", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/library/foo.com:latest")), ("foo.com:8080/bar", cd.ImageName(registry="foo.com:8080", name="bar", tag="latest", full_name="foo.com:8080/bar:latest")), ("foo.com/bar", cd.ImageName(registry="foo.com", name="bar", tag="latest", full_name="foo.com/bar:latest")), ("foo.com/bar/baz", cd.ImageName(registry="foo.com", name="bar/baz", tag="latest", full_name="foo.com/bar/baz:latest")), ("localhost:8080/bar", cd.ImageName(registry="localhost:8080", name="bar", tag="latest", full_name="localhost:8080/bar:latest")), ("sub-dom1.foo.com/bar/baz/quux", cd.ImageName(registry="sub-dom1.foo.com", name="bar/baz/quux", tag="latest", full_name="sub-dom1.foo.com/bar/baz/quux:latest")), ("blog.foo.com/bar/baz", cd.ImageName(registry="blog.foo.com", name="bar/baz", tag="latest", full_name="blog.foo.com/bar/baz:latest")), ("aa-a/a", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="aa-a/a", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/aa-a/a:latest")), ("foo_bar", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="library/foo_bar", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/library/foo_bar:latest")), ("foo_bar.com", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="library/foo_bar.com", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/library/foo_bar.com:latest")), ("foo.com/foo_bar", cd.ImageName(registry="foo.com", name="foo_bar", tag="latest", full_name="foo.com/foo_bar:latest")), ("b.gcr.io/test.example.com/my-app", cd.ImageName(registry="b.gcr.io", name="test.example.com/my-app", tag="latest", full_name="b.gcr.io/test.example.com/my-app:latest")), ("xn--n3h.com/myimage", cd.ImageName(registry="xn--n3h.com", name="myimage", tag="latest", full_name="xn--n3h.com/myimage:latest")), ("xn--7o8h.com/myimage", cd.ImageName(registry="xn--7o8h.com", name="myimage", tag="latest", full_name="xn--7o8h.com/myimage:latest")), ("example.com/xn--7o8h.com/myimage", cd.ImageName(registry="example.com", name="xn--7o8h.com/myimage", tag="latest", full_name="example.com/xn--7o8h.com/myimage:latest")), ("example.com/some_separator__underscore/myimage", cd.ImageName(registry="example.com", name="some_separator__underscore/myimage", tag="latest", full_name="example.com/some_separator__underscore/myimage:latest")), ("do__cker/docker", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="do__cker/docker", tag="latest", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/do__cker/docker:latest")), ("b.gcr.io/test.example.com/my-app", cd.ImageName(registry="b.gcr.io", name="test.example.com/my-app", tag="latest", full_name="b.gcr.io/test.example.com/my-app:latest")), ("registry.io/foo/project--id.module--name.ver---sion--name", cd.ImageName(registry="registry.io", name="foo/project--id.module--name.ver---sion--name", tag="latest", full_name="registry.io/foo/project--id.module--name.ver---sion--name:latest")), ("Asdf.com/foo/bar", cd.ImageName(registry="Asdf.com", name="foo/bar", tag="latest", full_name="Asdf.com/foo/bar:latest")), ("host.tld:12/name:tag", cd.ImageName(registry="host.tld:12", name="name", tag="tag", full_name="host.tld:12/name:tag")), ("host.tld/name:tag", cd.ImageName(registry="host.tld", name="name", tag="tag", full_name="host.tld/name:tag")), ("name/name:tag", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="name/name", tag="tag", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/name/name:tag")), ("name:tag", cd.ImageName(registry=cd.DEFAULT_PUBLIC_REGISTRY, name="library/name", tag="tag", full_name=cd.DEFAULT_PUBLIC_REGISTRY + "/library/name:tag")), ("host:21/name:tag", cd.ImageName(registry='host:21', name="name", tag="tag", full_name="host:21/name:tag")), )) def test_parse_image_name(check_docker, url, expected): parsed_name = check_docker.parse_image_name(url) assert parsed_name == expected def test_get_manifest_auth_token(check_docker): obj = {'token': 'test'} encoded = json.dumps(obj=obj).encode('utf-8') expected_response = FakeHttpResponse(content=encoded, http_code=200) with patch('check_docker.check_docker.request.urlopen', return_value=expected_response): www_authenticate_header = 'Bearer realm="https://example.com/token",service="example.com",scope="repository:test:pull"' token = check_docker.Oauth2TokenAuthHandler._get_outh2_token(www_authenticate_header) assert token == 'test' def test_get_container_image_urls(check_docker): container_response = {'Image': 'test'} image_response = {'RepoTags': ['test']} with patch('check_docker.check_docker.get_container_info', return_value=container_response), \ patch('check_docker.check_docker.get_image_info', return_value=image_response): urls = check_docker.get_container_image_urls('container') assert urls == ['test'] @pytest.mark.parametrize('image_url, expected_normal_url', ( ('foo', 'https://' + cd.DEFAULT_PUBLIC_REGISTRY + '/v2/library/foo/manifests/latest'), ('insecure.com/foo', 'http://insecure.com/v2/foo/manifests/latest'), )) def test_normalize_image_name_to_manifest_url(check_docker, image_url, expected_normal_url): insecure_registries = ('insecure.com',) normal_url, _ = check_docker.normalize_image_name_to_manifest_url(image_url, insecure_registries) assert normal_url == expected_normal_url def test_get_container_image_id(check_docker): container_response = {'Image': 'test'} with patch('check_docker.check_docker.get_container_info', return_value=container_response): digest = check_docker.get_container_image_id('container') assert digest == 'test' def test_get_digest_from_registry_no_auth(check_docker): fake_data = {'config': {'digest': 'test_token'}} with patch('check_docker.check_docker.get_url', return_value=(fake_data, 200)): digest = check_docker.get_digest_from_registry('https://example.com/v2/test/manifests/lastest') assert digest == "test_token" def test_get_digest_from_registry_missing_digest(check_docker): with patch('check_docker.check_docker.get_url', return_value=({},404)): with pytest.raises(check_docker.RegistryError): check_docker.get_digest_from_registry('https://example.com/v2/test/manifests/lastest') @pytest.mark.parametrize('local_container_container_image_id,registry_container_digest, image_urls, expected_rc', ( ('AAAA', 'AAAA', ('example.com/foo',), cd.OK_RC), ('AAAA', 'BBBB', ('example.com/foo',), cd.CRITICAL_RC), (None, '', ('example.com/foo',), cd.UNKNOWN_RC), ('AAAA', 'AAAA', ('example.com/foo', 'example.com/bar'), cd.UNKNOWN_RC), ('AAAA', 'AAAA', tuple(), cd.UNKNOWN_RC), )) def test_check_version(check_docker, local_container_container_image_id, registry_container_digest, image_urls, expected_rc): with patch('check_docker.check_docker.get_container_image_id', return_value=local_container_container_image_id), \ patch('check_docker.check_docker.get_container_image_urls', return_value=image_urls), \ patch('check_docker.check_docker.get_digest_from_registry', return_value=registry_container_digest): check_docker.check_version('container', tuple()) assert check_docker.rc == expected_rc def test_check_version_missing_digest(check_docker): with patch('check_docker.check_docker.get_container_image_id', return_value='AAA'), \ patch('check_docker.check_docker.get_container_image_urls', return_value=('example.com/foo',)), \ patch('check_docker.check_docker.get_digest_from_registry', side_effect=check_docker.RegistryError(response=None)): check_docker.check_version('container', tuple()) assert check_docker.rc == cd.UNKNOWN_RC def test_check_version_not_tls(check_docker): class Reason(): reason = 'UNKNOWN_PROTOCOL' exception = URLError(reason=Reason) with patch('check_docker.check_docker.get_container_image_id', return_value='AAA'), \ patch('check_docker.check_docker.get_container_image_urls', return_value=('example.com/foo',)), \ patch('check_docker.check_docker.get_digest_from_registry', side_effect=exception): check_docker.check_version('container', tuple()) assert check_docker.rc == cd.UNKNOWN_RC assert 'TLS error' in check_docker.messages[0] def test_check_version_no_such_host(check_docker): class Reason(): strerror = 'nodename nor servname provided, or not known' exception = URLError(reason=Reason) with patch('check_docker.check_docker.get_container_image_id', return_value='AAA'), \ patch('check_docker.check_docker.get_container_image_urls', return_value=('example.com/foo',)), \ patch('check_docker.check_docker.get_digest_from_registry', side_effect=exception): check_docker.check_version('container', tuple()) assert check_docker.rc == cd.UNKNOWN_RC assert 'Cannot reach registry' in check_docker.messages[0] def test_check_version_exception(check_docker): # Unhandled exceptions should be passed on exception = URLError(reason=None) with patch('check_docker.check_docker.get_container_image_id', return_value='AAA'), \ patch('check_docker.check_docker.get_container_image_urls', return_value=('example.com/foo',)), \ patch('check_docker.check_docker.get_digest_from_registry', side_effect=exception), \ pytest.raises(URLError): check_docker.check_version('container', tuple()) @pytest.mark.parametrize('names', ( (('\\a', 'a\\b'),), (('\\a'),), (('a\\b', '\\a'),) )) def test_get_ps_name_ok(check_docker, names): assert check_docker.ps_name(names) == 'a' @pytest.mark.parametrize('names', ( ('a\\b'), set(), ('a\\b', 'b\\a'), ('\\b', '\\a'), )) def test_get_ps_name_ok(check_docker, names): with pytest.raises(NameError): check_docker.get_ps_name(names)