salt/tests/unit/modules/test_dockermod.py
Erik Johnson 774a3f1147 Rename docker execution module to avoid shadowing in the loader
With the merging of #39996, salt/modules/docker.py now imports
salt.utils.docker. However, our loader appends the module dir (in
this case salt/modules/) to sys.path temporarily for the length of the
loading process. So, as the docker execution module tries to import
salt.utils.docker, when salt.utils.docker attempts to do an "import
docker", and docker-py is *not* installed, this results in
salt/modules/docker.py (the docker execution module) being loaded in its
place, which results in tracebacks in the minion log.

Renaming the docker execution module keeps this import shadowing from
occurring. Note that we don't need to do this for the placeholder
salt/states/docker.py as it does not import salt.utils.docker.
2017-03-28 10:50:47 -05:00

679 lines
27 KiB
Python

# -*- coding: utf-8 -*-
'''
Unit tests for the docker module
'''
# Import Python Libs
from __future__ import absolute_import
# Import Salt Testing Libs
from tests.support.unit import skipIf, TestCase
from tests.support.mock import (
MagicMock,
Mock,
NO_MOCK,
NO_MOCK_REASON,
patch
)
# Import Salt Libs
from salt.ext.six.moves import range
from salt.exceptions import CommandExecutionError
import salt.modules.dockermod as docker_mod
docker_mod.__context__ = {'docker.docker_version': ''}
docker_mod.__salt__ = {}
docker_mod.__opts__ = {}
def _docker_py_version():
try:
if docker_mod.HAS_DOCKER_PY:
return docker_mod.docker.version_info
except AttributeError:
pass
return (0,)
@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(docker_mod.HAS_DOCKER_PY is False, 'docker-py must be installed to run these tests. Skipping.')
class DockerTestCase(TestCase):
'''
Validate docker module
'''
def test_ps_with_host_true(self):
'''
Check that docker.ps called with host is ``True``,
include resutlt of ``network.interfaces`` command in returned result.
'''
network_interfaces = Mock(return_value={'mocked': None})
with patch.dict(docker_mod.__salt__,
{'network.interfaces': network_interfaces}):
with patch.dict(docker_mod.__context__,
{'docker.client': MagicMock()}):
ret = docker_mod.ps_(host=True)
self.assertEqual(ret,
{'host': {'interfaces': {'mocked': None}}})
def test_ps_with_filters(self):
'''
Check that docker.ps accept filters parameter.
'''
client = MagicMock()
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.ps_(filters={'label': 'KEY'})
client.containers.assert_called_once_with(
all=True,
filters={'label': 'KEY'})
@patch.object(docker_mod, '_get_exec_driver')
def test_check_mine_cache_is_refreshed_on_container_change_event(self, _):
'''
Every command that might modify docker containers state.
Should trig an update on ``mine.send``
'''
client_args_mock = MagicMock(return_value={
'create_container': [
'image', 'command', 'hostname', 'user', 'detach', 'stdin_open',
'tty', 'ports', 'environment', 'volumes', 'network_disabled',
'name', 'entrypoint', 'working_dir', 'domainname', 'cpuset',
'host_config', 'mac_address', 'labels', 'volume_driver',
'stop_signal', 'networking_config', 'healthcheck',
'stop_timeout'],
'host_config': [
'binds', 'port_bindings', 'lxc_conf', 'publish_all_ports',
'links', 'privileged', 'dns', 'dns_search', 'volumes_from',
'network_mode', 'restart_policy', 'cap_add', 'cap_drop',
'devices', 'extra_hosts', 'read_only', 'pid_mode', 'ipc_mode',
'security_opt', 'ulimits', 'log_config', 'mem_limit',
'memswap_limit', 'mem_reservation', 'kernel_memory',
'mem_swappiness', 'cgroup_parent', 'group_add', 'cpu_quota',
'cpu_period', 'blkio_weight', 'blkio_weight_device',
'device_read_bps', 'device_write_bps', 'device_read_iops',
'device_write_iops', 'oom_kill_disable', 'shm_size', 'sysctls',
'tmpfs', 'oom_score_adj', 'dns_opt', 'cpu_shares',
'cpuset_cpus', 'userns_mode', 'pids_limit', 'isolation',
'auto_remove', 'storage_opt'],
'networking_config': [
'aliases', 'links', 'ipv4_address', 'ipv6_address',
'link_local_ips'],
}
)
for command_name, args in (('create', ()),
('rm_', ()),
('kill', ()),
('pause', ()),
('signal_', ('KILL',)),
('start', ()),
('stop', ()),
('unpause', ()),
('_run', ('command',)),
('_script', ('command',)),
):
mine_send = Mock()
command = getattr(docker_mod, command_name)
docker_client = MagicMock()
docker_client.api_version = '1.12'
with patch.dict(docker_mod.__salt__,
{'mine.send': mine_send,
'container_resource.run': MagicMock(),
'cp.cache_file': MagicMock(return_value=False),
'docker.get_client_args': client_args_mock}):
with patch.dict(docker_mod.__context__,
{'docker.client': docker_client}):
command('container', *args)
mine_send.assert_called_with('docker.ps', verbose=True, all=True,
host=True)
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_list_networks(self, *args):
'''
test list networks.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.networks(
names=['foo'],
ids=['01234'],
)
client.networks.assert_called_once_with(
names=['foo'],
ids=['01234'],
)
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_create_network(self, *args):
'''
test create network.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.create_network(
'foo',
driver='bridge',
)
client.create_network.assert_called_once_with(
'foo',
driver='bridge',
)
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_remove_network(self, *args):
'''
test remove network.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.remove_network('foo')
client.remove_network.assert_called_once_with('foo')
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_inspect_network(self, *args):
'''
test inspect network.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.inspect_network('foo')
client.inspect_network.assert_called_once_with('foo')
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_connect_container_to_network(self, *args):
'''
test inspect network.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {}
client = Mock()
client.api_version = '1.21'
context = {'docker.client': client,
'docker.exec_driver': 'docker-exec'}
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__, context):
docker_mod.connect_container_to_network('container', 'foo')
client.connect_container_to_network.assert_called_once_with(
'container', 'foo', None)
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_disconnect_container_from_network(self, *args):
'''
test inspect network.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.disconnect_container_from_network('container', 'foo')
client.disconnect_container_from_network.assert_called_once_with(
'container', 'foo')
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_list_volumes(self, *args):
'''
test list volumes.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.volumes(
filters={'dangling': [True]},
)
client.volumes.assert_called_once_with(
filters={'dangling': [True]},
)
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_create_volume(self, *args):
'''
test create volume.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.create_volume(
'foo',
driver='bridge',
driver_opts={},
)
client.create_volume.assert_called_once_with(
'foo',
driver='bridge',
driver_opts={},
)
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_remove_volume(self, *args):
'''
test remove volume.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.remove_volume('foo')
client.remove_volume.assert_called_once_with('foo')
@skipIf(_docker_py_version() < (1, 5, 0),
'docker module must be installed to run this test or is too old. >=1.5.0')
def test_inspect_volume(self, *args):
'''
test inspect volume.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
client = Mock()
client.api_version = '1.21'
with patch.dict(docker_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod.inspect_volume('foo')
client.inspect_volume.assert_called_once_with('foo')
def test_wait_success(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=0)
docker_inspect_container = Mock(side_effect=[
{'State': {'Running': True}},
{'State': {'Stopped': True}}])
with patch.object(docker_mod, 'inspect_container',
docker_inspect_container):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod._clear_context()
result = docker_mod.wait('foo')
self.assertEqual(result, {'result': True,
'exit_status': 0,
'state': {'new': 'stopped',
'old': 'running'}})
def test_wait_fails_already_stopped(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=0)
docker_inspect_container = Mock(side_effect=[
{'State': {'Stopped': True}},
{'State': {'Stopped': True}},
])
with patch.object(docker_mod, 'inspect_container',
docker_inspect_container):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod._clear_context()
result = docker_mod.wait('foo')
self.assertEqual(result, {'result': False,
'comment': "Container 'foo' already stopped",
'exit_status': 0,
'state': {'new': 'stopped',
'old': 'stopped'}})
def test_wait_success_already_stopped(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=0)
docker_inspect_container = Mock(side_effect=[
{'State': {'Stopped': True}},
{'State': {'Stopped': True}},
])
with patch.object(docker_mod, 'inspect_container',
docker_inspect_container):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod._clear_context()
result = docker_mod.wait('foo', ignore_already_stopped=True)
self.assertEqual(result, {'result': True,
'comment': "Container 'foo' already stopped",
'exit_status': 0,
'state': {'new': 'stopped',
'old': 'stopped'}})
def test_wait_success_absent_container(self):
client = Mock()
client.api_version = '1.21'
docker_inspect_container = Mock(side_effect=CommandExecutionError)
with patch.object(docker_mod, 'inspect_container',
docker_inspect_container):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod._clear_context()
result = docker_mod.wait('foo', ignore_already_stopped=True)
self.assertEqual(result, {'result': True,
'comment': "Container 'foo' absent"})
def test_wait_fails_on_exit_status(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=1)
docker_inspect_container = Mock(side_effect=[
{'State': {'Running': True}},
{'State': {'Stopped': True}}])
with patch.object(docker_mod, 'inspect_container',
docker_inspect_container):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod._clear_context()
result = docker_mod.wait('foo', fail_on_exit_status=True)
self.assertEqual(result, {'result': False,
'exit_status': 1,
'state': {'new': 'stopped',
'old': 'running'}})
def test_wait_fails_on_exit_status_and_already_stopped(self):
client = Mock()
client.api_version = '1.21'
client.wait = Mock(return_value=1)
docker_inspect_container = Mock(side_effect=[
{'State': {'Stopped': True}},
{'State': {'Stopped': True}}])
with patch.object(docker_mod, 'inspect_container',
docker_inspect_container):
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod._clear_context()
result = docker_mod.wait('foo',
ignore_already_stopped=True,
fail_on_exit_status=True)
self.assertEqual(result, {'result': False,
'comment': "Container 'foo' already stopped",
'exit_status': 1,
'state': {'new': 'stopped',
'old': 'stopped'}})
def test_sls_build(self, *args):
'''
test build sls image.
'''
docker_start_mock = MagicMock(
return_value={})
docker_create_mock = MagicMock(
return_value={'Id': 'ID', 'Name': 'NAME'})
docker_stop_mock = MagicMock(
return_value={'state': {'old': 'running', 'new': 'stopped'},
'result': True})
docker_rm_mock = MagicMock(return_value={})
docker_commit_mock = MagicMock(
return_value={'Id': 'ID2', 'Image': 'foo', 'Time_Elapsed': 42})
docker_sls_mock = MagicMock(
return_value={
"file_|-/etc/test.sh_|-/etc/test.sh_|-managed": {
"comment": "File /etc/test.sh is in the correct state",
"name": "/etc/test.sh",
"start_time": "07:04:26.834792",
"result": True,
"duration": 13.492,
"__run_num__": 0,
"changes": {}
},
"test_|-always-passes_|-foo_|-succeed_without_changes": {
"comment": "Success!",
"name": "foo",
"start_time": "07:04:26.848915",
"result": True,
"duration": 0.363,
"__run_num__": 1,
"changes": {}
}
})
ret = None
with patch.object(docker_mod, 'start', docker_start_mock):
with patch.object(docker_mod, 'create', docker_create_mock):
with patch.object(docker_mod, 'stop', docker_stop_mock):
with patch.object(docker_mod, 'commit', docker_commit_mock):
with patch.object(docker_mod, 'sls', docker_sls_mock):
with patch.object(docker_mod, 'rm_', docker_rm_mock):
ret = docker_mod.sls_build(
'foo',
mods='foo',
)
docker_create_mock.assert_called_once_with(
cmd='sleep infinity',
image='opensuse/python', interactive=True, tty=True)
docker_start_mock.assert_called_once_with('ID')
docker_sls_mock.assert_called_once_with('ID', 'foo', 'base')
docker_stop_mock.assert_called_once_with('ID')
docker_rm_mock.assert_called_once_with('ID')
docker_commit_mock.assert_called_once_with('ID', 'foo')
self.assertEqual(
{'Id': 'ID2', 'Image': 'foo', 'Time_Elapsed': 42}, ret)
def test_sls_build_dryrun(self, *args):
'''
test build sls image in dryrun mode.
'''
docker_start_mock = MagicMock(
return_value={})
docker_create_mock = MagicMock(
return_value={'Id': 'ID', 'Name': 'NAME'})
docker_stop_mock = MagicMock(
return_value={'state': {'old': 'running', 'new': 'stopped'},
'result': True})
docker_rm_mock = MagicMock(
return_value={})
docker_sls_mock = MagicMock(
return_value={
"file_|-/etc/test.sh_|-/etc/test.sh_|-managed": {
"comment": "File /etc/test.sh is in the correct state",
"name": "/etc/test.sh",
"start_time": "07:04:26.834792",
"result": True,
"duration": 13.492,
"__run_num__": 0,
"changes": {}
},
"test_|-always-passes_|-foo_|-succeed_without_changes": {
"comment": "Success!",
"name": "foo",
"start_time": "07:04:26.848915",
"result": True,
"duration": 0.363,
"__run_num__": 1,
"changes": {}
}
})
ret = None
with patch.object(docker_mod, 'start', docker_start_mock):
with patch.object(docker_mod, 'create', docker_create_mock):
with patch.object(docker_mod, 'stop', docker_stop_mock):
with patch.object(docker_mod, 'rm_', docker_rm_mock):
with patch.object(docker_mod, 'sls', docker_sls_mock):
ret = docker_mod.sls_build(
'foo',
mods='foo',
dryrun=True
)
docker_create_mock.assert_called_once_with(
cmd='sleep infinity',
image='opensuse/python', interactive=True, tty=True)
docker_start_mock.assert_called_once_with('ID')
docker_sls_mock.assert_called_once_with('ID', 'foo', 'base')
docker_stop_mock.assert_called_once_with('ID')
docker_rm_mock.assert_called_once_with('ID')
self.assertEqual(
{
"file_|-/etc/test.sh_|-/etc/test.sh_|-managed": {
"comment": "File /etc/test.sh is in the correct state",
"name": "/etc/test.sh",
"start_time": "07:04:26.834792",
"result": True,
"duration": 13.492,
"__run_num__": 0,
"changes": {}
},
"test_|-always-passes_|-foo_|-succeed_without_changes": {
"comment": "Success!",
"name": "foo",
"start_time": "07:04:26.848915",
"result": True,
"duration": 0.363,
"__run_num__": 1,
"changes": {}
}
},
ret)
def test_call_success(self):
'''
test module calling inside containers
'''
ret = None
docker_run_all_mock = MagicMock(
return_value={
'retcode': 0,
'stdout': '{"retcode": 0, "comment": "container cmd"}',
'stderr': 'err',
})
docker_copy_to_mock = MagicMock(
return_value={
'retcode': 0
})
docker_config_mock = MagicMock(
return_value=''
)
client = Mock()
client.put_archive = Mock()
context = {'docker.client': client,
'docker.exec_driver': 'docker-exec'}
salt_dunder = {'config.option': docker_config_mock}
with patch.object(docker_mod, 'run_all', docker_run_all_mock):
with patch.object(docker_mod, 'copy_to', docker_copy_to_mock):
with patch.dict(docker_mod.__opts__, {'cachedir': '/tmp'}):
with patch.dict(docker_mod.__salt__, salt_dunder):
with patch.dict(docker_mod.__context__, context):
# call twice to verify tmp path later
for i in range(2):
ret = docker_mod.call(
'ID',
'test.arg',
1, 2,
arg1='val1')
# Check that the directory is different each time
# [ call(name, [args]), ...
self.assertIn('mkdir', docker_run_all_mock.mock_calls[0][1][1])
self.assertIn('mkdir', docker_run_all_mock.mock_calls[3][1][1])
self.assertNotEqual(docker_run_all_mock.mock_calls[0][1][1],
docker_run_all_mock.mock_calls[3][1][1])
self.assertIn('salt-call', docker_run_all_mock.mock_calls[1][1][1])
self.assertIn('salt-call', docker_run_all_mock.mock_calls[4][1][1])
self.assertNotEqual(docker_run_all_mock.mock_calls[1][1][1],
docker_run_all_mock.mock_calls[4][1][1])
# check directory cleanup
self.assertIn('rm -rf', docker_run_all_mock.mock_calls[2][1][1])
self.assertIn('rm -rf', docker_run_all_mock.mock_calls[5][1][1])
self.assertNotEqual(docker_run_all_mock.mock_calls[2][1][1],
docker_run_all_mock.mock_calls[5][1][1])
self.assertEqual({"retcode": 0, "comment": "container cmd"}, ret)
def test_images_with_empty_tags(self):
"""
docker 1.12 reports also images without tags with `null`.
"""
client = Mock()
client.api_version = '1.24'
client.images = Mock(
return_value=[{'Id': 'sha256:abcde',
'RepoTags': None},
{'Id': 'sha256:abcdef'},
{'Id': 'sha256:abcdefg',
'RepoTags': ['image:latest']}])
with patch.dict(docker_mod.__context__,
{'docker.client': client}):
docker_mod._clear_context()
result = docker_mod.images()
self.assertEqual(result,
{'sha256:abcdefg': {'RepoTags': ['image:latest']}})