Some more test adjustments for Swarm support

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2017-07-19 15:01:39 -07:00 committed by Joffrey F
parent 8f0ef26a73
commit 6a4adb64f9
9 changed files with 82 additions and 54 deletions

View File

@ -7,3 +7,5 @@ coverage-html
docs/_site docs/_site
venv venv
.tox .tox
**/__pycache__
*.pyc

View File

@ -20,8 +20,6 @@ from docker import errors
from .. import mock from .. import mock
from ..helpers import create_host_file from ..helpers import create_host_file
from ..helpers import is_cluster
from ..helpers import no_cluster
from compose.cli.command import get_project from compose.cli.command import get_project
from compose.config.errors import DuplicateOverrideFileFound from compose.config.errors import DuplicateOverrideFileFound
from compose.container import Container from compose.container import Container
@ -29,6 +27,8 @@ from compose.project import OneOffFilter
from compose.utils import nanoseconds_from_time_seconds from compose.utils import nanoseconds_from_time_seconds
from tests.integration.testcases import DockerClientTestCase from tests.integration.testcases import DockerClientTestCase
from tests.integration.testcases import get_links from tests.integration.testcases import get_links
from tests.integration.testcases import is_cluster
from tests.integration.testcases import no_cluster
from tests.integration.testcases import pull_busybox from tests.integration.testcases import pull_busybox
from tests.integration.testcases import SWARM_SKIP_RM_VOLUMES from tests.integration.testcases import SWARM_SKIP_RM_VOLUMES
from tests.integration.testcases import v2_1_only from tests.integration.testcases import v2_1_only
@ -116,7 +116,7 @@ class CLITestCase(DockerClientTestCase):
def tearDown(self): def tearDown(self):
if self.base_dir: if self.base_dir:
self.project.kill() self.project.kill()
self.project.remove_stopped() self.project.down(None, True)
for container in self.project.containers(stopped=True, one_off=OneOffFilter.only): for container in self.project.containers(stopped=True, one_off=OneOffFilter.only):
container.remove(force=True) container.remove(force=True)
@ -1214,6 +1214,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(proc.returncode, 1) self.assertEqual(proc.returncode, 1)
@v2_only() @v2_only()
@no_cluster('Container PID mode does not work across clusters')
def test_up_with_pid_mode(self): def test_up_with_pid_mode(self):
c = self.client.create_container( c = self.client.create_container(
'busybox', 'top', name='composetest_pid_mode_container', 'busybox', 'top', name='composetest_pid_mode_container',
@ -1244,8 +1245,8 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(self.project.containers()), 1) self.assertEqual(len(self.project.containers()), 1)
stdout, stderr = self.dispatch(['exec', '-T', 'console', 'ls', '-1d', '/']) stdout, stderr = self.dispatch(['exec', '-T', 'console', 'ls', '-1d', '/'])
self.assertEqual(stdout, "/\n")
self.assertEqual(stderr, "") self.assertEqual(stderr, "")
self.assertEqual(stdout, "/\n")
def test_exec_custom_user(self): def test_exec_custom_user(self):
self.base_dir = 'tests/fixtures/links-composefile' self.base_dir = 'tests/fixtures/links-composefile'
@ -1826,7 +1827,13 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['logs', '-f']) result = self.dispatch(['logs', '-f'])
if not is_cluster(self.client):
assert result.stdout.count('\n') == 5 assert result.stdout.count('\n') == 5
else:
# Sometimes logs are picked up from old containers that haven't yet
# been removed (removal in Swarm is async)
assert result.stdout.count('\n') >= 5
assert 'simple' in result.stdout assert 'simple' in result.stdout
assert 'another' in result.stdout assert 'another' in result.stdout
assert 'exited with code 0' in result.stdout assert 'exited with code 0' in result.stdout
@ -1882,7 +1889,10 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['up']) self.dispatch(['up'])
result = self.dispatch(['logs', '--tail', '2']) result = self.dispatch(['logs', '--tail', '2'])
assert result.stdout.count('\n') == 3 assert 'c\n' in result.stdout
assert 'd\n' in result.stdout
assert 'a\n' not in result.stdout
assert 'b\n' not in result.stdout
def test_kill(self): def test_kill(self):
self.dispatch(['up', '-d'], None) self.dispatch(['up', '-d'], None)
@ -2045,8 +2055,8 @@ class CLITestCase(DockerClientTestCase):
return result.stdout.rstrip() return result.stdout.rstrip()
assert get_port(3000) == container.get_local_port(3000) assert get_port(3000) == container.get_local_port(3000)
assert ':49152' in get_port(3001) assert ':53222' in get_port(3001)
assert ':49153' in get_port(3002) assert ':53223' in get_port(3002)
def test_port_with_scale(self): def test_port_with_scale(self):
self.base_dir = 'tests/fixtures/ports-composefile-scale' self.base_dir = 'tests/fixtures/ports-composefile-scale'

