icinga2-checks/check_docker/tests/test_check_swarm.py

386 lines
15 KiB
Python
Raw Normal View History

2023-05-03 12:20:18 -06:00
import argparse
import json
import stat
from io import BytesIO
from unittest.mock import patch, call
import pytest
from check_docker import check_swarm as cs
__author__ = 'tim'
@pytest.fixture
def check_swarm():
# This is needed because `check_docker` does not end a a .py so it won't be found by default
from check_docker import check_swarm
check_swarm.rc = -1
check_swarm.timeout = 1
check_swarm.messages = []
check_swarm.performance_data = []
check_swarm.daemon = 'socket:///notreal'
check_swarm.get_url.cache_clear()
return check_swarm
@pytest.fixture
def active_node():
return {"ID": 44, 'Spec': {'Availability': 'active'}}
@pytest.fixture
def paused_node():
return {"ID": 43, 'Spec': {'Availability': 'paused'}}
@pytest.fixture
def drain_node():
return {"ID": 42, 'Spec': {'Availability': 'drain'}}
@pytest.fixture
def node_list(active_node, paused_node, drain_node):
return active_node, paused_node, drain_node
active_node_task = {"NodeID": 44, 'Status': {'State': 'running'}}
paused_node_task = {"NodeID": 43, 'Status': {'State': 'running'}}
drain_node_task = {"NodeID": 42, 'Status': {'State': 'running'}}
class FakeHttpResponse(BytesIO):
def __init__(self, content, http_code):
self.status = http_code
super(FakeHttpResponse, self).__init__(content)
def test_get_url(check_swarm, monkeypatch):
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
monkeypatch.setattr(check_swarm.better_urllib_get, 'open', value=mock_open)
response, _ = check_swarm.get_url(url='/test')
assert response == obj
def test_get_swarm_status(check_swarm):
with patch('check_docker.check_swarm.get_url', return_value=('', 999)):
response = check_swarm.get_swarm_status()
assert response == 999
def test_get_service_info(check_swarm):
sample_response = ([{'Status': {'State': 'running', 'DesiredState': 'running'}},
{'Status': {'State': 'failed', 'DesiredState': 'running'}}], 999)
with patch('check_docker.check_swarm.get_url', return_value=sample_response):
response_data = check_swarm.get_service_tasks('FOO')
assert len(response_data) == 2
def test_get_services_not_swarm(check_swarm):
with patch('check_docker.check_swarm.get_url', return_value=('', 406)):
check_swarm.get_services('FOO')
assert check_swarm.rc == check_swarm.CRITICAL_RC
def test_get_services_error(check_swarm):
with patch('check_docker.check_swarm.get_url', return_value=('', 500)):
check_swarm.get_services('FOO')
assert check_swarm.rc == check_swarm.UNKNOWN_RC
def test_get_services_all(check_swarm):
services = [{'Spec': {"Name": 'FOO'}},
{'Spec': {"Name": 'BAR'}}]
with patch('check_docker.check_swarm.get_url', return_value=(services, 200)):
result = check_swarm.get_services('all')
assert len(result) == len(services)
@pytest.mark.parametrize('func,arg,rc,messages',
(
('ok', "OK test", cs.OK_RC, ['OK: OK test']),
('warning', "WARN test", cs.WARNING_RC, ['WARNING: WARN test']),
('critical', "CRIT test", cs.CRITICAL_RC, ['CRITICAL: CRIT test']),
('unknown', "UNKNOWN test", cs.UNKNOWN_RC, ['UNKNOWN: UNKNOWN test']),
))
def test_status_update(check_swarm, func, arg, rc, messages):
getattr(check_swarm, func)(arg)
assert check_swarm.rc == rc
assert check_swarm.messages == messages
def test_set_rc(check_swarm):
# Can I do a basic set
check_swarm.set_rc(check_swarm.OK_RC)
assert check_swarm.rc == check_swarm.OK_RC
# Does it prevent downgrades of rc
check_swarm.set_rc(check_swarm.WARNING_RC)
assert check_swarm.rc == check_swarm.WARNING_RC
check_swarm.set_rc(check_swarm.OK_RC)
assert check_swarm.rc == check_swarm.WARNING_RC
@pytest.mark.parametrize('code, expected_rc, expected_messages', (
(200, cs.OK_RC, ['OK: ok_msg']),
(404, cs.CRITICAL_RC, ['CRITICAL: critical_msg']),
(418, cs.UNKNOWN_RC, ['UNKNOWN: unknown_msg']),
))
def test_process_url_status_ok(check_swarm, code, expected_rc, expected_messages):
check_swarm.process_url_status(code, ok_msg='ok_msg', critical_msg='critical_msg', unknown_msg='unknown_msg')
assert check_swarm.rc == expected_rc
assert check_swarm.messages == expected_messages
def test_args_timeout(check_swarm):
args = ('--timeout', '9999', '--swarm')
result = check_swarm.process_args(args=args)
assert result.timeout == 9999.0
def test_args_connection(check_swarm):
args = ('--connection', '/foo', '--swarm')
result = check_swarm.process_args(args=args)
assert result.connection == '/foo'
assert check_swarm.daemon == 'socket:///foo:'
args = ('--connection', 'foo.com/bar', '--swarm')
result = check_swarm.process_args(args=args)
assert result.connection == 'foo.com/bar'
assert check_swarm.daemon == 'http://foo.com/bar'
def test_args_secure_connection(check_swarm):
args = ('--secure-connection', 'non-default', '--swarm')
result = check_swarm.process_args(args=args)
assert result.secure_connection == 'non-default'
args = ('--secure-connection', 'foo.com/bar', '--swarm')
result = check_swarm.process_args(args=args)
assert result.secure_connection == 'foo.com/bar'
assert check_swarm.daemon == 'https://foo.com/bar'
def test_args_mixed_connection(check_swarm):
args = ('--connection', 'non-default', '--secure-connection', 'non-default', '--swarm')
with pytest.raises(SystemExit):
check_swarm.process_args(args)
def test_missing_check(check_swarm):
try:
with pytest.raises(argparse.ArgumentError):
check_swarm.process_args(tuple())
except SystemExit: # Argument failures exit as well
pass
def test_args_mixed_checks(check_swarm):
try:
with pytest.raises(argparse.ArgumentError):
check_swarm.process_args(['--swarm', "--service", "FOO"])
except SystemExit: # Argument failures exit as well
pass
def test_socketfile_failure_false(check_swarm, fs):
fs.create_file('/tmp/socket', contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ('--swarm', '--connection', '/tmp/socket')
result = check_swarm.process_args(args=args)
assert not check_swarm.socketfile_permissions_failure(parsed_args=result)
def test_socketfile_failure_filetype(check_swarm, fs):
fs.create_file('/tmp/not_socket', contents='testing')
args = ('--swarm', '--connection', '/tmp/not_socket')
result = check_swarm.process_args(args=args)
assert check_swarm.socketfile_permissions_failure(parsed_args=result)
def test_socketfile_failure_missing(check_swarm, fs):
args = ('--swarm', '--connection', '/tmp/missing')
result = check_swarm.process_args(args=args)
check_swarm.socketfile_permissions_failure(parsed_args=result)
def test_socketfile_failure_unwriteable(check_swarm, fs):
fs.create_file('/tmp/unwritable', contents='', st_mode=(stat.S_IFSOCK | 0o000))
args = ('--swarm', '--connection', '/tmp/unwritable')
result = check_swarm.process_args(args=args)
assert check_swarm.socketfile_permissions_failure(parsed_args=result)
def test_socketfile_failure_unreadable(check_swarm, fs):
fs.create_file('/tmp/unreadable', contents='', st_mode=(stat.S_IFSOCK | 0o000))
args = ('--swarm', '--connection', '/tmp/unreadable')
result = check_swarm.process_args(args=args)
assert check_swarm.socketfile_permissions_failure(parsed_args=result)
def test_socketfile_failure_http(check_swarm, fs):
fs.create_file('/tmp/http', contents='', st_mode=(stat.S_IFSOCK | 0o000))
args = ('--swarm', '--connection', 'http://127.0.0.1')
result = check_swarm.process_args(args=args)
assert not check_swarm.socketfile_permissions_failure(parsed_args=result)
def test_check_swarm_called(check_swarm, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--swarm']
with patch('check_docker.check_swarm.check_swarm') as patched:
check_swarm.perform_checks(args)
assert patched.call_count == 1
def test_check_swarm_results_OK(check_swarm, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--swarm']
with patch('check_docker.check_swarm.get_swarm_status', return_value=200):
check_swarm.perform_checks(args)
assert check_swarm.rc == cs.OK_RC
def test_check_swarm_results_CRITICAL(check_swarm, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--swarm']
with patch('check_docker.check_swarm.get_swarm_status', return_value=406):
check_swarm.perform_checks(args)
assert check_swarm.rc == cs.CRITICAL_RC
def test_check_service_called(check_swarm, fs):
service_info = {'Spec': {'Mode': {'Replicated': {'Replicas': 1}}}}
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--service', 'FOO']
with patch('check_docker.check_swarm.get_services', return_value=[service_info]):
with patch('check_docker.check_swarm.check_service') as patched:
check_swarm.perform_checks(args)
assert patched.call_count == 1
@pytest.mark.parametrize("service_info, expected_func, expected_args", (
({'Spec': {'Mode': {'Global': {}}}}, 'process_global_service', {'name': 'FOO', 'ignore_paused': False}),
({'Spec': {'Mode': {'Replicated': {'Replicas': 1}}}}, 'process_replicated_service',
{'name': 'FOO', 'replicas_desired': 1}),
({'Spec': {'Mode': {'Replicated': {'Replicas': 3}}}}, 'process_replicated_service',
{'name': 'FOO', 'replicas_desired': 3}),
))
def test_check_services_routing_global(check_swarm, service_info, expected_func, expected_args, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
with patch('check_docker.check_swarm.get_service_info', return_value=(service_info, 999)), \
patch('check_docker.check_swarm.{}'.format(expected_func)) as patched:
check_swarm.check_service('FOO')
assert patched.call_count == 1
assert patched.call_args == call(**expected_args)
def test_check_services_global_ignore_paused(check_swarm, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
service_info = {'Spec': {'Mode': {'Global': {}}}}
with patch('check_docker.check_swarm.get_service_info', return_value=(service_info, 999)), \
patch('check_docker.check_swarm.process_global_service') as patched:
check_swarm.check_service('FOO', True)
assert patched.call_count == 1
assert patched.call_args == call(name='FOO', ignore_paused=True)
@pytest.mark.parametrize("service_list, ignore_paused, expected_rc", (
([active_node_task, paused_node_task, drain_node_task], False, cs.OK_RC),
([active_node_task, drain_node_task], False, cs.CRITICAL_RC),
([active_node_task, paused_node_task], False, cs.OK_RC),
([active_node_task], False, cs.CRITICAL_RC),
([paused_node_task], False, cs.CRITICAL_RC),
([], False, cs.CRITICAL_RC),
([active_node_task], True, cs.OK_RC),
([paused_node_task], True, cs.CRITICAL_RC),
))
def test_process_global_service(check_swarm, fs, node_list, service_list, ignore_paused, expected_rc):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
with patch('check_docker.check_swarm.get_nodes', return_value=(node_list, 999)) as patched_get_nodes, \
patch('check_docker.check_swarm.get_service_tasks', return_value=service_list) as patched_get_service_tasks:
check_swarm.process_global_service('FOO', ignore_paused)
assert patched_get_nodes.call_count == 1
assert patched_get_service_tasks.call_count == 1
assert check_swarm.rc == expected_rc
@pytest.mark.parametrize("service_list, expected_rc", (
([active_node_task, paused_node_task, drain_node_task], cs.CRITICAL_RC),
([active_node_task, paused_node_task], cs.OK_RC),
([active_node_task], cs.CRITICAL_RC),
([], cs.CRITICAL_RC),
))
def test_process_replicated_service(check_swarm, fs, service_list, expected_rc):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
with patch('check_docker.check_swarm.get_service_tasks',
return_value=service_list) as patched_get_service_running_tasks:
check_swarm.process_replicated_service('FOO', 2)
assert patched_get_service_running_tasks.call_count == 1
assert check_swarm.rc == expected_rc
def test_check_service_results_FAIL_missing(check_swarm, fs):
service_info = {'Spec': {'Name': 'FOO', 'Mode': {'Global': {}}}}
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--service', 'missing1']
with patch('check_docker.check_swarm.get_url', return_value=([service_info], 200)):
check_swarm.perform_checks(args)
assert check_swarm.rc == cs.CRITICAL_RC
def test_check_service_results_FAIL_unknown(check_swarm, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--service', 'FOO']
with patch('check_docker.check_swarm.get_services', return_value=['FOO', 'BAR']):
with patch('check_docker.check_swarm.get_service_info', return_value=('', 500)):
check_swarm.perform_checks(args)
assert check_swarm.rc == cs.UNKNOWN_RC
def test_check_no_services(check_swarm, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--service', 'missing2']
with patch('check_docker.check_swarm.get_url', return_value=([], 200)):
check_swarm.perform_checks(args)
assert check_swarm.rc == cs.CRITICAL_RC
def test_check_missing_service(check_swarm, fs):
service_info = {'Spec': {'Name': 'FOO', 'Mode': {'Global': {}}}}
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--service', 'missing3']
with patch('check_docker.check_swarm.get_url', return_value=([service_info], 200)):
check_swarm.perform_checks(args)
assert check_swarm.rc == cs.CRITICAL_RC
def test_check_not_swarm_service(check_swarm, fs):
fs.create_file(check_swarm.DEFAULT_SOCKET, contents='', st_mode=(stat.S_IFSOCK | 0o666))
args = ['--service', 'missing4']
with patch('check_docker.check_swarm.get_url', return_value=('', 406)):
check_swarm.perform_checks(args)
assert check_swarm.rc == cs.CRITICAL_RC
@pytest.mark.parametrize("messages, perf_data, expected", (
([], [], ''),
(['TEST'], [], 'TEST'),
(['FOO', 'BAR'], [], 'FOO; BAR'),
))
def test_print_results(check_swarm, capsys, messages, perf_data, expected):
check_swarm.messages = messages
check_swarm.performance_data = perf_data
check_swarm.print_results()
out, err = capsys.readouterr()
assert out.strip() == expected