Rewriting tests to be UCP/Swarm compatible

- Event may contain more information in some cases.
  Don't assume order or format
- Don't assume ports are always exposed on 0.0.0.0 by default
- Absence of HostConfig in a create payload sometimes causes an error at the
  engine level
- In Swarm, volume names are prefixed by "<node_name>/"
- When testing against Swarm, the default network driver is overlay
- Ensure custom test networks are always attachable
- Handle Swarm network names
- Some params moved to host config in recent (1.21+) version
- Conditional test skips for Swarm environments

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2017-05-22 14:58:51 -07:00 committed by Joffrey F
parent 59c4c2388e
commit a0119ae1a5
11 changed files with 316 additions and 160 deletions

View File

@ -56,7 +56,9 @@ HOST_CONFIG_KEYS = [
'cpu_count', 'cpu_count',
'cpu_percent', 'cpu_percent',
'cpu_quota', 'cpu_quota',
'cpu_shares',
'cpus', 'cpus',
'cpuset',
'devices', 'devices',
'dns', 'dns',
'dns_search', 'dns_search',
@ -83,6 +85,7 @@ HOST_CONFIG_KEYS = [
'sysctls', 'sysctls',
'userns_mode', 'userns_mode',
'volumes_from', 'volumes_from',
'volume_driver',
] ]
CONDITION_STARTED = 'service_started' CONDITION_STARTED = 'service_started'
@ -848,6 +851,9 @@ class Service(object):
cpu_count=options.get('cpu_count'), cpu_count=options.get('cpu_count'),
cpu_percent=options.get('cpu_percent'), cpu_percent=options.get('cpu_percent'),
nano_cpus=nano_cpus, nano_cpus=nano_cpus,
volume_driver=options.get('volume_driver'),
cpuset_cpus=options.get('cpuset'),
cpu_shares=options.get('cpu_shares'),
) )
def get_secret_volumes(self): def get_secret_volumes(self):

View File