View File

@ -6,10 +6,10 @@ services:
ports: ports:
- target: 3000 - target: 3000
- target: 3001 - target: 3001
published: 49152 published: 53222
- target: 3002 - target: 3002
published: 49153 published: 53223
protocol: tcp protocol: tcp
- target: 3003 - target: 3003
published: 49154 published: 53224
protocol: udp protocol: udp

View File

@ -1,12 +1,8 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import functools
import os import os
from docker.errors import APIError
from pytest import skip
from compose.config.config import ConfigDetails from compose.config.config import ConfigDetails
from compose.config.config import ConfigFile from compose.config.config import ConfigFile
from compose.config.config import load from compose.config.config import load
@ -48,34 +44,3 @@ def create_host_file(client, filename):
"Container exited with code {}:\n{}".format(exitcode, output)) "Container exited with code {}:\n{}".format(exitcode, output))
finally: finally:
client.remove_container(container, force=True) client.remove_container(container, force=True)
def is_cluster(client):
nodes = None
def get_nodes_number():
try:
return len(client.nodes())
except APIError:
# If the Engine is not part of a Swarm, the SDK will raise
# an APIError
return 0
if nodes is None:
# Only make the API call if the value hasn't been cached yet
nodes = get_nodes_number()
return nodes > 1
def no_cluster(reason):
def decorator(f):
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
if is_cluster(self.client):
skip("Test will not be run in cluster mode: %s" % reason)
return
return f(self, *args, **kwargs)
return wrapper
return decorator

View File

@ -12,8 +12,6 @@ from docker.errors import NotFound
from .. import mock from .. import mock
from ..helpers import build_config as load_config from ..helpers import build_config as load_config
from ..helpers import create_host_file from ..helpers import create_host_file
from ..helpers import is_cluster
from ..helpers import no_cluster
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from .testcases import SWARM_SKIP_CONTAINERS_ALL from .testcases import SWARM_SKIP_CONTAINERS_ALL
from compose.config import config from compose.config import config
@ -33,6 +31,8 @@ from compose.errors import NoHealthCheckConfigured
from compose.project import Project from compose.project import Project
from compose.project import ProjectError from compose.project import ProjectError
from compose.service import ConvergenceStrategy from compose.service import ConvergenceStrategy
from tests.integration.testcases import is_cluster
from tests.integration.testcases import no_cluster
from tests.integration.testcases import v2_1_only from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_2_only from tests.integration.testcases import v2_2_only
from tests.integration.testcases import v2_only from tests.integration.testcases import v2_only

View File

