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 e9b6cc23fc
commit 7a4c328c41
11 changed files with 316 additions and 160 deletions

View File

@ -56,7 +56,9 @@ HOST_CONFIG_KEYS = [
'cpu_count',
'cpu_percent',
'cpu_quota',
'cpu_shares',
'cpus',
'cpuset',
'devices',
'dns',
'dns_search',
@ -83,6 +85,7 @@ HOST_CONFIG_KEYS = [
'sysctls',
'userns_mode',
'volumes_from',
'volume_driver',
]
CONDITION_STARTED = 'service_started'
@ -848,6 +851,9 @@ class Service(object):
cpu_count=options.get('cpu_count'),
cpu_percent=options.get('cpu_percent'),
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):

View File

@ -20,6 +20,8 @@ from docker import errors
from .. import mock
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.config.errors import DuplicateOverrideFileFound
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 get_links
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_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):
for container in service.containers():
container.kill()
if container.is_running:
container.kill()
class ContainerCountCondition(object):
@ -78,7 +82,7 @@ class ContainerCountCondition(object):
self.expected = expected
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):
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):
container.remove(force=True)
networks = self.client.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'])
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'):
del self._project
@ -175,7 +182,10 @@ class CLITestCase(DockerClientTestCase):
def test_host_not_reachable_volumes_from_container(self):
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)
result = self.dispatch(['-H=tcp://doesnotexist:8000', 'ps'], returncode=1)
@ -545,42 +555,48 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['create'])
service = self.project.get_service('simple')
another = self.project.get_service('another')
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(another.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
self.assertEqual(len(another.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
another_containers = another.containers(stopped=True)
assert len(service_containers) == 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):
self.dispatch(['create'], None)
service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
assert len(service_containers) == 1
assert not service_containers[0].is_running
old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--force-recreate'], None)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
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):
self.dispatch(['create'], None)
service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
assert len(service_containers) == 1
assert not service_containers[0].is_running
old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--no-recreate'], None)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
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):
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
networks = self.client.networks(names=[network_name])
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']
network = self.client.inspect_network(networks[0]['Id'])
@ -733,11 +749,11 @@ class CLITestCase(DockerClientTestCase):
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
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]
back_aliases = web_container.get(
@ -761,11 +777,11 @@ class CLITestCase(DockerClientTestCase):
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
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
@ -780,11 +796,11 @@ class CLITestCase(DockerClientTestCase):
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
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]
ipam_config = web_container.get(
@ -803,11 +819,11 @@ class CLITestCase(DockerClientTestCase):
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
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]
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
@v2_only()
@no_cluster('container networks not supported in Swarm')
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.client.start(c)
container_mode_source = 'container:{}'.format(c['Id'])
@ -862,7 +882,7 @@ class CLITestCase(DockerClientTestCase):
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
@ -899,7 +919,7 @@ class CLITestCase(DockerClientTestCase):
network_names = ['{}_{}'.format(self.project.name, n) for n in ['foo', 'bar']]
for name in network_names:
self.client.create_network(name)
self.client.create_network(name, attachable=True)
self.dispatch(['-f', filename, 'up', '-d'])
container = self.project.containers()[0]
@ -917,12 +937,12 @@ class CLITestCase(DockerClientTestCase):
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
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'])
container = self.project.containers()[0]
@ -941,10 +961,10 @@ class CLITestCase(DockerClientTestCase):
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 networks[0]['Labels']['label_key'] == 'label_val'
@ -961,10 +981,10 @@ class CLITestCase(DockerClientTestCase):
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 volumes[0]['Labels']['label_key'] == 'label_val'
@ -975,7 +995,7 @@ class CLITestCase(DockerClientTestCase):
network_names = [
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 == []
@ -1010,6 +1030,7 @@ class CLITestCase(DockerClientTestCase):
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):
self.base_dir = 'tests/fixtures/net-container'
self.dispatch(['up', '-d'], None)
@ -1261,6 +1282,7 @@ class CLITestCase(DockerClientTestCase):
[u'/bin/true'],
)
@py.test.mark.skipif(SWARM_SKIP_RM_VOLUMES, reason='Swarm DELETE /containers/<id> bug')
def test_run_rm(self):
self.base_dir = 'tests/fixtures/volume'
proc = start_process(self.base_dir, ['run', '--rm', 'test'])
@ -1274,7 +1296,7 @@ class CLITestCase(DockerClientTestCase):
mounts = containers[0].get('Mounts')
for mount in mounts:
if mount['Destination'] == '/container-path':
anonymousName = mount['Name']
anonymous_name = mount['Name']
break
os.kill(proc.pid, signal.SIGINT)
wait_on_process(proc, 1)
@ -1287,9 +1309,11 @@ class CLITestCase(DockerClientTestCase):
if volume.internal == '/container-named-path':
name = volume.external
break
volumeNames = [v['Name'] for v in volumes]
assert name in volumeNames
assert anonymousName not in volumeNames
volume_names = [v['Name'].split('/')[-1] for v in volumes]
assert name in volume_names
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):
self.base_dir = 'tests/fixtures/entrypoint-dockerfile'
@ -1411,11 +1435,10 @@ class CLITestCase(DockerClientTestCase):
container.stop()
# check the ports
self.assertNotEqual(port_random, None)
self.assertIn("0.0.0.0", port_random)
self.assertEqual(port_assigned, "0.0.0.0:49152")
self.assertEqual(port_range[0], "0.0.0.0:49153")
self.assertEqual(port_range[1], "0.0.0.0:49154")
assert port_random is not None
assert port_assigned.endswith(':49152')
assert port_range[0].endswith(':49153')
assert port_range[1].endswith(':49154')
def test_run_service_with_explicitly_mapped_ports(self):
# create one off container
@ -1431,8 +1454,8 @@ class CLITestCase(DockerClientTestCase):
container.stop()
# check the ports
self.assertEqual(port_short, "0.0.0.0:30000")
self.assertEqual(port_full, "0.0.0.0:30001")
assert port_short.endswith(':30000')
assert port_full.endswith(':30001')
def test_run_service_with_explicitly_mapped_ip_ports(self):
# create one off container
@ -1953,9 +1976,9 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['port', 'simple', str(number)])
return result.stdout.rstrip()
self.assertEqual(get_port(3000), container.get_local_port(3000))
self.assertEqual(get_port(3001), "0.0.0.0:49152")
self.assertEqual(get_port(3002), "0.0.0.0:49153")
assert get_port(3000) == container.get_local_port(3000)
assert ':49152' in get_port(3001)
assert ':49153' in get_port(3002)
def test_expanded_port(self):
self.base_dir = 'tests/fixtures/ports-composefile'
@ -1966,9 +1989,9 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['port', 'simple', str(number)])
return result.stdout.rstrip()
self.assertEqual(get_port(3000), container.get_local_port(3000))
self.assertEqual(get_port(3001), "0.0.0.0:49152")
self.assertEqual(get_port(3002), "0.0.0.0:49153")
assert get_port(3000) == container.get_local_port(3000)
assert ':49152' in get_port(3001)
assert ':49153' in get_port(3002)
def test_port_with_scale(self):
self.base_dir = 'tests/fixtures/ports-composefile-scale'
@ -2021,12 +2044,14 @@ class CLITestCase(DockerClientTestCase):
assert len(lines) == 2
container, = self.project.containers()
expected_template = (
' container {} {} (image=busybox:latest, '
'name=simplecomposefile_simple_1)')
expected_template = ' container {} {}'
expected_meta_info = ['image=busybox:latest', 'name=simplecomposefile_simple_1']
assert expected_template.format('create', container.id) in lines[0]
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])
@ -2069,7 +2094,6 @@ class CLITestCase(DockerClientTestCase):
'docker-compose.yml',
'docker-compose.override.yml',
'extra.yml',
]
self._project = get_project(self.base_dir, config_paths)
self.dispatch(
@ -2086,7 +2110,6 @@ class CLITestCase(DockerClientTestCase):
web, other, db = containers
self.assertEqual(web.human_readable_command, 'top')
self.assertTrue({'db', 'other'} <= set(get_links(web)))
self.assertEqual(db.human_readable_command, 'top')
self.assertEqual(other.human_readable_command, 'top')

View File

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

View File

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

View File

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

View File

@ -1,8 +1,12 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import functools
import os
from docker.errors import APIError
from pytest import skip
from compose.config.config import ConfigDetails
from compose.config.config import ConfigFile
from compose.config.config import load
@ -44,3 +48,34 @@ def create_host_file(client, filename):
"Container exited with code {}:\n{}".format(exitcode, output))
finally:
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 pytest
from docker.errors import APIError
from docker.errors import NotFound
from .. import mock
from ..helpers import build_config as load_config
from ..helpers import create_host_file
from ..helpers import is_cluster
from ..helpers import no_cluster
from .testcases import DockerClientTestCase
from .testcases import SWARM_SKIP_CONTAINERS_ALL
from compose.config import config
from compose.config import ConfigurationError
from compose.config import types
@ -57,6 +61,20 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers()
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):
web = self.create_service('web')
db = self.create_service('db')
@ -110,6 +128,7 @@ class ProjectTest(DockerClientTestCase):
volumes=['/var/data'],
name='composetest_data_container',
labels={LABEL_PROJECT: 'composetest'},
host_config={},
)
project = Project.from_config(
name='composetest',
@ -125,6 +144,7 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw'])
@v2_only()
@no_cluster('container networks not supported in Swarm')
def test_network_mode_from_service(self):
project = Project.from_config(
name='composetest',
@ -152,6 +172,7 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
@v2_only()
@no_cluster('container networks not supported in Swarm')
def test_network_mode_from_container(self):
def get_project():
return Project.from_config(
@ -179,6 +200,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
host_config={},
)
net_container.start()
@ -188,6 +210,7 @@ class ProjectTest(DockerClientTestCase):
web = project.get_service('web')
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):
project = Project.from_config(
name='composetest',
@ -211,6 +234,7 @@ class ProjectTest(DockerClientTestCase):
net = project.get_service('net')
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 get_project():
return Project.from_config(
@ -235,6 +259,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
host_config={},
)
net_container.start()
@ -260,12 +285,12 @@ class ProjectTest(DockerClientTestCase):
project.start(service_names=['web'])
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]))
project.start()
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]))
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)
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'])
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)
project.remove_stopped(service_names=['web'])
@ -303,11 +330,13 @@ class ProjectTest(DockerClientTestCase):
project = Project('composetest', [web, db], self.client)
project.create(['db'])
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 1)
self.assertEqual(len(db.containers()), 0)
self.assertEqual(len(db.containers(stopped=True)), 1)
self.assertEqual(len(web.containers(stopped=True)), 0)
containers = project.containers(stopped=True)
assert len(containers) == 1
assert not containers[0].is_running
db_containers = db.containers(stopped=True)
assert len(db_containers) == 1
assert not db_containers[0].is_running
assert len(web.containers(stopped=True)) == 0
def test_create_twice(self):
web = self.create_service('web')
@ -316,12 +345,14 @@ class ProjectTest(DockerClientTestCase):
project.create(['db', 'web'])
project.create(['db', 'web'])
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 2)
self.assertEqual(len(db.containers()), 0)
self.assertEqual(len(db.containers(stopped=True)), 1)
self.assertEqual(len(web.containers()), 0)
self.assertEqual(len(web.containers(stopped=True)), 1)
containers = project.containers(stopped=True)
assert len(containers) == 2
db_containers = db.containers(stopped=True)
assert len(db_containers) == 1
assert not db_containers[0].is_running
web_containers = web.containers(stopped=True)
assert len(web_containers) == 1
assert not web_containers[0].is_running
def test_create_with_links(self):
db = self.create_service('db')
@ -329,12 +360,11 @@ class ProjectTest(DockerClientTestCase):
project = Project('composetest', [db, web], self.client)
project.create(['web'])
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 2)
self.assertEqual(len(db.containers()), 0)
self.assertEqual(len(db.containers(stopped=True)), 1)
self.assertEqual(len(web.containers()), 0)
self.assertEqual(len(web.containers(stopped=True)), 1)
# self.assertEqual(len(project.containers()), 0)
assert len(project.containers(stopped=True)) == 2
assert not [c for c in project.containers(stopped=True) if c.is_running]
assert len(db.containers(stopped=True)) == 1
assert len(web.containers(stopped=True)) == 1
def test_create_strategy_always(self):
db = self.create_service('db')
@ -343,11 +373,11 @@ class ProjectTest(DockerClientTestCase):
old_id = project.containers(stopped=True)[0].id
project.create(['db'], strategy=ConvergenceStrategy.always)
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 1)
assert len(project.containers(stopped=True)) == 1
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):
db = self.create_service('db')
@ -356,11 +386,11 @@ class ProjectTest(DockerClientTestCase):
old_id = project.containers(stopped=True)[0].id
project.create(['db'], strategy=ConvergenceStrategy.never)
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 1)
assert len(project.containers(stopped=True)) == 1
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):
web = self.create_service('web')
@ -550,8 +580,8 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(project.containers(stopped=True)), 2)
self.assertEqual(len(project.get_service('web').containers()), 0)
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)
assert not project.get_service('data').containers(stopped=True)[0].is_running
self.assertEqual(len(project.get_service('console').containers()), 0)
def test_project_up_recreate_with_tmpfs_volume(self):
@ -737,10 +767,10 @@ class ProjectTest(DockerClientTestCase):
"com.docker.compose.network.test": "9-29-045"
}
@v2_only()
@v2_1_only()
def test_up_with_network_static_addresses(self):
config_data = build_config(
version=V2_0,
version=V2_1,
services=[{
'name': 'web',
'image': 'busybox:latest',
@ -766,7 +796,8 @@ class ProjectTest(DockerClientTestCase):
{"subnet": "fe80::/64",
"gateway": "fe80::1001:1"}
]
}
},
'enable_ipv6': True,
}
}
)
@ -777,13 +808,8 @@ class ProjectTest(DockerClientTestCase):
)
project.up(detached=True)
network = self.client.networks(names=['static_test'])[0]
service_container = project.get_service('web').containers()[0]
assert network['Options'] == {
"com.docker.network.enable_ipv6": "true"
}
IPAMConfig = (service_container.inspect().get('NetworkSettings', {}).
get('Networks', {}).get('composetest_static_test', {}).
get('IPAMConfig', {}))
@ -825,7 +851,7 @@ class ProjectTest(DockerClientTestCase):
config_data=config_data,
)
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]
assert network['EnableIPv6'] is True
@ -1026,8 +1052,8 @@ class ProjectTest(DockerClientTestCase):
project.up()
self.assertEqual(len(project.containers()), 1)
volume_data = self.client.inspect_volume(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name)
volume_data = self.get_volume_data(full_vol_name)
assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local')
@v2_1_only()
@ -1062,10 +1088,12 @@ class ProjectTest(DockerClientTestCase):
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 volumes[0]['Labels']['label_key'] == 'label_val'
@ -1205,8 +1233,8 @@ class ProjectTest(DockerClientTestCase):
)
project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name)
assert volume_data['Name'] == full_vol_name
volume_data = self.get_volume_data(full_vol_name)
assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local'
@v2_only()
@ -1229,8 +1257,8 @@ class ProjectTest(DockerClientTestCase):
)
project.up()
volume_data = self.client.inspect_volume(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name)
volume_data = self.get_volume_data(full_vol_name)
assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local')
@v3_only()
@ -1287,10 +1315,11 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
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()
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_updated_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
@ -1310,8 +1339,8 @@ class ProjectTest(DockerClientTestCase):
)
project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name)
volume_data = self.get_volume_data(full_vol_name)
assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local')
config_data = config_data._replace(
@ -1348,8 +1377,8 @@ class ProjectTest(DockerClientTestCase):
)
project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name)
volume_data = self.get_volume_data(full_vol_name)
assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local')
config_data = config_data._replace(
@ -1361,11 +1390,12 @@ class ProjectTest(DockerClientTestCase):
client=self.client
)
project.volumes.initialize()
volume_data = self.client.inspect_volume(full_vol_name)
self.assertEqual(volume_data['Name'], full_vol_name)
volume_data = self.get_volume_data(full_vol_name)
assert volume_data['Name'].split('/')[-1] == full_vol_name
self.assertEqual(volume_data['Driver'], 'local')
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_external_volumes(self):
# Use composetest_ prefix so it gets garbage-collected in tearDown()
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 .. import mock
from ..helpers import is_cluster
from ..helpers import no_cluster
from .testcases import DockerClientTestCase
from .testcases import get_links
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.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec
@ -100,6 +104,7 @@ class ServiceTest(DockerClientTestCase):
service.start_container(container)
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):
service = self.create_service('db', cpu_shares=73)
container = service.create_container()
@ -151,6 +156,7 @@ class ServiceTest(DockerClientTestCase):
service.start_container(container)
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):
self.require_api_version('1.25')
docker_init_path = find_executable('docker-init')
@ -249,6 +255,7 @@ class ServiceTest(DockerClientTestCase):
'busybox', 'true',
volumes={container_path: {}},
labels={'com.docker.compose.test_image': 'true'},
host_config={}
)
image = self.client.commit(tmp_container)['Id']
@ -278,6 +285,7 @@ class ServiceTest(DockerClientTestCase):
image='busybox:latest',
command=["top"],
labels={LABEL_PROJECT: 'composetest'},
host_config={},
)
host_service = self.create_service(
'host',
@ -321,9 +329,15 @@ class ServiceTest(DockerClientTestCase):
self.assertIn('FOO=2', new_container.get('Config.Env'))
self.assertEqual(new_container.name, 'composetest_db_1')
self.assertEqual(new_container.get_mount('/etc')['Source'], volume_path)
self.assertIn(
'affinity:container==%s' % old_container.id,
new_container.get('Config.Env'))
if not is_cluster(self.client):
assert (
'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.assertNotEqual(old_container.id, new_container.id)
@ -350,8 +364,13 @@ class ServiceTest(DockerClientTestCase):
ConvergencePlan('recreate', [orig_container]))
assert new_container.get_mount('/etc')['Source'] == volume_path
assert ('affinity:container==%s' % orig_container.id in
new_container.get('Config.Env'))
if not is_cluster(self.client):
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
@ -464,18 +483,21 @@ class ServiceTest(DockerClientTestCase):
)
containers = service.execute_convergence_plan(ConvergencePlan('create', []), start=False)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
assert len(service_containers) == 1
assert not service_containers[0].is_running
containers = service.execute_convergence_plan(
ConvergencePlan('recreate', containers),
start=False)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
assert len(service_containers) == 1
assert not service_containers[0].is_running
service.execute_convergence_plan(ConvergencePlan('start', containers), start=False)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service_containers = service.containers(stopped=True)
assert len(service_containers) == 1
assert not service_containers[0].is_running
def test_start_container_passes_through_options(self):
db = self.create_service('db')
@ -487,6 +509,7 @@ class ServiceTest(DockerClientTestCase):
create_and_start_container(db)
self.assertEqual(db.containers()[0].environment['FOO'], 'BAR')
@no_cluster('No legacy links support in Swarm')
def test_start_container_creates_links(self):
db = self.create_service('db')
web = self.create_service('web', links=[(db, None)])
@ -503,6 +526,7 @@ class ServiceTest(DockerClientTestCase):
'db'])
)
@no_cluster('No legacy links support in Swarm')
def test_start_container_creates_links_with_names(self):
db = self.create_service('db')
web = self.create_service('web', links=[(db, 'custom_link_name')])
@ -519,6 +543,7 @@ class ServiceTest(DockerClientTestCase):
'custom_link_name'])
)
@no_cluster('No legacy links support in Swarm')
def test_start_container_with_external_links(self):
db = self.create_service('db')
web = self.create_service('web', external_links=['composetest_db_1',
@ -537,6 +562,7 @@ class ServiceTest(DockerClientTestCase):
'db_3']),
)
@no_cluster('No legacy links support in Swarm')
def test_start_normal_container_does_not_create_links_to_its_own_service(self):
db = self.create_service('db')
@ -546,6 +572,7 @@ class ServiceTest(DockerClientTestCase):
c = create_and_start_container(db)
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):
db = self.create_service('db')
@ -572,7 +599,7 @@ class ServiceTest(DockerClientTestCase):
container = create_and_start_container(service)
container.wait()
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):
self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test')
@ -719,20 +746,27 @@ class ServiceTest(DockerClientTestCase):
'0.0.0.0:9001:9000/udp',
])
container = create_and_start_container(service).inspect()
self.assertEqual(container['NetworkSettings']['Ports'], {
'8000/tcp': [
{
'HostIp': '127.0.0.1',
'HostPort': '8001',
},
],
'9000/udp': [
{
'HostIp': '0.0.0.0',
'HostPort': '9001',
},
],
})
assert container['NetworkSettings']['Ports']['8000/tcp'] == [{
'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'
# self.assertEqual(container['NetworkSettings']['Ports'], {
# '8000/tcp': [
# {
# 'HostIp': '127.0.0.1',
# 'HostPort': '8001',
# },
# ],
# '9000/udp': [
# {
# 'HostIp': '0.0.0.0',
# 'HostPort': '9001',
# },
# ],
# })
def test_create_with_image_id(self):
# Get image id for the current busybox:latest
@ -760,6 +794,10 @@ class ServiceTest(DockerClientTestCase):
service.scale(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):
"""
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()
# 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.remove_container(c)

View File

@ -8,6 +8,7 @@ from docker.utils import version_lt
from pytest import skip
from .. import unittest
from ..helpers import is_cluster
from compose.cli.docker_client import docker_client
from compose.config.config import resolve_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.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):
client.pull('busybox:latest', stream=False)
@ -97,7 +102,7 @@ class DockerClientTestCase(unittest.TestCase):
for i in self.client.images(
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 []
for v in volumes:
@ -133,3 +138,11 @@ class DockerClientTestCase(unittest.TestCase):
api_version = self.client.version()['ApiVersion']
if version_lt(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 ..helpers import no_cluster
from .testcases import DockerClientTestCase
from compose.const import LABEL_PROJECT
from compose.const import LABEL_VOLUME
@ -35,26 +36,28 @@ class VolumeTest(DockerClientTestCase):
def test_create_volume(self):
vol = self.create_volume('volume01')
vol.create()
info = self.client.inspect_volume(vol.full_name)
assert info['Name'] == vol.full_name
info = self.get_volume_data(vol.full_name)
assert info['Name'].split('/')[-1] == vol.full_name
def test_recreate_existing_volume(self):
vol = self.create_volume('volume01')
vol.create()
info = self.client.inspect_volume(vol.full_name)
assert info['Name'] == vol.full_name
info = self.get_volume_data(vol.full_name)
assert info['Name'].split('/')[-1] == vol.full_name
vol.create()
info = self.client.inspect_volume(vol.full_name)
assert info['Name'] == vol.full_name
info = self.get_volume_data(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):
vol = self.create_volume('volume01')
vol.create()
info = vol.inspect()
assert info['Name'] == vol.full_name
@no_cluster('remove volume by name defect on Swarm Classic')
def test_remove_volume(self):
vol = Volume(self.client, 'composetest', 'volume01')
vol.create()
@ -62,6 +65,7 @@ class VolumeTest(DockerClientTestCase):
volumes = self.client.volumes()['Volumes']
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):
vol = self.create_volume('composetest_volume_ext', external=True)
assert vol.external is True
@ -70,6 +74,7 @@ class VolumeTest(DockerClientTestCase):
info = vol.inspect()
assert info['Name'] == vol.name
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_external_aliased_volume(self):
alias_name = 'composetest_alias01'
vol = self.create_volume('volume01', external=alias_name)
@ -79,24 +84,28 @@ class VolumeTest(DockerClientTestCase):
info = vol.inspect()
assert info['Name'] == alias_name
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_exists(self):
vol = self.create_volume('volume01')
assert vol.exists() is False
vol.create()
assert vol.exists() is True
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_exists_external(self):
vol = self.create_volume('volume01', external=True)
assert vol.exists() is False
vol.create()
assert vol.exists() is True
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_exists_external_aliased(self):
vol = self.create_volume('volume01', external='composetest_alias01')
assert vol.exists() is False
vol.create()
assert vol.exists() is True
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_volume_default_labels(self):
vol = self.create_volume('volume01')
vol.create()