icinga2-checks/check_docker/tests/test_check_docker.py

957 lines
44 KiB
Python
Raw Permalink Normal View History

2023-05-03 12:20:18 -06:00
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)