@ -13,8 +13,6 @@ from six import StringIO
from six import text_type from six import text_type
from .. import mock from .. import mock
from ..helpers import is_cluster
from ..helpers import no_cluster
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from .testcases import get_links from .testcases import get_links
from .testcases import pull_busybox from .testcases import pull_busybox
@ -38,6 +36,8 @@ from compose.service import ConvergenceStrategy
from compose.service import NetworkMode from compose.service import NetworkMode
from compose.service import PidMode from compose.service import PidMode
from compose.service import Service from compose.service import Service
from tests.integration.testcases import is_cluster
from tests.integration.testcases import no_cluster
from tests.integration.testcases import v2_1_only from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_2_only from tests.integration.testcases import v2_2_only
from tests.integration.testcases import v2_only from tests.integration.testcases import v2_only
@ -635,7 +635,10 @@ class ServiceTest(DockerClientTestCase):
with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f: with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
f.write("FROM busybox\n") f.write("FROM busybox\n")
self.create_service('web', build={'context': base_dir}).build() service = self.create_service('web', build={'context': base_dir})
service.build()
self.addCleanup(self.client.remove_image, service.image_name)
assert self.client.inspect_image('composetest_web') assert self.client.inspect_image('composetest_web')
def test_build_non_ascii_filename(self): def test_build_non_ascii_filename(self):
@ -648,7 +651,9 @@ class ServiceTest(DockerClientTestCase):
with open(os.path.join(base_dir.encode('utf8'), b'foo\xE2bar'), 'w') as f: with open(os.path.join(base_dir.encode('utf8'), b'foo\xE2bar'), 'w') as f:
f.write("hello world\n") f.write("hello world\n")
self.create_service('web', build={'context': text_type(base_dir)}).build() service = self.create_service('web', build={'context': text_type(base_dir)})
service.build()
self.addCleanup(self.client.remove_image, service.image_name)
assert self.client.inspect_image('composetest_web') assert self.client.inspect_image('composetest_web')
def test_build_with_image_name(self): def test_build_with_image_name(self):
@ -683,6 +688,7 @@ class ServiceTest(DockerClientTestCase):
build={'context': text_type(base_dir), build={'context': text_type(base_dir),
'args': {"build_version": "1"}}) 'args': {"build_version": "1"}})
service.build() service.build()
self.addCleanup(self.client.remove_image, service.image_name)
assert service.image() assert service.image()
assert "build_version=1" in service.image()['ContainerConfig']['Cmd'] assert "build_version=1" in service.image()['ContainerConfig']['Cmd']
@ -699,6 +705,8 @@ class ServiceTest(DockerClientTestCase):
build={'context': text_type(base_dir), build={'context': text_type(base_dir),
'args': {"build_version": "1"}}) 'args': {"build_version": "1"}})
service.build(build_args_override={'build_version': '2'}) service.build(build_args_override={'build_version': '2'})
self.addCleanup(self.client.remove_image, service.image_name)
assert service.image() assert service.image()
assert "build_version=2" in service.image()['ContainerConfig']['Cmd'] assert "build_version=2" in service.image()['ContainerConfig']['Cmd']
@ -714,9 +722,12 @@ class ServiceTest(DockerClientTestCase):
'labels': {'com.docker.compose.test': 'true'} 'labels': {'com.docker.compose.test': 'true'}
}) })
service.build() service.build()
self.addCleanup(self.client.remove_image, service.image_name)
assert service.image() assert service.image()
assert service.image()['Config']['Labels']['com.docker.compose.test'] == 'true' assert service.image()['Config']['Labels']['com.docker.compose.test'] == 'true'
@no_cluster('Container networks not on Swarm')
def test_build_with_network(self): def test_build_with_network(self):
base_dir = tempfile.mkdtemp() base_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir) self.addCleanup(shutil.rmtree, base_dir)
@ -739,6 +750,8 @@ class ServiceTest(DockerClientTestCase):
}) })
service.build() service.build()
self.addCleanup(self.client.remove_image, service.image_name)
assert service.image() assert service.image()
def test_start_container_stays_unprivileged(self): def test_start_container_stays_unprivileged(self):
@ -1130,6 +1143,8 @@ class ServiceTest(DockerClientTestCase):
build={'context': base_dir, build={'context': base_dir,
'cache_from': ['build1']}) 'cache_from': ['build1']})
service.build() service.build()
self.addCleanup(self.client.remove_image, service.image_name)
assert service.image() assert service.image()
@mock.patch.dict(os.environ) @mock.patch.dict(os.environ)