@ -20,6 +20,8 @@ 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
@ -28,6 +30,7 @@ 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 pull_busybox from tests.integration.testcases import pull_busybox
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
from tests.integration.testcases import v2_only from tests.integration.testcases import v2_only
from tests.integration.testcases import v3_only from tests.integration.testcases import v3_only
@ -68,7 +71,8 @@ def wait_on_condition(condition, delay=0.1, timeout=40):
def kill_service(service): def kill_service(service):
for container in service.containers(): for container in service.containers():
container.kill() if container.is_running:
container.kill()
class ContainerCountCondition(object): class ContainerCountCondition(object):
@ -78,7 +82,7 @@ class ContainerCountCondition(object):
self.expected = expected self.expected = expected
def __call__(self): def __call__(self):
return len(self.project.containers()) == self.expected return len([c for c in self.project.containers() if c.is_running]) == self.expected
def __str__(self): def __str__(self):
return "waiting for counter count == %s" % self.expected return "waiting for counter count == %s" % self.expected
@ -116,11 +120,14 @@ class CLITestCase(DockerClientTestCase):
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)
networks = self.client.networks() networks = self.client.networks()
for n in networks: for n in networks:
if n['Name'].startswith('{}_'.format(self.project.name)): if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name)):
self.client.remove_network(n['Name']) self.client.remove_network(n['Name'])
volumes = self.client.volumes().get('Volumes') or []
for v in volumes:
if v['Name'].split('/')[-1].startswith('{}_'.format(self.project.name)):
self.client.remove_volume(v['Name'])
if hasattr(self, '_project'): if hasattr(self, '_project'):
del self._project del self._project
@ -175,7 +182,10 @@ class CLITestCase(DockerClientTestCase):
def test_host_not_reachable_volumes_from_container(self): def test_host_not_reachable_volumes_from_container(self):
self.base_dir = 'tests/fixtures/volumes-from-container' self.base_dir = 'tests/fixtures/volumes-from-container'
container = self.client.create_container('busybox', 'true', name='composetest_data_container') container = self.client.create_container(
'busybox', 'true', name='composetest_data_container',
host_config={}
)
self.addCleanup(self.client.remove_container, container) self.addCleanup(self.client.remove_container, container)
result = self.dispatch(['-H=tcp://doesnotexist:8000', 'ps'], returncode=1) result = self.dispatch(['-H=tcp://doesnotexist:8000', 'ps'], returncode=1)
@ -545,42 +555,48 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['create']) self.dispatch(['create'])
service = self.project.get_service('simple') service = self.project.get_service('simple')
another = self.project.get_service('another') another = self.project.get_service('another')
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(another.containers()), 0) another_containers = another.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
self.assertEqual(len(another.containers(stopped=True)), 1) assert len(another_containers) == 1
assert not service_containers[0].is_running
assert not another_containers[0].is_running
def test_create_with_force_recreate(self): def test_create_with_force_recreate(self):
self.dispatch(['create'], None) self.dispatch(['create'], None)
service = self.project.get_service('simple') service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
assert not service_containers[0].is_running
old_ids = [c.id for c in service.containers(stopped=True)] old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--force-recreate'], None) self.dispatch(['create', '--force-recreate'], None)
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
assert not service_containers[0].is_running
new_ids = [c.id for c in service.containers(stopped=True)] new_ids = [c.id for c in service_containers]
self.assertNotEqual(old_ids, new_ids) assert old_ids != new_ids
def test_create_with_no_recreate(self): def test_create_with_no_recreate(self):
self.dispatch(['create'], None) self.dispatch(['create'], None)
service = self.project.get_service('simple') service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
assert not service_containers[0].is_running
old_ids = [c.id for c in service.containers(stopped=True)] old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--no-recreate'], None) self.dispatch(['create', '--no-recreate'], None)
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
assert not service_containers[0].is_running
new_ids = [c.id for c in service.containers(stopped=True)] new_ids = [c.id for c in service_containers]
self.assertEqual(old_ids, new_ids) assert old_ids == new_ids
def test_run_one_off_with_volume(self): def test_run_one_off_with_volume(self):
self.base_dir = 'tests/fixtures/simple-composefile-volume-ready' self.base_dir = 'tests/fixtures/simple-composefile-volume-ready'
@ -687,7 +703,7 @@ class CLITestCase(DockerClientTestCase):
network_name = self.project.networks.networks['default'].full_name network_name = self.project.networks.networks['default'].full_name
networks = self.client.networks(names=[network_name]) networks = self.client.networks(names=[network_name])
self.assertEqual(len(networks), 1) self.assertEqual(len(networks), 1)
self.assertEqual(networks[0]['Driver'], 'bridge') assert networks[0]['Driver'] == 'bridge' if not is_cluster(self.client) else 'overlay'
assert 'com.docker.network.bridge.enable_icc' not in networks[0]['Options'] assert 'com.docker.network.bridge.enable_icc' not in networks[0]['Options']
network = self.client.inspect_network(networks[0]['Id']) network = self.client.inspect_network(networks[0]['Id'])
@ -733,11 +749,11 @@ class CLITestCase(DockerClientTestCase):
networks = [ networks = [
n for n in self.client.networks() n for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
# Two networks were created: back and front # Two networks were created: back and front
assert sorted(n['Name'] for n in networks) == [back_name, front_name] assert sorted(n['Name'].split('/')[-1] for n in networks) == [back_name, front_name]
web_container = self.project.get_service('web').containers()[0] web_container = self.project.get_service('web').containers()[0]
back_aliases = web_container.get( back_aliases = web_container.get(
@ -761,11 +777,11 @@ class CLITestCase(DockerClientTestCase):
networks = [ networks = [
n for n in self.client.networks() n for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
# One network was created: internal # One network was created: internal
assert sorted(n['Name'] for n in networks) == [internal_net] assert sorted(n['Name'].split('/')[-1] for n in networks) == [internal_net]
assert networks[0]['Internal'] is True assert networks[0]['Internal'] is True
@ -780,11 +796,11 @@ class CLITestCase(DockerClientTestCase):
networks = [ networks = [
n for n in self.client.networks() n for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
# One networks was created: front # One networks was created: front
assert sorted(n['Name'] for n in networks) == [static_net] assert sorted(n['Name'].split('/')[-1] for n in networks) == [static_net]
web_container = self.project.get_service('web').containers()[0] web_container = self.project.get_service('web').containers()[0]
ipam_config = web_container.get( ipam_config = web_container.get(
@ -803,11 +819,11 @@ class CLITestCase(DockerClientTestCase):
networks = [ networks = [
n for n in self.client.networks() n for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
# Two networks were created: back and front # Two networks were created: back and front
assert sorted(n['Name'] for n in networks) == [back_name, front_name] assert sorted(n['Name'].split('/')[-1] for n in networks) == [back_name, front_name]
back_network = [n for n in networks if n['Name'] == back_name][0] back_network = [n for n in networks if n['Name'] == back_name][0]
front_network = [n for n in networks if n['Name'] == front_name][0] front_network = [n for n in networks if n['Name'] == front_name][0]
@ -847,8 +863,12 @@ class CLITestCase(DockerClientTestCase):
assert 'Service "web" uses an undefined network "foo"' in result.stderr assert 'Service "web" uses an undefined network "foo"' in result.stderr
@v2_only() @v2_only()
@no_cluster('container networks not supported in Swarm')
def test_up_with_network_mode(self): def test_up_with_network_mode(self):
c = self.client.create_container('busybox', 'top', name='composetest_network_mode_container') c = self.client.create_container(
'busybox', 'top', name='composetest_network_mode_container',
host_config={}
)
self.addCleanup(self.client.remove_container, c, force=True) self.addCleanup(self.client.remove_container, c, force=True)
self.client.start(c) self.client.start(c)
container_mode_source = 'container:{}'.format(c['Id']) container_mode_source = 'container:{}'.format(c['Id'])
@ -862,7 +882,7 @@ class CLITestCase(DockerClientTestCase):
networks = [ networks = [
n for n in self.client.networks() n for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
assert not networks assert not networks
@ -899,7 +919,7 @@ class CLITestCase(DockerClientTestCase):
network_names = ['{}_{}'.format(self.project.name, n) for n in ['foo', 'bar']] network_names = ['{}_{}'.format(self.project.name, n) for n in ['foo', 'bar']]
for name in network_names: for name in network_names:
self.client.create_network(name) self.client.create_network(name, attachable=True)
self.dispatch(['-f', filename, 'up', '-d']) self.dispatch(['-f', filename, 'up', '-d'])
container = self.project.containers()[0] container = self.project.containers()[0]
@ -917,12 +937,12 @@ class CLITestCase(DockerClientTestCase):
networks = [ networks = [
n['Name'] for n in self.client.networks() n['Name'] for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
assert not networks assert not networks
network_name = 'composetest_external_network' network_name = 'composetest_external_network'
self.client.create_network(network_name) self.client.create_network(network_name, attachable=True)
self.dispatch(['-f', filename, 'up', '-d']) self.dispatch(['-f', filename, 'up', '-d'])
container = self.project.containers()[0] container = self.project.containers()[0]
@ -941,10 +961,10 @@ class CLITestCase(DockerClientTestCase):
networks = [ networks = [
n for n in self.client.networks() n for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
assert [n['Name'] for n in networks] == [network_with_label] assert [n['Name'].split('/')[-1] for n in networks] == [network_with_label]
assert 'label_key' in networks[0]['Labels'] assert 'label_key' in networks[0]['Labels']
assert networks[0]['Labels']['label_key'] == 'label_val' assert networks[0]['Labels']['label_key'] == 'label_val'
@ -961,10 +981,10 @@ class CLITestCase(DockerClientTestCase):
volumes = [ volumes = [
v for v in self.client.volumes().get('Volumes', []) v for v in self.client.volumes().get('Volumes', [])
if v['Name'].startswith('{}_'.format(self.project.name)) if v['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
assert [v['Name'] for v in volumes] == [volume_with_label] assert set([v['Name'].split('/')[-1] for v in volumes]) == set([volume_with_label])
assert 'label_key' in volumes[0]['Labels'] assert 'label_key' in volumes[0]['Labels']
assert volumes[0]['Labels']['label_key'] == 'label_val' assert volumes[0]['Labels']['label_key'] == 'label_val'
@ -975,7 +995,7 @@ class CLITestCase(DockerClientTestCase):
network_names = [ network_names = [
n['Name'] for n in self.client.networks() n['Name'] for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name)) if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
] ]
assert network_names == [] assert network_names == []
@ -1010,6 +1030,7 @@ class CLITestCase(DockerClientTestCase):
assert "Unsupported config option for services.bar: 'net'" in result.stderr assert "Unsupported config option for services.bar: 'net'" in result.stderr
@no_cluster("Legacy networking not supported on Swarm")
def test_up_with_net_v1(self): def test_up_with_net_v1(self):
self.base_dir = 'tests/fixtures/net-container' self.base_dir = 'tests/fixtures/net-container'
self.dispatch(['up', '-d'], None) self.dispatch(['up', '-d'], None)
@ -1261,6 +1282,7 @@ class CLITestCase(DockerClientTestCase):
[u'/bin/true'], [u'/bin/true'],
) )
@py.test.mark.skipif(SWARM_SKIP_RM_VOLUMES, reason='Swarm DELETE /containers/<id> bug')
def test_run_rm(self): def test_run_rm(self):
self.base_dir = 'tests/fixtures/volume' self.base_dir = 'tests/fixtures/volume'
proc = start_process(self.base_dir, ['run', '--rm', 'test']) proc = start_process(self.base_dir, ['run', '--rm', 'test'])
@ -1274,7 +1296,7 @@ class CLITestCase(DockerClientTestCase):
mounts = containers[0].get('Mounts') mounts = containers[0].get('Mounts')
for mount in mounts: for mount in mounts:
if mount['Destination'] == '/container-path': if mount['Destination'] == '/container-path':
anonymousName = mount['Name'] anonymous_name = mount['Name']
break break
os.kill(proc.pid, signal.SIGINT) os.kill(proc.pid, signal.SIGINT)
wait_on_process(proc, 1) wait_on_process(proc, 1)
@ -1287,9 +1309,11 @@ class CLITestCase(DockerClientTestCase):
if volume.internal == '/container-named-path': if volume.internal == '/container-named-path':
name = volume.external name = volume.external
break break
volumeNames = [v['Name'] for v in volumes] volume_names = [v['Name'].split('/')[-1] for v in volumes]
assert name in volumeNames assert name in volume_names
assert anonymousName not in volumeNames if not is_cluster(self.client):
# The `-v` flag for `docker rm` in Swarm seems to be broken
assert anonymous_name not in volume_names
def test_run_service_with_dockerfile_entrypoint(self): def test_run_service_with_dockerfile_entrypoint(self):
self.base_dir = 'tests/fixtures/entrypoint-dockerfile' self.base_dir = 'tests/fixtures/entrypoint-dockerfile'
@ -1411,11 +1435,10 @@ class CLITestCase(DockerClientTestCase):
container.stop() container.stop()
# check the ports # check the ports
self.assertNotEqual(port_random, None) assert port_random is not None
self.assertIn("0.0.0.0", port_random) assert port_assigned.endswith(':49152')
self.assertEqual(port_assigned, "0.0.0.0:49152") assert port_range[0].endswith(':49153')
self.assertEqual(port_range[0], "0.0.0.0:49153") assert port_range[1].endswith(':49154')
self.assertEqual(port_range[1], "0.0.0.0:49154")
def test_run_service_with_explicitly_mapped_ports(self): def test_run_service_with_explicitly_mapped_ports(self):
# create one off container # create one off container
@ -1431,8 +1454,8 @@ class CLITestCase(DockerClientTestCase):
container.stop() container.stop()
# check the ports # check the ports
self.assertEqual(port_short, "0.0.0.0:30000") assert port_short.endswith(':30000')
self.assertEqual(port_full, "0.0.0.0:30001") assert port_full.endswith(':30001')
def test_run_service_with_explicitly_mapped_ip_ports(self): def test_run_service_with_explicitly_mapped_ip_ports(self):
# create one off container # create one off container
@ -1953,9 +1976,9 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['port', 'simple', str(number)]) result = self.dispatch(['port', 'simple', str(number)])
return result.stdout.rstrip() return result.stdout.rstrip()
self.assertEqual(get_port(3000), container.get_local_port(3000)) assert get_port(3000) == container.get_local_port(3000)
self.assertEqual(get_port(3001), "0.0.0.0:49152") assert ':49152' in get_port(3001)
self.assertEqual(get_port(3002), "0.0.0.0:49153") assert ':49153' in get_port(3002)
def test_expanded_port(self): def test_expanded_port(self):
self.base_dir = 'tests/fixtures/ports-composefile' self.base_dir = 'tests/fixtures/ports-composefile'
@ -1966,9 +1989,9 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['port', 'simple', str(number)]) result = self.dispatch(['port', 'simple', str(number)])
return result.stdout.rstrip() return result.stdout.rstrip()
self.assertEqual(get_port(3000), container.get_local_port(3000)) assert get_port(3000) == container.get_local_port(3000)
self.assertEqual(get_port(3001), "0.0.0.0:49152") assert ':49152' in get_port(3001)
self.assertEqual(get_port(3002), "0.0.0.0:49153") assert ':49153' 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'
@ -2021,12 +2044,14 @@ class CLITestCase(DockerClientTestCase):
assert len(lines) == 2 assert len(lines) == 2
container, = self.project.containers() container, = self.project.containers()
expected_template = ( expected_template = ' container {} {}'
' container {} {} (image=busybox:latest, ' expected_meta_info = ['image=busybox:latest', 'name=simplecomposefile_simple_1']
'name=simplecomposefile_simple_1)')
assert expected_template.format('create', container.id) in lines[0] assert expected_template.format('create', container.id) in lines[0]
assert expected_template.format('start', container.id) in lines[1] assert expected_template.format('start', container.id) in lines[1]
for line in lines:
for info in expected_meta_info:
assert info in line
assert has_timestamp(lines[0]) assert has_timestamp(lines[0])
@ -2069,7 +2094,6 @@ class CLITestCase(DockerClientTestCase):
'docker-compose.yml', 'docker-compose.yml',
'docker-compose.override.yml', 'docker-compose.override.yml',
'extra.yml', 'extra.yml',
] ]
self._project = get_project(self.base_dir, config_paths) self._project = get_project(self.base_dir, config_paths)
self.dispatch( self.dispatch(
@ -2086,7 +2110,6 @@ class CLITestCase(DockerClientTestCase):
web, other, db = containers web, other, db = containers
self.assertEqual(web.human_readable_command, 'top') self.assertEqual(web.human_readable_command, 'top')
self.assertTrue({'db', 'other'} <= set(get_links(web)))
self.assertEqual(db.human_readable_command, 'top') self.assertEqual(db.human_readable_command, 'top')
self.assertEqual(other.human_readable_command, 'top') self.assertEqual(other.human_readable_command, 'top')

View File

@ -1,6 +1,7 @@
version: '2.2'
web: services:
web:
command: "top" command: "top"
db: db:
command: "top" command: "top"

View File

@ -1,10 +1,10 @@
version: '2.2'
web: services:
web:
image: busybox:latest image: busybox:latest
command: "sleep 200" command: "sleep 200"
links: depends_on:
- db - db
db:
db:
image: busybox:latest image: busybox:latest
command: "sleep 200" command: "sleep 200"

View File

@ -1,9 +1,10 @@
version: '2.2'
web: services:
links: web:
depends_on:
- db - db
- other - other
other: other:
image: busybox:latest image: busybox:latest
command: "top" command: "top"

View File

@ -1,8 +1,12 @@
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
@ -44,3 +48,34 @@ 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

@ -6,12 +6,16 @@ import random
import py import py
import pytest import pytest
from docker.errors import APIError
from docker.errors import NotFound 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 compose.config import config from compose.config import config
from compose.config import ConfigurationError from compose.config import ConfigurationError
from compose.config import types from compose.config import types
@ -57,6 +61,20 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers() containers = project.containers()
self.assertEqual(len(containers), 2) self.assertEqual(len(containers), 2)
@pytest.mark.skipif(SWARM_SKIP_CONTAINERS_ALL, reason='Swarm /containers/json bug')
def test_containers_stopped(self):
web = self.create_service('web')
db = self.create_service('db')
project = Project('composetest', [web, db], self.client)
project.up()
assert len(project.containers()) == 2
assert len(project.containers(stopped=True)) == 2
project.stop()
assert len(project.containers()) == 0
assert len(project.containers(stopped=True)) == 2
def test_containers_with_service_names(self): def test_containers_with_service_names(self):
web = self.create_service('web') web = self.create_service('web')
db = self.create_service('db') db = self.create_service('db')
@ -110,6 +128,7 @@ class ProjectTest(DockerClientTestCase):
volumes=['/var/data'], volumes=['/var/data'],
name='composetest_data_container', name='composetest_data_container',
labels={LABEL_PROJECT: 'composetest'}, labels={LABEL_PROJECT: 'composetest'},
host_config={},
) )
project = Project.from_config( project = Project.from_config(
name='composetest', name='composetest',
@ -125,6 +144,7 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw']) self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw'])
@v2_only() @v2_only()
@no_cluster('container networks not supported in Swarm')
def test_network_mode_from_service(self): def test_network_mode_from_service(self):
project = Project.from_config( project = Project.from_config(
name='composetest', name='composetest',
@ -152,6 +172,7 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id) self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
@v2_only() @v2_only()
@no_cluster('container networks not supported in Swarm')
def test_network_mode_from_container(self): def test_network_mode_from_container(self):
def get_project(): def get_project():
return Project.from_config( return Project.from_config(
@ -179,6 +200,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest_net_container', name='composetest_net_container',
command='top', command='top',
labels={LABEL_PROJECT: 'composetest'}, labels={LABEL_PROJECT: 'composetest'},
host_config={},
) )
net_container.start() net_container.start()
@ -188,6 +210,7 @@ class ProjectTest(DockerClientTestCase):
web = project.get_service('web') web = project.get_service('web')
self.assertEqual(web.network_mode.mode, 'container:' + net_container.id) self.assertEqual(web.network_mode.mode, 'container:' + net_container.id)
@no_cluster('container networks not supported in Swarm')
def test_net_from_service_v1(self): def test_net_from_service_v1(self):
project = Project.from_config( project = Project.from_config(
name='composetest', name='composetest',
@ -211,6 +234,7 @@ class ProjectTest(DockerClientTestCase):
net = project.get_service('net') net = project.get_service('net')
self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id) self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
@no_cluster('container networks not supported in Swarm')
def test_net_from_container_v1(self): def test_net_from_container_v1(self):
def get_project(): def get_project():
return Project.from_config( return Project.from_config(
@ -235,6 +259,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest_net_container', name='composetest_net_container',
command='top', command='top',
labels={LABEL_PROJECT: 'composetest'}, labels={LABEL_PROJECT: 'composetest'},
host_config={},
) )
net_container.start() net_container.start()
@ -260,12 +285,12 @@ class ProjectTest(DockerClientTestCase):
project.start(service_names=['web']) project.start(service_names=['web'])
self.assertEqual( self.assertEqual(
set(c.name for c in project.containers()), set(c.name for c in project.containers() if c.is_running),
set([web_container_1.name, web_container_2.name])) set([web_container_1.name, web_container_2.name]))
project.start() project.start()
self.assertEqual( self.assertEqual(
set(c.name for c in project.containers()), set(c.name for c in project.containers() if c.is_running),
set([web_container_1.name, web_container_2.name, db_container.name])) set([web_container_1.name, web_container_2.name, db_container.name]))
project.pause(service_names=['web']) project.pause(service_names=['web'])
@ -285,10 +310,12 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 0) self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 0)
project.stop(service_names=['web'], timeout=1) project.stop(service_names=['web'], timeout=1)
self.assertEqual(set(c.name for c in project.containers()), set([db_container.name])) self.assertEqual(
set(c.name for c in project.containers() if c.is_running), set([db_container.name])
)
project.kill(service_names=['db']) project.kill(service_names=['db'])
self.assertEqual(len(project.containers()), 0) self.assertEqual(len([c for c in project.containers() if c.is_running]), 0)
self.assertEqual(len(project.containers(stopped=True)), 3) self.assertEqual(len(project.containers(stopped=True)), 3)
project.remove_stopped(service_names=['web']) project.remove_stopped(service_names=['web'])
@ -303,11 +330,13 @@ class ProjectTest(DockerClientTestCase):
project = Project('composetest', [web, db], self.client) project = Project('composetest', [web, db], self.client)
project.create(['db']) project.create(['db'])
self.assertEqual(len(project.containers()), 0) containers = project.containers(stopped=True)
self.assertEqual(len(project.containers(stopped=True)), 1) assert len(containers) == 1
self.assertEqual(len(db.containers()), 0) assert not containers[0].is_running
self.assertEqual(len(db.containers(stopped=True)), 1) db_containers = db.containers(stopped=True)
self.assertEqual(len(web.containers(stopped=True)), 0) assert len(db_containers) == 1
assert not db_containers[0].is_running
assert len(web.containers(stopped=True)) == 0
def test_create_twice(self): def test_create_twice(self):
web = self.create_service('web') web = self.create_service('web')
@ -316,12 +345,14 @@ class ProjectTest(DockerClientTestCase):
project.create(['db', 'web']) project.create(['db', 'web'])
project.create(['db', 'web']) project.create(['db', 'web'])
self.assertEqual(len(project.containers()), 0) containers = project.containers(stopped=True)
self.assertEqual(len(project.containers(stopped=True)), 2) assert len(containers) == 2
self.assertEqual(len(db.containers()), 0) db_containers = db.containers(stopped=True)
self.assertEqual(len(db.containers(stopped=True)), 1) assert len(db_containers) == 1
self.assertEqual(len(web.containers()), 0) assert not db_containers[0].is_running
self.assertEqual(len(web.containers(stopped=True)), 1) web_containers = web.containers(stopped=True)
assert len(web_containers) == 1
assert not web_containers[0].is_running
def test_create_with_links(self): def test_create_with_links(self):
db = self.create_service('db') db = self.create_service('db')
@ -329,12 +360,11 @@ class ProjectTest(DockerClientTestCase):
project = Project('composetest', [db, web], self.client) project = Project('composetest', [db, web], self.client)
project.create(['web']) project.create(['web'])
self.assertEqual(len(project.containers()), 0) # self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 2) assert len(project.containers(stopped=True)) == 2
self.assertEqual(len(db.containers()), 0) assert not [c for c in project.containers(stopped=True) if c.is_running]
self.assertEqual(len(db.containers(stopped=True)), 1) assert len(db.containers(stopped=True)) == 1
self.assertEqual(len(web.containers()), 0) assert len(web.containers(stopped=True)) == 1
self.assertEqual(len(web.containers(stopped=True)), 1)
def test_create_strategy_always(self): def test_create_strategy_always(self):
db = self.create_service('db') db = self.create_service('db')
@ -343,11 +373,11 @@ class ProjectTest(DockerClientTestCase):
old_id = project.containers(stopped=True)[0].id old_id = project.containers(stopped=True)[0].id
project.create(['db'], strategy=ConvergenceStrategy.always) project.create(['db'], strategy=ConvergenceStrategy.always)
self.assertEqual(len(project.containers()), 0) assert len(project.containers(stopped=True)) == 1
self.assertEqual(len(project.containers(stopped=True)), 1)
db_container = project.containers(stopped=True)[0] db_container = project.containers(stopped=True)[0]
self.assertNotEqual(db_container.id, old_id) assert not db_container.is_running
assert db_container.id != old_id
def test_create_strategy_never(self): def test_create_strategy_never(self):
db = self.create_service('db') db = self.create_service('db')
@ -356,11 +386,11 @@ class ProjectTest(DockerClientTestCase):
old_id = project.containers(stopped=True)[0].id old_id = project.containers(stopped=True)[0].id
project.create(['db'], strategy=ConvergenceStrategy.never) project.create(['db'], strategy=ConvergenceStrategy.never)
self.assertEqual(len(project.containers()), 0) assert len(project.containers(stopped=True)) == 1
self.assertEqual(len(project.containers(stopped=True)), 1)
db_container = project.containers(stopped=True)[0] db_container = project.containers(stopped=True)[0]
self.assertEqual(db_container.id, old_id) assert not db_container.is_running
assert db_container.id == old_id
def test_project_up(self): def test_project_up(self):
web = self.create_service('web') web = self.create_service('web')
@ -550,8 +580,8 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(project.containers(stopped=True)), 2) self.assertEqual(len(project.containers(stopped=True)), 2)
self.assertEqual(len(project.get_service('web').containers()), 0) self.assertEqual(len(project.get_service('web').containers()), 0)
self.assertEqual(len(project.get_service('db').containers()), 1) self.assertEqual(len(project.get_service('db').containers()), 1)
self.assertEqual(len(project.get_service('data').containers()), 0)
self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1) self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
assert not project.get_service('data').containers(stopped=True)[0].is_running
self.assertEqual(len(project.get_service('console').containers()), 0) self.assertEqual(len(project.get_service('console').containers()), 0)
def test_project_up_recreate_with_tmpfs_volume(self): def test_project_up_recreate_with_tmpfs_volume(self):
@ -737,10 +767,10 @@ class ProjectTest(DockerClientTestCase):
"com.docker.compose.network.test": "9-29-045" "com.docker.compose.network.test": "9-29-045"
} }
@v2_only() @v2_1_only()
def test_up_with_network_static_addresses(self): def test_up_with_network_static_addresses(self):
config_data = build_config( config_data = build_config(
version=V2_0, version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -766,7 +796,8 @@ class ProjectTest(DockerClientTestCase):
{"subnet": "fe80::/64", {"subnet": "fe80::/64",
"gateway": "fe80::1001:1"} "gateway": "fe80::1001:1"}
] ]
} },
'enable_ipv6': True,
} }
} }
) )
@ -777,13 +808,8 @@ class ProjectTest(DockerClientTestCase):
) )
project.up(detached=True) project.up(detached=True)
network = self.client.networks(names=['static_test'])[0]
service_container = project.get_service('web').containers()[0] service_container = project.get_service('web').containers()[0]
assert network['Options'] == {
"com.docker.network.enable_ipv6": "true"
}
IPAMConfig = (service_container.inspect().get('NetworkSettings', {}). IPAMConfig = (service_container.inspect().get('NetworkSettings', {}).
get('Networks', {}).get('composetest_static_test', {}). get('Networks', {}).get('composetest_static_test', {}).
get('IPAMConfig', {})) get('IPAMConfig', {}))
@ -825,7 +851,7 @@ class ProjectTest(DockerClientTestCase):
config_data=config_data, config_data=config_data,
) )
project.up(detached=True) project.up(detached=True)
network = self.client.networks(names=['static_test'])[0] network = [n for n in self.client.networks() if 'static_test' in n['Name']][0]
service_container = project.get_service('web').containers()[0] service_container = project.get_service('web').containers()[0]
assert network['EnableIPv6'] is True assert network['EnableIPv6'] is True
@ -1026,8 +1052,8 @@ class ProjectTest(DockerClientTestCase):
project.up() project.up()
self.assertEqual(len(project.containers()), 1) self.assertEqual(len(project.containers()), 1)
volume_data = self.client.inspect_volume(full_vol_name) volume_data = self.get_volume_data(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name) assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local') self.assertEqual(volume_data['Driver'], 'local')
@v2_1_only() @v2_1_only()
@ -1062,10 +1088,12 @@ class ProjectTest(DockerClientTestCase):
volumes = [ volumes = [
v for v in self.client.volumes().get('Volumes', []) v for v in self.client.volumes().get('Volumes', [])
if v['Name'].startswith('composetest_') if v['Name'].split('/')[-1].startswith('composetest_')
] ]
assert [v['Name'] for v in volumes] == ['composetest_{}'.format(volume_name)] assert set([v['Name'].split('/')[-1] for v in volumes]) == set(
['composetest_{}'.format(volume_name)]
)
assert 'label_key' in volumes[0]['Labels'] assert 'label_key' in volumes[0]['Labels']
assert volumes[0]['Labels']['label_key'] == 'label_val' assert volumes[0]['Labels']['label_key'] == 'label_val'
@ -1205,8 +1233,8 @@ class ProjectTest(DockerClientTestCase):
) )
project.volumes.initialize() project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name) volume_data = self.get_volume_data(full_vol_name)
assert volume_data['Name'] == full_vol_name assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local' assert volume_data['Driver'] == 'local'
@v2_only() @v2_only()
@ -1229,8 +1257,8 @@ class ProjectTest(DockerClientTestCase):
) )
project.up() project.up()
volume_data = self.client.inspect_volume(full_vol_name) volume_data = self.get_volume_data(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name) assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local') self.assertEqual(volume_data['Driver'], 'local')
@v3_only() @v3_only()
@ -1287,10 +1315,11 @@ class ProjectTest(DockerClientTestCase):
name='composetest', name='composetest',
config_data=config_data, client=self.client config_data=config_data, client=self.client
) )
with self.assertRaises(config.ConfigurationError): with self.assertRaises(APIError if is_cluster(self.client) else config.ConfigurationError):
project.volumes.initialize() project.volumes.initialize()
@v2_only() @v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_updated_driver(self): def test_initialize_volumes_updated_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
@ -1310,8 +1339,8 @@ class ProjectTest(DockerClientTestCase):
) )
project.volumes.initialize() project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name) volume_data = self.get_volume_data(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name) assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local') self.assertEqual(volume_data['Driver'], 'local')
config_data = config_data._replace( config_data = config_data._replace(
@ -1348,8 +1377,8 @@ class ProjectTest(DockerClientTestCase):
) )
project.volumes.initialize() project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name) volume_data = self.get_volume_data(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name) assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local') self.assertEqual(volume_data['Driver'], 'local')
config_data = config_data._replace( config_data = config_data._replace(
@ -1361,11 +1390,12 @@ class ProjectTest(DockerClientTestCase):
client=self.client client=self.client
) )
project.volumes.initialize() project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name) volume_data = self.get_volume_data(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name) assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local') self.assertEqual(volume_data['Driver'], 'local')
@v2_only() @v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_external_volumes(self): def test_initialize_volumes_external_volumes(self):
# Use composetest_ prefix so it gets garbage-collected in tearDown() # Use composetest_ prefix so it gets garbage-collected in tearDown()
vol_name = 'composetest_{0:x}'.format(random.getrandbits(32)) vol_name = 'composetest_{0:x}'.format(random.getrandbits(32))

