mirror of
https://github.com/thegeeklab/docker-tidy.git
synced 2024-11-24 13:10:40 +00:00
refactor testing
This commit is contained in:
parent
b9479d501c
commit
152e7a3f73
@ -73,10 +73,7 @@ class GarbageCollector:
|
||||
return containers
|
||||
|
||||
def include_container(container):
|
||||
if self._should_exclude_container_with_labels(
|
||||
container,
|
||||
config["gc"]["exclude_container_labels"],
|
||||
):
|
||||
if self._should_exclude_container_with_labels(container):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
import datetime
|
||||
|
||||
from dateutil import tz
|
||||
from docker_custodian import args
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
|
||||
def test_datetime_seconds_ago(now):
|
||||
expected = datetime.datetime(2014, 1, 15, 10, 10, tzinfo=tz.tzutc())
|
||||
with mock.patch(
|
||||
'docker_custodian.args.datetime.datetime',
|
||||
autospec=True,
|
||||
) as mock_datetime:
|
||||
mock_datetime.now.return_value = now
|
||||
assert args.datetime_seconds_ago(24 * 60 * 60 * 5) == expected
|
||||
|
||||
|
||||
def test_timedelta_type_none():
|
||||
assert args.timedelta_type(None) is None
|
||||
|
||||
|
||||
def test_timedelta_type(now):
|
||||
expected = datetime.datetime(2014, 1, 15, 10, 10, tzinfo=tz.tzutc())
|
||||
with mock.patch(
|
||||
'docker_custodian.args.datetime.datetime',
|
||||
autospec=True,
|
||||
) as mock_datetime:
|
||||
mock_datetime.now.return_value = now
|
||||
assert args.timedelta_type('5 days') == expected
|
@ -1,54 +0,0 @@
|
||||
import datetime
|
||||
|
||||
import docker
|
||||
import pytest
|
||||
from dateutil import tz
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
return {
|
||||
'Id': 'abcdabcdabcdabcd',
|
||||
'Created': '2013-12-20T17:00:00Z',
|
||||
'Name': '/container_name',
|
||||
'State': {
|
||||
'Running': False,
|
||||
'FinishedAt': '2014-01-01T17:30:00Z',
|
||||
'StartedAt': '2014-01-01T17:01:00Z',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def image():
|
||||
return {
|
||||
'Id': 'abcdabcdabcdabcd',
|
||||
'Created': '2014-01-20T05:00:00Z',
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def now():
|
||||
return datetime.datetime(2014, 1, 20, 10, 10, tzinfo=tz.tzutc())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def earlier_time():
|
||||
return datetime.datetime(2014, 1, 1, 0, 0, tzinfo=tz.tzutc())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def later_time():
|
||||
return datetime.datetime(2014, 1, 20, 0, 10, tzinfo=tz.tzutc())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client():
|
||||
client = mock.create_autospec(docker.APIClient)
|
||||
client._version = '1.21'
|
||||
return client
|
@ -1,75 +0,0 @@
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from docker_custodian.docker_autostop import build_container_matcher
|
||||
from docker_custodian.docker_autostop import get_opts
|
||||
from docker_custodian.docker_autostop import has_been_running_since
|
||||
from docker_custodian.docker_autostop import main
|
||||
from docker_custodian.docker_autostop import stop_container
|
||||
from docker_custodian.docker_autostop import stop_containers
|
||||
|
||||
|
||||
def test_stop_containers(mock_client, container, now):
|
||||
matcher = mock.Mock()
|
||||
mock_client.containers.return_value = [container]
|
||||
mock_client.inspect_container.return_value = container
|
||||
|
||||
stop_containers(mock_client, now, matcher, False)
|
||||
matcher.assert_called_once_with('container_name')
|
||||
mock_client.stop.assert_called_once_with(container['Id'])
|
||||
|
||||
|
||||
def test_stop_container(mock_client):
|
||||
id = 'asdb'
|
||||
stop_container(mock_client, id)
|
||||
mock_client.stop.assert_called_once_with(id)
|
||||
|
||||
|
||||
def test_build_container_matcher():
|
||||
prefixes = ['one_', 'two_']
|
||||
matcher = build_container_matcher(prefixes)
|
||||
|
||||
assert matcher('one_container')
|
||||
assert matcher('two_container')
|
||||
assert not matcher('three_container')
|
||||
assert not matcher('one')
|
||||
|
||||
|
||||
def test_has_been_running_since_true(container, later_time):
|
||||
assert has_been_running_since(container, later_time)
|
||||
|
||||
|
||||
def test_has_been_running_since_false(container, earlier_time):
|
||||
assert not has_been_running_since(container, earlier_time)
|
||||
|
||||
|
||||
@mock.patch('docker_custodian.docker_autostop.build_container_matcher', autospec=True)
|
||||
@mock.patch('docker_custodian.docker_autostop.stop_containers', autospec=True)
|
||||
@mock.patch('docker_custodian.docker_autostop.get_opts', autospec=True)
|
||||
@mock.patch('docker_custodian.docker_autostop.docker', autospec=True)
|
||||
def test_main(mock_docker, mock_get_opts, mock_stop_containers, mock_build_matcher):
|
||||
mock_get_opts.return_value.timeout = 30
|
||||
main()
|
||||
mock_get_opts.assert_called_once_with()
|
||||
mock_build_matcher.assert_called_once_with(mock_get_opts.return_value.prefix)
|
||||
mock_stop_containers.assert_called_once_with(mock.ANY, mock_get_opts.return_value.max_run_time,
|
||||
mock_build_matcher.return_value,
|
||||
mock_get_opts.return_value.dry_run)
|
||||
|
||||
|
||||
def test_get_opts_with_defaults():
|
||||
opts = get_opts(args=['--prefix', 'one', '--prefix', 'two'])
|
||||
assert opts.timeout == 60
|
||||
assert opts.dry_run is False
|
||||
assert opts.prefix == ['one', 'two']
|
||||
assert opts.max_run_time is None
|
||||
|
||||
|
||||
def test_get_opts_with_args(now):
|
||||
with mock.patch('docker_custodian.docker_autostop.timedelta_type',
|
||||
autospec=True) as mock_timedelta_type:
|
||||
opts = get_opts(args=['--prefix', 'one', '--max-run-time', '24h'])
|
||||
assert opts.max_run_time == mock_timedelta_type.return_value
|
||||
mock_timedelta_type.assert_called_once_with('24h')
|
@ -1,545 +0,0 @@
|
||||
import textwrap
|
||||
from io import StringIO
|
||||
|
||||
import docker.errors
|
||||
import requests.exceptions
|
||||
from docker_custodian import docker_gc
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
|
||||
class TestShouldRemoveContainer(object):
|
||||
|
||||
def test_is_running(self, container, now):
|
||||
container['State']['Running'] = True
|
||||
assert not docker_gc.should_remove_container(container, now)
|
||||
|
||||
def test_is_ghost(self, container, now):
|
||||
container['State']['Ghost'] = True
|
||||
assert docker_gc.should_remove_container(container, now)
|
||||
|
||||
def test_old_never_run(self, container, now, earlier_time):
|
||||
container['Created'] = str(earlier_time)
|
||||
container['State']['FinishedAt'] = docker_gc.YEAR_ZERO
|
||||
assert docker_gc.should_remove_container(container, now)
|
||||
|
||||
def test_not_old_never_run(self, container, now, earlier_time):
|
||||
container['Created'] = str(now)
|
||||
container['State']['FinishedAt'] = docker_gc.YEAR_ZERO
|
||||
assert not docker_gc.should_remove_container(container, now)
|
||||
|
||||
def test_old_stopped(self, container, now):
|
||||
assert docker_gc.should_remove_container(container, now)
|
||||
|
||||
def test_not_old(self, container, now):
|
||||
container['State']['FinishedAt'] = '2014-01-21T00:00:00Z'
|
||||
assert not docker_gc.should_remove_container(container, now)
|
||||
|
||||
|
||||
def test_cleanup_containers(mock_client, now):
|
||||
max_container_age = now
|
||||
mock_client.containers.return_value = [
|
||||
{
|
||||
'Id': 'abcd'
|
||||
},
|
||||
{
|
||||
'Id': 'abbb'
|
||||
},
|
||||
]
|
||||
mock_containers = [
|
||||
{
|
||||
'Id': 'abcd',
|
||||
'Name': 'one',
|
||||
'State': {
|
||||
'Running': False,
|
||||
'FinishedAt': '2014-01-01T01:01:01Z',
|
||||
},
|
||||
},
|
||||
{
|
||||
'Id': 'abbb',
|
||||
'Name': 'two',
|
||||
'State': {
|
||||
'Running': True,
|
||||
'FinishedAt': '2014-01-01T01:01:01Z',
|
||||
},
|
||||
},
|
||||
]
|
||||
mock_client.inspect_container.side_effect = iter(mock_containers)
|
||||
docker_gc.cleanup_containers(mock_client, max_container_age, False, None)
|
||||
mock_client.remove_container.assert_called_once_with(container='abcd', v=True)
|
||||
|
||||
|
||||
def test_filter_excluded_containers():
|
||||
mock_containers = [
|
||||
{
|
||||
'Labels': {
|
||||
'toot': ''
|
||||
}
|
||||
},
|
||||
{
|
||||
'Labels': {
|
||||
'too': 'lol'
|
||||
}
|
||||
},
|
||||
{
|
||||
'Labels': {
|
||||
'toots': 'lol'
|
||||
}
|
||||
},
|
||||
{
|
||||
'Labels': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
},
|
||||
{
|
||||
'Labels': None
|
||||
},
|
||||
]
|
||||
result = docker_gc.filter_excluded_containers(mock_containers, None)
|
||||
assert mock_containers == list(result)
|
||||
exclude_labels = [
|
||||
docker_gc.ExcludeLabel(key='too', value=None),
|
||||
docker_gc.ExcludeLabel(key='foo', value=None),
|
||||
]
|
||||
result = docker_gc.filter_excluded_containers(
|
||||
mock_containers,
|
||||
exclude_labels,
|
||||
)
|
||||
assert [mock_containers[0], mock_containers[2], mock_containers[4]] == list(result)
|
||||
exclude_labels = [
|
||||
docker_gc.ExcludeLabel(key='too*', value='lol'),
|
||||
]
|
||||
result = docker_gc.filter_excluded_containers(
|
||||
mock_containers,
|
||||
exclude_labels,
|
||||
)
|
||||
assert [mock_containers[0], mock_containers[3], mock_containers[4]] == list(result)
|
||||
|
||||
|
||||
def test_cleanup_images(mock_client, now):
|
||||
max_image_age = now
|
||||
mock_client.images.return_value = images = [
|
||||
{
|
||||
'Id': 'abcd'
|
||||
},
|
||||
{
|
||||
'Id': 'abbb'
|
||||
},
|
||||
]
|
||||
mock_images = [
|
||||
{
|
||||
'Id': 'abcd',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
{
|
||||
'Id': 'abbb',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
]
|
||||
mock_client.inspect_image.side_effect = iter(mock_images)
|
||||
|
||||
docker_gc.cleanup_images(mock_client, max_image_age, False, set())
|
||||
assert mock_client.remove_image.mock_calls == [
|
||||
mock.call(image=image['Id']) for image in reversed(images)
|
||||
]
|
||||
|
||||
|
||||
def test_cleanup_volumes(mock_client):
|
||||
mock_client.volumes.return_value = volumes = {
|
||||
'Volumes': [
|
||||
{
|
||||
'Mountpoint': 'unused',
|
||||
'Labels': None,
|
||||
'Driver': 'unused',
|
||||
'Name': u'one'
|
||||
},
|
||||
{
|
||||
'Mountpoint': 'unused',
|
||||
'Labels': None,
|
||||
'Driver': 'unused',
|
||||
'Name': u'two'
|
||||
},
|
||||
],
|
||||
'Warnings': None,
|
||||
}
|
||||
|
||||
docker_gc.cleanup_volumes(mock_client, False)
|
||||
assert mock_client.remove_volume.mock_calls == [
|
||||
mock.call(name=volume['Name']) for volume in reversed(volumes['Volumes'])
|
||||
]
|
||||
|
||||
|
||||
def test_filter_images_in_use():
|
||||
image_tags_in_use = set([
|
||||
'user/one:latest',
|
||||
'user/foo:latest',
|
||||
'other:12345',
|
||||
'2471708c19be:latest',
|
||||
])
|
||||
images = [
|
||||
{
|
||||
'RepoTags': ['<none>:<none>'],
|
||||
'Id': '2471708c19beabababab'
|
||||
},
|
||||
{
|
||||
'RepoTags': ['<none>:<none>'],
|
||||
'Id': 'babababababaabababab'
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/one:latest', 'user/one:abcd']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['other:abcda']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['other:12345']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['new_image:latest', 'new_image:123']
|
||||
},
|
||||
]
|
||||
expected = [
|
||||
{
|
||||
'RepoTags': ['<none>:<none>'],
|
||||
'Id': 'babababababaabababab'
|
||||
},
|
||||
{
|
||||
'RepoTags': ['other:abcda']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['new_image:latest', 'new_image:123']
|
||||
},
|
||||
]
|
||||
actual = docker_gc.filter_images_in_use(images, image_tags_in_use)
|
||||
assert list(actual) == expected
|
||||
|
||||
|
||||
def test_filter_images_in_use_by_id(mock_client, now):
|
||||
mock_client._version = '1.21'
|
||||
mock_client.containers.return_value = [
|
||||
{
|
||||
'Id': 'abcd',
|
||||
'ImageID': '1'
|
||||
},
|
||||
{
|
||||
'Id': 'abbb',
|
||||
'ImageID': '2'
|
||||
},
|
||||
]
|
||||
mock_containers = [{
|
||||
'Id': 'abcd',
|
||||
'Name': 'one',
|
||||
'State': {
|
||||
'Running': False,
|
||||
'FinishedAt': '2014-01-01T01:01:01Z'
|
||||
}
|
||||
}, {
|
||||
'Id': 'abbb',
|
||||
'Name': 'two',
|
||||
'State': {
|
||||
'Running': True,
|
||||
'FinishedAt': '2014-01-01T01:01:01Z'
|
||||
}
|
||||
}]
|
||||
mock_client.inspect_container.side_effect = iter(mock_containers)
|
||||
mock_client.images.return_value = [
|
||||
{
|
||||
'Id': '1',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
{
|
||||
'Id': '2',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
{
|
||||
'Id': '3',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
{
|
||||
'Id': '4',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
{
|
||||
'Id': '5',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
{
|
||||
'Id': '6',
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
},
|
||||
]
|
||||
mock_client.inspect_image.side_effect = lambda image: {
|
||||
'Id': image,
|
||||
'Created': '2014-01-01T01:01:01Z'
|
||||
}
|
||||
docker_gc.cleanup_images(mock_client, now, False, set())
|
||||
assert mock_client.remove_image.mock_calls == [
|
||||
mock.call(image=id_) for id_ in ['6', '5', '4', '3']
|
||||
]
|
||||
|
||||
|
||||
def test_filter_excluded_images():
|
||||
exclude_set = set([
|
||||
'user/one:latest',
|
||||
'user/foo:latest',
|
||||
'other:12345',
|
||||
])
|
||||
images = [
|
||||
{
|
||||
'RepoTags': ['<none>:<none>'],
|
||||
'Id': 'babababababaabababab'
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/one:latest', 'user/one:abcd']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['other:abcda']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['other:12345']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['new_image:latest', 'new_image:123']
|
||||
},
|
||||
]
|
||||
expected = [
|
||||
{
|
||||
'RepoTags': ['<none>:<none>'],
|
||||
'Id': 'babababababaabababab'
|
||||
},
|
||||
{
|
||||
'RepoTags': ['other:abcda']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['new_image:latest', 'new_image:123']
|
||||
},
|
||||
]
|
||||
actual = docker_gc.filter_excluded_images(images, exclude_set)
|
||||
assert list(actual) == expected
|
||||
|
||||
|
||||
def test_filter_excluded_images_advanced():
|
||||
exclude_set = set([
|
||||
'user/one:*',
|
||||
'user/foo:tag*',
|
||||
'user/repo-*:tag',
|
||||
])
|
||||
images = [
|
||||
{
|
||||
'RepoTags': ['<none>:<none>'],
|
||||
'Id': 'babababababaabababab'
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/one:latest', 'user/one:abcd']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/foo:test']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/foo:tag123']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/repo-1:tag']
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/repo-2:tag']
|
||||
},
|
||||
]
|
||||
expected = [
|
||||
{
|
||||
'RepoTags': ['<none>:<none>'],
|
||||
'Id': 'babababababaabababab'
|
||||
},
|
||||
{
|
||||
'RepoTags': ['user/foo:test'],
|
||||
},
|
||||
]
|
||||
actual = docker_gc.filter_excluded_images(images, exclude_set)
|
||||
assert list(actual) == expected
|
||||
|
||||
|
||||
def test_is_image_old(image, now):
|
||||
assert docker_gc.is_image_old(image, now)
|
||||
|
||||
|
||||
def test_is_image_old_false(image, later_time):
|
||||
assert not docker_gc.is_image_old(image, later_time)
|
||||
|
||||
|
||||
def test_remove_image_no_tags(mock_client, image, now):
|
||||
image_id = 'abcd'
|
||||
image_summary = {'Id': image_id}
|
||||
mock_client.inspect_image.return_value = image
|
||||
docker_gc.remove_image(mock_client, image_summary, now, False)
|
||||
|
||||
mock_client.remove_image.assert_called_once_with(image=image_id)
|
||||
|
||||
|
||||
def test_remove_image_new_image_not_removed(mock_client, image, later_time):
|
||||
image_id = 'abcd'
|
||||
image_summary = {'Id': image_id}
|
||||
mock_client.inspect_image.return_value = image
|
||||
docker_gc.remove_image(mock_client, image_summary, later_time, False)
|
||||
|
||||
assert not mock_client.remove_image.mock_calls
|
||||
|
||||
|
||||
def test_remove_image_with_tags(mock_client, image, now):
|
||||
image_id = 'abcd'
|
||||
repo_tags = ['user/one:latest', 'user/one:12345']
|
||||
image_summary = {'Id': image_id, 'RepoTags': repo_tags}
|
||||
mock_client.inspect_image.return_value = image
|
||||
docker_gc.remove_image(mock_client, image_summary, now, False)
|
||||
|
||||
assert mock_client.remove_image.mock_calls == [mock.call(image=tag) for tag in repo_tags]
|
||||
|
||||
|
||||
def test_api_call_success():
|
||||
func = mock.Mock()
|
||||
container = "abcd"
|
||||
result = docker_gc.api_call(func, container=container)
|
||||
func.assert_called_once_with(container="abcd")
|
||||
assert result == func.return_value
|
||||
|
||||
|
||||
def test_api_call_with_timeout():
|
||||
func = mock.Mock(side_effect=requests.exceptions.ReadTimeout("msg"), __name__="remove_image")
|
||||
image = "abcd"
|
||||
|
||||
with mock.patch('docker_custodian.docker_gc.log', autospec=True) as mock_log:
|
||||
docker_gc.api_call(func, image=image)
|
||||
|
||||
func.assert_called_once_with(image="abcd")
|
||||
mock_log.warn.assert_called_once_with('Failed to call remove_image ' + 'image=abcd msg')
|
||||
|
||||
|
||||
def test_api_call_with_api_error():
|
||||
func = mock.Mock(side_effect=docker.errors.APIError("Ooops",
|
||||
mock.Mock(status_code=409,
|
||||
reason="Conflict"),
|
||||
explanation="failed"),
|
||||
__name__="remove_image")
|
||||
image = "abcd"
|
||||
|
||||
with mock.patch('docker_custodian.docker_gc.log', autospec=True) as mock_log:
|
||||
docker_gc.api_call(func, image=image)
|
||||
|
||||
func.assert_called_once_with(image="abcd")
|
||||
mock_log.warn.assert_called_once_with('Error calling remove_image image=abcd '
|
||||
'409 Client Error: Conflict ("failed")')
|
||||
|
||||
|
||||
def days_as_seconds(num):
|
||||
return num * 60 * 60 * 24
|
||||
|
||||
|
||||
def test_get_args_with_defaults():
|
||||
opts = docker_gc.get_args(args=[])
|
||||
assert opts.timeout == 60
|
||||
assert opts.dry_run is False
|
||||
assert opts.max_container_age is None
|
||||
assert opts.max_image_age is None
|
||||
|
||||
|
||||
def test_get_args_with_args():
|
||||
with mock.patch('docker_custodian.docker_gc.timedelta_type',
|
||||
autospec=True) as mock_timedelta_type:
|
||||
opts = docker_gc.get_args(args=[
|
||||
'--max-image-age',
|
||||
'30 days',
|
||||
'--max-container-age',
|
||||
'3d',
|
||||
])
|
||||
assert mock_timedelta_type.mock_calls == [
|
||||
mock.call('30 days'),
|
||||
mock.call('3d'),
|
||||
]
|
||||
assert opts.max_container_age == mock_timedelta_type.return_value
|
||||
assert opts.max_image_age == mock_timedelta_type.return_value
|
||||
|
||||
|
||||
def test_get_all_containers(mock_client):
|
||||
count = 10
|
||||
mock_client.containers.return_value = [mock.Mock() for _ in range(count)]
|
||||
with mock.patch('docker_custodian.docker_gc.log', autospec=True) as mock_log:
|
||||
containers = docker_gc.get_all_containers(mock_client)
|
||||
assert containers == mock_client.containers.return_value
|
||||
mock_client.containers.assert_called_once_with(all=True)
|
||||
mock_log.info.assert_called_with("Found %s containers", count)
|
||||
|
||||
|
||||
def test_get_all_images(mock_client):
|
||||
count = 7
|
||||
mock_client.images.return_value = [mock.Mock() for _ in range(count)]
|
||||
with mock.patch('docker_custodian.docker_gc.log', autospec=True) as mock_log:
|
||||
images = docker_gc.get_all_images(mock_client)
|
||||
assert images == mock_client.images.return_value
|
||||
mock_log.info.assert_called_with("Found %s images", count)
|
||||
|
||||
|
||||
def test_get_dangling_volumes(mock_client):
|
||||
count = 4
|
||||
mock_client.volumes.return_value = {'Volumes': [mock.Mock() for _ in range(count)]}
|
||||
with mock.patch('docker_custodian.docker_gc.log', autospec=True) as mock_log:
|
||||
volumes = docker_gc.get_dangling_volumes(mock_client)
|
||||
assert volumes == mock_client.volumes.return_value['Volumes']
|
||||
mock_log.info.assert_called_with("Found %s dangling volumes", count)
|
||||
|
||||
|
||||
def test_build_exclude_set():
|
||||
image_tags = [
|
||||
'some_image:latest',
|
||||
'repo/foo:12345',
|
||||
'duplicate:latest',
|
||||
]
|
||||
exclude_image_file = StringIO(
|
||||
textwrap.dedent("""
|
||||
# Exclude this one because
|
||||
duplicate:latest
|
||||
# Also this one
|
||||
repo/bar:abab
|
||||
"""))
|
||||
expected = set([
|
||||
'some_image:latest',
|
||||
'repo/foo:12345',
|
||||
'duplicate:latest',
|
||||
'repo/bar:abab',
|
||||
])
|
||||
|
||||
exclude_set = docker_gc.build_exclude_set(image_tags, exclude_image_file)
|
||||
assert exclude_set == expected
|
||||
|
||||
|
||||
def test_format_exclude_labels():
|
||||
exclude_label_args = [
|
||||
'voo*',
|
||||
'doo=poo',
|
||||
]
|
||||
expected = [
|
||||
docker_gc.ExcludeLabel(key='voo*', value=None),
|
||||
docker_gc.ExcludeLabel(key='doo', value='poo'),
|
||||
]
|
||||
exclude_labels = docker_gc.format_exclude_labels(exclude_label_args)
|
||||
assert expected == exclude_labels
|
||||
|
||||
|
||||
def test_build_exclude_set_empty():
|
||||
exclude_set = docker_gc.build_exclude_set(None, None)
|
||||
assert exclude_set == set()
|
||||
|
||||
|
||||
def test_main(mock_client):
|
||||
with mock.patch('docker_custodian.docker_gc.docker.APIClient', return_value=mock_client):
|
||||
|
||||
with mock.patch('docker_custodian.docker_gc.get_args', autospec=True) as mock_get_args:
|
||||
mock_get_args.return_value = mock.Mock(
|
||||
max_image_age=100,
|
||||
max_container_age=200,
|
||||
exclude_image=[],
|
||||
exclude_image_file=None,
|
||||
exclude_container_label=[],
|
||||
)
|
||||
docker_gc.main()
|
0
dockertidy/tests/fixtures/__init__.py
vendored
Normal file
0
dockertidy/tests/fixtures/__init__.py
vendored
Normal file
92
dockertidy/tests/fixtures/fixtures.py
vendored
Normal file
92
dockertidy/tests/fixtures/fixtures.py
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
"""Global pytest fixtures."""
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
from dateutil import tz
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
return {
|
||||
"Id": "abcdabcdabcdabcd",
|
||||
"Created": "2013-12-20T17:00:00Z",
|
||||
"Name": "/container_name",
|
||||
"State": {
|
||||
"Running": False,
|
||||
"FinishedAt": "2014-01-01T17:30:00Z",
|
||||
"StartedAt": "2014-01-01T17:01:00Z",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def containers():
|
||||
return [
|
||||
{
|
||||
"Id": "abcd",
|
||||
"Name": "one",
|
||||
"Created": "2014-01-01T01:01:01Z",
|
||||
"State": {
|
||||
"Running": False,
|
||||
"FinishedAt": "2014-01-01T01:01:01Z",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Id": "abbb",
|
||||
"Name": "two",
|
||||
"Created": "2014-01-01T01:01:01Z",
|
||||
"State": {
|
||||
"Running": True,
|
||||
"FinishedAt": "2014-01-01T01:01:01Z",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def image():
|
||||
return {
|
||||
"Id": "abcdabcdabcdabcd",
|
||||
"Created": "2014-01-20T05:00:00Z",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def images():
|
||||
return [
|
||||
{
|
||||
"RepoTags": ["<none>:<none>"],
|
||||
"Id": "2471708c19beabababab"
|
||||
},
|
||||
{
|
||||
"RepoTags": ["<none>:<none>"],
|
||||
"Id": "babababababaabababab"
|
||||
},
|
||||
{
|
||||
"RepoTags": ["user/one:latest", "user/one:abcd"]
|
||||
},
|
||||
{
|
||||
"RepoTags": ["other-1:abcda"]
|
||||
},
|
||||
{
|
||||
"RepoTags": ["other-2:abc45"]
|
||||
},
|
||||
{
|
||||
"RepoTags": ["new_image:latest", "new_image:123"]
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def now():
|
||||
return datetime.datetime(2014, 1, 20, 10, 10, tzinfo=tz.tzutc())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def earlier_time():
|
||||
return datetime.datetime(2014, 1, 1, 0, 0, tzinfo=tz.tzutc())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def later_time():
|
||||
return datetime.datetime(2014, 1, 20, 0, 10, tzinfo=tz.tzutc())
|
42
dockertidy/tests/unit/test_autostop.py
Normal file
42
dockertidy/tests/unit/test_autostop.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""Test Autostop class."""
|
||||
|
||||
import docker
|
||||
import pytest
|
||||
|
||||
from dockertidy import Autostop
|
||||
|
||||
pytest_plugins = [
|
||||
"dockertidy.tests.fixtures.fixtures",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def autostop():
|
||||
stop = Autostop.AutoStop()
|
||||
return stop
|
||||
|
||||
|
||||
def test_stop_container(autostop, mocker):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
cid = "asdb"
|
||||
|
||||
autostop._stop_container(client, cid)
|
||||
client.stop.assert_called_once_with(cid)
|
||||
|
||||
|
||||
def test_build_container_matcher(autostop, mocker):
|
||||
prefixes = ["one_", "two_"]
|
||||
matcher = autostop._build_container_matcher(prefixes)
|
||||
|
||||
assert matcher("one_container")
|
||||
assert matcher("two_container")
|
||||
assert not matcher("three_container")
|
||||
assert not matcher("one")
|
||||
|
||||
|
||||
def test_has_been_running_since_true(autostop, container, later_time):
|
||||
assert autostop._has_been_running_since(container, later_time)
|
||||
|
||||
|
||||
def test_has_been_running_since_false(autostop, container, earlier_time):
|
||||
assert not autostop._has_been_running_since(container, earlier_time)
|
449
dockertidy/tests/unit/test_garbagecollector.py
Normal file
449
dockertidy/tests/unit/test_garbagecollector.py
Normal file
@ -0,0 +1,449 @@
|
||||
"""Test GarbageCollector class."""
|
||||
|
||||
import docker
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from dockertidy import GarbageCollector
|
||||
|
||||
pytest_plugins = [
|
||||
"dockertidy.tests.fixtures.fixtures",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gc():
|
||||
gc = GarbageCollector.GarbageCollector()
|
||||
return gc
|
||||
|
||||
|
||||
def test_is_running(gc, container, now):
|
||||
container["State"]["Running"] = True
|
||||
|
||||
assert not gc._should_remove_container(container, now)
|
||||
|
||||
|
||||
def test_is_ghost(gc, container, now):
|
||||
container["State"]["Ghost"] = True
|
||||
|
||||
assert gc._should_remove_container(container, now)
|
||||
|
||||
|
||||
def test_old_never_run(gc, container, now, earlier_time):
|
||||
container["Created"] = str(earlier_time)
|
||||
container["State"]["FinishedAt"] = gc.YEAR_ZERO
|
||||
|
||||
assert gc._should_remove_container(container, now)
|
||||
|
||||
|
||||
def test_not_old_never_run(gc, container, now, earlier_time):
|
||||
container["Created"] = str(now)
|
||||
container["State"]["FinishedAt"] = gc.YEAR_ZERO
|
||||
|
||||
assert not gc._should_remove_container(container, now)
|
||||
|
||||
|
||||
def test_old_stopped(gc, container, now):
|
||||
assert gc._should_remove_container(container, now)
|
||||
|
||||
|
||||
def test_not_old(gc, container, now):
|
||||
container["State"]["FinishedAt"] = "2014-01-21T00:00:00Z"
|
||||
|
||||
assert not gc._should_remove_container(container, now)
|
||||
|
||||
|
||||
def test_cleanup_containers(gc, mocker, containers):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
client.containers.return_value = [
|
||||
{
|
||||
"Id": "abcd"
|
||||
},
|
||||
{
|
||||
"Id": "abbb"
|
||||
},
|
||||
]
|
||||
client.inspect_container.side_effect = iter(containers)
|
||||
|
||||
gc.config.config["gc"]["max_container_age"] = "0day"
|
||||
gc.docker = client
|
||||
gc.cleanup_containers()
|
||||
client.remove_container.assert_called_once_with(container="abcd", v=True)
|
||||
|
||||
|
||||
def test_filter_excluded_containers(gc):
|
||||
containers = [
|
||||
{
|
||||
"Labels": {
|
||||
"toot": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"Labels": {
|
||||
"too": "lol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Labels": {
|
||||
"toots": "lol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Labels": {
|
||||
"foo": "bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Labels": None
|
||||
},
|
||||
]
|
||||
|
||||
result = gc._filter_excluded_containers(containers)
|
||||
assert containers == list(result)
|
||||
|
||||
gc.config.config["gc"]["exclude_container_labels"] = [
|
||||
gc.ExcludeLabel(key="too", value=None),
|
||||
gc.ExcludeLabel(key="foo", value=None),
|
||||
]
|
||||
result = gc._filter_excluded_containers(containers)
|
||||
assert [containers[0], containers[2], containers[4]] == list(result)
|
||||
|
||||
gc.config.config["gc"]["exclude_container_labels"] = [
|
||||
gc.ExcludeLabel(key="too*", value="lol"),
|
||||
]
|
||||
result = gc._filter_excluded_containers(containers)
|
||||
assert [containers[0], containers[3], containers[4]] == list(result)
|
||||
|
||||
|
||||
def test_cleanup_images(mocker, gc, containers):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
client._version = "1.21"
|
||||
client.images.return_value = images = [
|
||||
{
|
||||
"Id": "abcd"
|
||||
},
|
||||
{
|
||||
"Id": "abbb"
|
||||
},
|
||||
]
|
||||
client.inspect_image.side_effect = iter(containers)
|
||||
|
||||
gc.docker = client
|
||||
gc.config.config["gc"]["max_image_age"] = "0days"
|
||||
gc.cleanup_images(client)
|
||||
assert client.remove_image.mock_calls == [
|
||||
mocker.call(image=image["Id"]) for image in reversed(images)
|
||||
]
|
||||
|
||||
|
||||
def test_cleanup_volumes(mocker, gc):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
client.volumes.return_value = volumes = {
|
||||
"Volumes": [
|
||||
{
|
||||
"Mountpoint": "unused",
|
||||
"Labels": None,
|
||||
"Driver": "unused",
|
||||
"Name": u"one"
|
||||
},
|
||||
{
|
||||
"Mountpoint": "unused",
|
||||
"Labels": None,
|
||||
"Driver": "unused",
|
||||
"Name": u"two"
|
||||
},
|
||||
],
|
||||
"Warnings": None,
|
||||
}
|
||||
|
||||
gc.docker = client
|
||||
gc.cleanup_volumes()
|
||||
assert client.remove_volume.mock_calls == [
|
||||
mocker.call(name=volume["Name"]) for volume in reversed(volumes["Volumes"])
|
||||
]
|
||||
|
||||
|
||||
def test_filter_images_in_use(gc, images):
|
||||
image_tags_in_use = set([
|
||||
"user/one:latest",
|
||||
"user/foo:latest",
|
||||
"other-2:abc45",
|
||||
"2471708c19be:latest",
|
||||
])
|
||||
expected = [
|
||||
{
|
||||
"RepoTags": ["<none>:<none>"],
|
||||
"Id": "babababababaabababab"
|
||||
},
|
||||
{
|
||||
"RepoTags": ["other-1:abcda"]
|
||||
},
|
||||
{
|
||||
"RepoTags": ["new_image:latest", "new_image:123"]
|
||||
},
|
||||
]
|
||||
|
||||
actual = gc._filter_images_in_use(images, image_tags_in_use)
|
||||
assert list(actual) == expected
|
||||
|
||||
|
||||
def test_filter_images_in_use_by_id(mocker, gc, containers):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
client._version = "1.21"
|
||||
client.containers.return_value = [
|
||||
{
|
||||
"Id": "abcd",
|
||||
"ImageID": "1"
|
||||
},
|
||||
{
|
||||
"Id": "abbb",
|
||||
"ImageID": "2"
|
||||
},
|
||||
]
|
||||
|
||||
client.inspect_container.side_effect = iter(containers)
|
||||
client.images.return_value = [
|
||||
{
|
||||
"Id": "1",
|
||||
"Created": "2014-01-01T01:01:01Z"
|
||||
},
|
||||
{
|
||||
"Id": "2",
|
||||
"Created": "2014-01-01T01:01:01Z"
|
||||
},
|
||||
{
|
||||
"Id": "3",
|
||||
"Created": "2014-01-01T01:01:01Z"
|
||||
},
|
||||
{
|
||||
"Id": "4",
|
||||
"Created": "2014-01-01T01:01:01Z"
|
||||
},
|
||||
{
|
||||
"Id": "5",
|
||||
"Created": "2014-01-01T01:01:01Z"
|
||||
},
|
||||
{
|
||||
"Id": "6",
|
||||
"Created": "2014-01-01T01:01:01Z"
|
||||
},
|
||||
]
|
||||
|
||||
client.inspect_image.side_effect = lambda image: {
|
||||
"Id": image,
|
||||
"Created": "2014-01-01T01:01:01Z"
|
||||
}
|
||||
|
||||
gc.docker = client
|
||||
gc.config.config["gc"]["max_image_age"] = "0days"
|
||||
gc.cleanup_images(set())
|
||||
assert client.remove_image.mock_calls == [
|
||||
mocker.call(image=id_) for id_ in ["6", "5", "4", "3"]
|
||||
]
|
||||
|
||||
|
||||
def test_filter_excluded_images(gc, images):
|
||||
exclude_set = set([
|
||||
"user/one:latest",
|
||||
"user/foo:latest",
|
||||
"other-2:abc45",
|
||||
])
|
||||
expected = [
|
||||
{
|
||||
"RepoTags": ["<none>:<none>"],
|
||||
"Id": "2471708c19beabababab"
|
||||
},
|
||||
{
|
||||
"RepoTags": ["<none>:<none>"],
|
||||
"Id": "babababababaabababab"
|
||||
},
|
||||
{
|
||||
"RepoTags": ["other-1:abcda"]
|
||||
},
|
||||
{
|
||||
"RepoTags": ["new_image:latest", "new_image:123"]
|
||||
},
|
||||
]
|
||||
|
||||
actual = gc._filter_excluded_images(images, exclude_set)
|
||||
assert list(actual) == expected
|
||||
|
||||
|
||||
def test_filter_excluded_images_advanced(gc, images):
|
||||
exclude_set = set([
|
||||
"user/one:*",
|
||||
"new_*:123",
|
||||
"other-1:abc*",
|
||||
])
|
||||
expected = [
|
||||
{
|
||||
"RepoTags": ["<none>:<none>"],
|
||||
"Id": "2471708c19beabababab"
|
||||
},
|
||||
{
|
||||
"RepoTags": ["<none>:<none>"],
|
||||
"Id": "babababababaabababab"
|
||||
},
|
||||
{
|
||||
"RepoTags": ["other-2:abc45"]
|
||||
},
|
||||
]
|
||||
|
||||
actual = gc._filter_excluded_images(images, exclude_set)
|
||||
assert list(actual) == expected
|
||||
|
||||
|
||||
def test_is_image_old(gc, image, now):
|
||||
assert gc._is_image_old(image, now)
|
||||
|
||||
|
||||
def test_is_image_old_false(gc, image, later_time):
|
||||
assert not gc._is_image_old(image, later_time)
|
||||
|
||||
|
||||
def test_remove_image_no_tags(mocker, gc, image, now):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
image_id = "abcd"
|
||||
image_summary = {"Id": image_id}
|
||||
client.inspect_image.return_value = image
|
||||
|
||||
gc.docker = client
|
||||
gc._remove_image(image_summary, now)
|
||||
client.remove_image.assert_called_once_with(image=image_id)
|
||||
|
||||
|
||||
def test_remove_image_new_image_not_removed(mocker, gc, image, later_time):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
image_id = "abcd"
|
||||
image_summary = {"Id": image_id}
|
||||
client.inspect_image.return_value = image
|
||||
|
||||
gc.docker = client
|
||||
gc._remove_image(image_summary, later_time)
|
||||
assert not client.remove_image.mock_calls
|
||||
|
||||
|
||||
def test_remove_image_with_tags(mocker, gc, image, now):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
image_id = "abcd"
|
||||
repo_tags = ["user/one:latest", "user/one:12345"]
|
||||
image_summary = {"Id": image_id, "RepoTags": repo_tags}
|
||||
client.inspect_image.return_value = image
|
||||
|
||||
gc.docker = client
|
||||
gc._remove_image(image_summary, now)
|
||||
assert client.remove_image.mock_calls == [mocker.call(image=tag) for tag in repo_tags]
|
||||
|
||||
|
||||
def test_api_call_success(mocker, gc):
|
||||
func = mocker.Mock()
|
||||
container = "abcd"
|
||||
result = gc._api_call(func, container=container)
|
||||
func.assert_called_once_with(container="abcd")
|
||||
|
||||
assert result == func.return_value
|
||||
|
||||
|
||||
def test_api_call_with_timeout(mocker, gc):
|
||||
func = mocker.Mock(side_effect=requests.exceptions.ReadTimeout("msg"), __name__="remove_image")
|
||||
image = "abcd"
|
||||
|
||||
mock_log = mocker.patch.object(gc, "logger", autospec=True)
|
||||
gc._api_call(func, image=image)
|
||||
|
||||
func.assert_called_once_with(image="abcd")
|
||||
mock_log.warn.assert_called_once_with("Failed to call remove_image " + "image=abcd msg")
|
||||
|
||||
|
||||
def test_api_call_with_api_error(mocker, gc):
|
||||
func = mocker.Mock(
|
||||
side_effect=docker.errors.APIError(
|
||||
"Ooops", mocker.Mock(status_code=409, reason="Conflict"), explanation="failed"
|
||||
),
|
||||
__name__="remove_image"
|
||||
)
|
||||
image = "abcd"
|
||||
|
||||
mock_log = mocker.patch.object(gc, "logger", autospec=True)
|
||||
gc._api_call(func, image=image)
|
||||
|
||||
func.assert_called_once_with(image="abcd")
|
||||
mock_log.warn.assert_called_once_with(
|
||||
"Error calling remove_image image=abcd "
|
||||
'409 Client Error: Conflict ("failed")'
|
||||
)
|
||||
|
||||
|
||||
def test_get_all_containers(mocker, gc):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
count = 10
|
||||
client.containers.return_value = [mocker.Mock() for _ in range(count)]
|
||||
|
||||
mock_log = mocker.patch.object(gc, "logger", autospec=True)
|
||||
|
||||
gc.docker = client
|
||||
containers = gc._get_all_containers()
|
||||
assert containers == client.containers.return_value
|
||||
client.containers.assert_called_once_with(all=True)
|
||||
mock_log.info.assert_called_with("Found %s containers", count)
|
||||
|
||||
|
||||
def test_get_all_images(mocker, gc):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
count = 7
|
||||
client.images.return_value = [mocker.Mock() for _ in range(count)]
|
||||
|
||||
mock_log = mocker.patch.object(gc, "logger", autospec=True)
|
||||
|
||||
gc.docker = client
|
||||
images = gc._get_all_images()
|
||||
assert images == client.images.return_value
|
||||
mock_log.info.assert_called_with("Found %s images", count)
|
||||
|
||||
|
||||
def test_get_dangling_volumes(mocker, gc):
|
||||
client = mocker.create_autospec(docker.APIClient)
|
||||
count = 4
|
||||
client.volumes.return_value = {"Volumes": [mocker.Mock() for _ in range(count)]}
|
||||
|
||||
mock_log = mocker.patch.object(gc, "logger", autospec=True)
|
||||
|
||||
gc.docker = client
|
||||
volumes = gc._get_dangling_volumes()
|
||||
assert volumes == client.volumes.return_value["Volumes"]
|
||||
mock_log.info.assert_called_with("Found %s dangling volumes", count)
|
||||
|
||||
|
||||
def test_build_exclude_set(gc):
|
||||
gc.config.config["gc"]["exclude_images"] = [
|
||||
"some_image:latest",
|
||||
"repo/foo:12345",
|
||||
"duplicate:latest",
|
||||
]
|
||||
expected = set([
|
||||
"some_image:latest",
|
||||
"repo/foo:12345",
|
||||
"duplicate:latest",
|
||||
])
|
||||
|
||||
exclude_set = gc._build_exclude_set()
|
||||
assert exclude_set == expected
|
||||
|
||||
|
||||
def test_format_exclude_labels(gc):
|
||||
gc.config.config["gc"]["exclude_container_label"] = [
|
||||
"voo*",
|
||||
"doo=poo",
|
||||
]
|
||||
expected = [
|
||||
gc.ExcludeLabel(key="voo*", value=None),
|
||||
gc.ExcludeLabel(key="doo", value="poo"),
|
||||
]
|
||||
exclude_labels = gc._format_exclude_labels()
|
||||
assert expected == exclude_labels
|
||||
|
||||
|
||||
def test_build_exclude_set_empty(gc):
|
||||
gc.config.config["gc"]["exclude_images"] = []
|
||||
exclude_set = gc._build_exclude_set()
|
||||
assert exclude_set == set()
|
Loading…
Reference in New Issue
Block a user