View File

@ -1,13 +1,14 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import functools
import os import os
import pytest import pytest
from docker.errors import APIError
from docker.utils import version_lt from docker.utils import version_lt
from .. import unittest from .. import unittest
from ..helpers import is_cluster
from compose.cli.docker_client import docker_client from compose.cli.docker_client import docker_client
from compose.config.config import resolve_environment from compose.config.config import resolve_environment
from compose.config.environment import Environment from compose.config.environment import Environment
@ -25,6 +26,7 @@ from compose.service import Service
SWARM_SKIP_CONTAINERS_ALL = os.environ.get('SWARM_SKIP_CONTAINERS_ALL', '0') != '0' SWARM_SKIP_CONTAINERS_ALL = os.environ.get('SWARM_SKIP_CONTAINERS_ALL', '0') != '0'
SWARM_SKIP_CPU_SHARES = os.environ.get('SWARM_SKIP_CPU_SHARES', '0') != '0' SWARM_SKIP_CPU_SHARES = os.environ.get('SWARM_SKIP_CPU_SHARES', '0') != '0'
SWARM_SKIP_RM_VOLUMES = os.environ.get('SWARM_SKIP_RM_VOLUMES', '0') != '0' SWARM_SKIP_RM_VOLUMES = os.environ.get('SWARM_SKIP_RM_VOLUMES', '0') != '0'
SWARM_ASSUME_MULTINODE = os.environ.get('SWARM_ASSUME_MULTINODE', '0') != '0'
def pull_busybox(client): def pull_busybox(client):
@ -141,3 +143,35 @@ class DockerClientTestCase(unittest.TestCase):
volumes = self.client.volumes(filters={'name': volume_name})['Volumes'] volumes = self.client.volumes(filters={'name': volume_name})['Volumes']
assert len(volumes) > 0 assert len(volumes) > 0
return self.client.inspect_volume(volumes[0]['Name']) return self.client.inspect_volume(volumes[0]['Name'])
def is_cluster(client):
if SWARM_ASSUME_MULTINODE:
return True
def get_nodes_number():
try:
return len(client.nodes())
except APIError:
# If the Engine is not part of a Swarm, the SDK will raise
# an APIError
return 0
if not hasattr(is_cluster, 'nodes') or is_cluster.nodes is None:
# Only make the API call if the value hasn't been cached yet
is_cluster.nodes = get_nodes_number()
return is_cluster.nodes > 1
def no_cluster(reason):
def decorator(f):
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
if is_cluster(self.client):
pytest.skip("Test will not be run in cluster mode: %s" % reason)
return
return f(self, *args, **kwargs)
return wrapper
return decorator

View File

@ -3,8 +3,8 @@ from __future__ import unicode_literals
from docker.errors import DockerException from docker.errors import DockerException
from ..helpers import no_cluster
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from .testcases import no_cluster
from compose.const import LABEL_PROJECT from compose.const import LABEL_PROJECT
from compose.const import LABEL_VOLUME from compose.const import LABEL_VOLUME
from compose.volume import Volume from compose.volume import Volume

View File

@ -9,6 +9,8 @@ passenv =
DOCKER_CERT_PATH DOCKER_CERT_PATH
DOCKER_TLS_VERIFY DOCKER_TLS_VERIFY
DOCKER_VERSION DOCKER_VERSION
SWARM_SKIP_*
SWARM_ASSUME_MULTINODE
setenv = setenv =
HOME=/tmp HOME=/tmp
deps = deps =