View File

@ -13,9 +13,13 @@ 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
from .testcases import SWARM_SKIP_CONTAINERS_ALL
from .testcases import SWARM_SKIP_CPU_SHARES
from compose import __version__ from compose import __version__
from compose.config.types import VolumeFromSpec from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec from compose.config.types import VolumeSpec
@ -100,6 +104,7 @@ class ServiceTest(DockerClientTestCase):
service.start_container(container) service.start_container(container)
self.assertEqual('foodriver', container.get('HostConfig.VolumeDriver')) self.assertEqual('foodriver', container.get('HostConfig.VolumeDriver'))
@pytest.mark.skipif(SWARM_SKIP_CPU_SHARES, reason='Swarm --cpu-shares bug')
def test_create_container_with_cpu_shares(self): def test_create_container_with_cpu_shares(self):
service = self.create_service('db', cpu_shares=73) service = self.create_service('db', cpu_shares=73)
container = service.create_container() container = service.create_container()
@ -151,6 +156,7 @@ class ServiceTest(DockerClientTestCase):
service.start_container(container) service.start_container(container)
assert container.get('HostConfig.Init') is True assert container.get('HostConfig.Init') is True
@pytest.mark.xfail(True, reason='Option has been removed in Engine 17.06.0')
def test_create_container_with_init_path(self): def test_create_container_with_init_path(self):
self.require_api_version('1.25') self.require_api_version('1.25')
docker_init_path = find_executable('docker-init') docker_init_path = find_executable('docker-init')
@ -249,6 +255,7 @@ class ServiceTest(DockerClientTestCase):
'busybox', 'true', 'busybox', 'true',
volumes={container_path: {}}, volumes={container_path: {}},
labels={'com.docker.compose.test_image': 'true'}, labels={'com.docker.compose.test_image': 'true'},
host_config={}
) )
image = self.client.commit(tmp_container)['Id'] image = self.client.commit(tmp_container)['Id']
@ -278,6 +285,7 @@ class ServiceTest(DockerClientTestCase):
image='busybox:latest', image='busybox:latest',
command=["top"], command=["top"],
labels={LABEL_PROJECT: 'composetest'}, labels={LABEL_PROJECT: 'composetest'},
host_config={},
) )
host_service = self.create_service( host_service = self.create_service(
'host', 'host',
@ -321,9 +329,15 @@ class ServiceTest(DockerClientTestCase):
self.assertIn('FOO=2', new_container.get('Config.Env')) self.assertIn('FOO=2', new_container.get('Config.Env'))
self.assertEqual(new_container.name, 'composetest_db_1') self.assertEqual(new_container.name, 'composetest_db_1')
self.assertEqual(new_container.get_mount('/etc')['Source'], volume_path) self.assertEqual(new_container.get_mount('/etc')['Source'], volume_path)
self.assertIn( if not is_cluster(self.client):
'affinity:container==%s' % old_container.id, assert (
new_container.get('Config.Env')) 'affinity:container==%s' % old_container.id in
new_container.get('Config.Env')
)
else:
# In Swarm, the env marker is consumed and the container should be deployed
# on the same node.
assert old_container.get('Node.Name') == new_container.get('Node.Name')
self.assertEqual(len(self.client.containers(all=True)), num_containers_before) self.assertEqual(len(self.client.containers(all=True)), num_containers_before)
self.assertNotEqual(old_container.id, new_container.id) self.assertNotEqual(old_container.id, new_container.id)
@ -350,8 +364,13 @@ class ServiceTest(DockerClientTestCase):
ConvergencePlan('recreate', [orig_container])) ConvergencePlan('recreate', [orig_container]))
assert new_container.get_mount('/etc')['Source'] == volume_path assert new_container.get_mount('/etc')['Source'] == volume_path
assert ('affinity:container==%s' % orig_container.id in if not is_cluster(self.client):
new_container.get('Config.Env')) assert ('affinity:container==%s' % orig_container.id in
new_container.get('Config.Env'))
else:
# In Swarm, the env marker is consumed and the container should be deployed
# on the same node.
assert orig_container.get('Node.Name') == new_container.get('Node.Name')
orig_container = new_container orig_container = new_container
@ -464,18 +483,21 @@ class ServiceTest(DockerClientTestCase):
) )
containers = service.execute_convergence_plan(ConvergencePlan('create', []), start=False) containers = service.execute_convergence_plan(ConvergencePlan('create', []), start=False)
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
assert not service_containers[0].is_running
containers = service.execute_convergence_plan( containers = service.execute_convergence_plan(
ConvergencePlan('recreate', containers), ConvergencePlan('recreate', containers),
start=False) start=False)
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
assert not service_containers[0].is_running
service.execute_convergence_plan(ConvergencePlan('start', containers), start=False) service.execute_convergence_plan(ConvergencePlan('start', containers), start=False)
self.assertEqual(len(service.containers()), 0) service_containers = service.containers(stopped=True)
self.assertEqual(len(service.containers(stopped=True)), 1) assert len(service_containers) == 1
assert not service_containers[0].is_running
def test_start_container_passes_through_options(self): def test_start_container_passes_through_options(self):
db = self.create_service('db') db = self.create_service('db')
@ -487,6 +509,7 @@ class ServiceTest(DockerClientTestCase):
create_and_start_container(db) create_and_start_container(db)
self.assertEqual(db.containers()[0].environment['FOO'], 'BAR') self.assertEqual(db.containers()[0].environment['FOO'], 'BAR')
@no_cluster('No legacy links support in Swarm')
def test_start_container_creates_links(self): def test_start_container_creates_links(self):
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', links=[(db, None)]) web = self.create_service('web', links=[(db, None)])
@ -503,6 +526,7 @@ class ServiceTest(DockerClientTestCase):
'db']) 'db'])
) )
@no_cluster('No legacy links support in Swarm')
def test_start_container_creates_links_with_names(self): def test_start_container_creates_links_with_names(self):
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', links=[(db, 'custom_link_name')]) web = self.create_service('web', links=[(db, 'custom_link_name')])
@ -519,6 +543,7 @@ class ServiceTest(DockerClientTestCase):
'custom_link_name']) 'custom_link_name'])
) )
@no_cluster('No legacy links support in Swarm')
def test_start_container_with_external_links(self): def test_start_container_with_external_links(self):
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', external_links=['composetest_db_1', web = self.create_service('web', external_links=['composetest_db_1',
@ -537,6 +562,7 @@ class ServiceTest(DockerClientTestCase):
'db_3']), 'db_3']),
) )
@no_cluster('No legacy links support in Swarm')
def test_start_normal_container_does_not_create_links_to_its_own_service(self): def test_start_normal_container_does_not_create_links_to_its_own_service(self):
db = self.create_service('db') db = self.create_service('db')
@ -546,6 +572,7 @@ class ServiceTest(DockerClientTestCase):
c = create_and_start_container(db) c = create_and_start_container(db)
self.assertEqual(set(get_links(c)), set([])) self.assertEqual(set(get_links(c)), set([]))
@no_cluster('No legacy links support in Swarm')
def test_start_one_off_container_creates_links_to_its_own_service(self): def test_start_one_off_container_creates_links_to_its_own_service(self):
db = self.create_service('db') db = self.create_service('db')
@ -572,7 +599,7 @@ class ServiceTest(DockerClientTestCase):
container = create_and_start_container(service) container = create_and_start_container(service)
container.wait() container.wait()
self.assertIn(b'success', container.logs()) self.assertIn(b'success', container.logs())
self.assertEqual(len(self.client.images(name='composetest_test')), 1) assert len(self.client.images(name='composetest_test')) >= 1
def test_start_container_uses_tagged_image_if_it_exists(self): def test_start_container_uses_tagged_image_if_it_exists(self):
self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test') self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test')
@ -719,20 +746,27 @@ class ServiceTest(DockerClientTestCase):
'0.0.0.0:9001:9000/udp', '0.0.0.0:9001:9000/udp',
]) ])
container = create_and_start_container(service).inspect() container = create_and_start_container(service).inspect()
self.assertEqual(container['NetworkSettings']['Ports'], { assert container['NetworkSettings']['Ports']['8000/tcp'] == [{
'8000/tcp': [ 'HostIp': '127.0.0.1',
{ 'HostPort': '8001',
'HostIp': '127.0.0.1', }]
'HostPort': '8001', assert container['NetworkSettings']['Ports']['9000/udp'][0]['HostPort'] == '9001'
}, if not is_cluster(self.client):
], assert container['NetworkSettings']['Ports']['9000/udp'][0]['HostIp'] == '0.0.0.0'
'9000/udp': [ # self.assertEqual(container['NetworkSettings']['Ports'], {
{ # '8000/tcp': [
'HostIp': '0.0.0.0', # {
'HostPort': '9001', # 'HostIp': '127.0.0.1',
}, # 'HostPort': '8001',
], # },
}) # ],
# '9000/udp': [
# {
# 'HostIp': '0.0.0.0',
# 'HostPort': '9001',
# },
# ],
# })
def test_create_with_image_id(self): def test_create_with_image_id(self):
# Get image id for the current busybox:latest # Get image id for the current busybox:latest
@ -760,6 +794,10 @@ class ServiceTest(DockerClientTestCase):
service.scale(0) service.scale(0)
self.assertEqual(len(service.containers()), 0) self.assertEqual(len(service.containers()), 0)
@pytest.mark.skipif(
SWARM_SKIP_CONTAINERS_ALL,
reason='Swarm /containers/json bug'
)
def test_scale_with_stopped_containers(self): def test_scale_with_stopped_containers(self):
""" """
Given there are some stopped containers and scale is called with a Given there are some stopped containers and scale is called with a

View File

@ -251,7 +251,7 @@ class ServiceStateTest(DockerClientTestCase):
container = web.create_container() container = web.create_container()
# update the image # update the image
c = self.client.create_container(image, ['touch', '/hello.txt']) c = self.client.create_container(image, ['touch', '/hello.txt'], host_config={})
self.client.commit(c, repository=repo, tag=tag) self.client.commit(c, repository=repo, tag=tag)
self.client.remove_container(c) self.client.remove_container(c)

View File

@ -8,6 +8,7 @@ from docker.utils import version_lt
from pytest import skip from pytest import skip
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
@ -21,6 +22,10 @@ from compose.const import LABEL_PROJECT
from compose.progress_stream import stream_output from compose.progress_stream import stream_output
from compose.service import Service from compose.service import Service
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_RM_VOLUMES = os.environ.get('SWARM_SKIP_RM_VOLUMES', '0') != '0'
def pull_busybox(client): def pull_busybox(client):
client.pull('busybox:latest', stream=False) client.pull('busybox:latest', stream=False)
@ -97,7 +102,7 @@ class DockerClientTestCase(unittest.TestCase):
for i in self.client.images( for i in self.client.images(
filters={'label': 'com.docker.compose.test_image'}): filters={'label': 'com.docker.compose.test_image'}):
self.client.remove_image(i) self.client.remove_image(i, force=True)
volumes = self.client.volumes().get('Volumes') or [] volumes = self.client.volumes().get('Volumes') or []
for v in volumes: for v in volumes:
@ -133,3 +138,11 @@ class DockerClientTestCase(unittest.TestCase):
api_version = self.client.version()['ApiVersion'] api_version = self.client.version()['ApiVersion']
if version_lt(api_version, minimum): if version_lt(api_version, minimum):
skip("API version is too low ({} < {})".format(api_version, minimum)) skip("API version is too low ({} < {})".format(api_version, minimum))
def get_volume_data(self, volume_name):
if not is_cluster(self.client):
return self.client.inspect_volume(volume_name)
volumes = self.client.volumes(filters={'name': volume_name})['Volumes']
assert len(volumes) > 0
return self.client.inspect_volume(volumes[0]['Name'])

View File

@ -3,6 +3,7 @@ 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 compose.const import LABEL_PROJECT from compose.const import LABEL_PROJECT
from compose.const import LABEL_VOLUME from compose.const import LABEL_VOLUME
@ -35,26 +36,28 @@ class VolumeTest(DockerClientTestCase):
def test_create_volume(self): def test_create_volume(self):
vol = self.create_volume('volume01') vol = self.create_volume('volume01')
vol.create() vol.create()
info = self.client.inspect_volume(vol.full_name) info = self.get_volume_data(vol.full_name)
assert info['Name'] == vol.full_name assert info['Name'].split('/')[-1] == vol.full_name
def test_recreate_existing_volume(self): def test_recreate_existing_volume(self):
vol = self.create_volume('volume01') vol = self.create_volume('volume01')
vol.create() vol.create()
info = self.client.inspect_volume(vol.full_name) info = self.get_volume_data(vol.full_name)
assert info['Name'] == vol.full_name assert info['Name'].split('/')[-1] == vol.full_name
vol.create() vol.create()
info = self.client.inspect_volume(vol.full_name) info = self.get_volume_data(vol.full_name)
assert info['Name'] == vol.full_name assert info['Name'].split('/')[-1] == vol.full_name
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_inspect_volume(self): def test_inspect_volume(self):
vol = self.create_volume('volume01') vol = self.create_volume('volume01')
vol.create() vol.create()
info = vol.inspect() info = vol.inspect()
assert info['Name'] == vol.full_name assert info['Name'] == vol.full_name
@no_cluster('remove volume by name defect on Swarm Classic')
def test_remove_volume(self): def test_remove_volume(self):
vol = Volume(self.client, 'composetest', 'volume01') vol = Volume(self.client, 'composetest', 'volume01')
vol.create() vol.create()
@ -62,6 +65,7 @@ class VolumeTest(DockerClientTestCase):
volumes = self.client.volumes()['Volumes'] volumes = self.client.volumes()['Volumes']
assert len([v for v in volumes if v['Name'] == vol.full_name]) == 0 assert len([v for v in volumes if v['Name'] == vol.full_name]) == 0
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_external_volume(self): def test_external_volume(self):
vol = self.create_volume('composetest_volume_ext', external=True) vol = self.create_volume('composetest_volume_ext', external=True)
assert vol.external is True assert vol.external is True
@ -70,6 +74,7 @@ class VolumeTest(DockerClientTestCase):
info = vol.inspect() info = vol.inspect()
assert info['Name'] == vol.name assert info['Name'] == vol.name
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_external_aliased_volume(self): def test_external_aliased_volume(self):
alias_name = 'composetest_alias01' alias_name = 'composetest_alias01'
vol = self.create_volume('volume01', external=alias_name) vol = self.create_volume('volume01', external=alias_name)
@ -79,24 +84,28 @@ class VolumeTest(DockerClientTestCase):
info = vol.inspect() info = vol.inspect()
assert info['Name'] == alias_name assert info['Name'] == alias_name
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_exists(self): def test_exists(self):
vol = self.create_volume('volume01') vol = self.create_volume('volume01')
assert vol.exists() is False assert vol.exists() is False
vol.create() vol.create()
assert vol.exists() is True assert vol.exists() is True
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_exists_external(self): def test_exists_external(self):
vol = self.create_volume('volume01', external=True) vol = self.create_volume('volume01', external=True)
assert vol.exists() is False assert vol.exists() is False
vol.create() vol.create()
assert vol.exists() is True assert vol.exists() is True
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_exists_external_aliased(self): def test_exists_external_aliased(self):
vol = self.create_volume('volume01', external='composetest_alias01') vol = self.create_volume('volume01', external='composetest_alias01')
assert vol.exists() is False assert vol.exists() is False
vol.create() vol.create()
assert vol.exists() is True assert vol.exists() is True
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_volume_default_labels(self): def test_volume_default_labels(self):
vol = self.create_volume('volume01') vol = self.create_volume('volume01')
vol.create() vol.create()