summaryrefslogtreecommitdiff
path: root/tests/integration
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration')
-rw-r--r--tests/integration/network_test.py17
-rw-r--r--tests/integration/project_test.py683
-rw-r--r--tests/integration/resilience_test.py5
-rw-r--r--tests/integration/service_test.py393
-rw-r--r--tests/integration/state_test.py19
-rw-r--r--tests/integration/testcases.py122
-rw-r--r--tests/integration/volume_test.py52
7 files changed, 1117 insertions, 174 deletions
diff --git a/tests/integration/network_test.py b/tests/integration/network_test.py
new file mode 100644
index 00000000..2ff610fb
--- /dev/null
+++ b/tests/integration/network_test.py
@@ -0,0 +1,17 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from .testcases import DockerClientTestCase
+from compose.const import LABEL_NETWORK
+from compose.const import LABEL_PROJECT
+from compose.network import Network
+
+
+class NetworkTest(DockerClientTestCase):
+ def test_network_default_labels(self):
+ net = Network(self.client, 'composetest', 'foonet')
+ net.ensure()
+ net_data = net.inspect()
+ labels = net_data['Labels']
+ assert labels[LABEL_NETWORK] == net.name
+ assert labels[LABEL_PROJECT] == net.project
diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py
index 6e82e931..953dd52b 100644
--- a/tests/integration/project_test.py
+++ b/tests/integration/project_test.py
@@ -1,27 +1,53 @@
from __future__ import absolute_import
from __future__ import unicode_literals
+import os.path
import random
import py
import pytest
+from docker.errors import APIError
from docker.errors import NotFound
from .. import mock
-from ..helpers import build_config
+from ..helpers import build_config as load_config
+from ..helpers import create_host_file
from .testcases import DockerClientTestCase
+from .testcases import SWARM_SKIP_CONTAINERS_ALL
from compose.config import config
from compose.config import ConfigurationError
-from compose.config.config import V2_0
+from compose.config import types
from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec
+from compose.const import COMPOSEFILE_V2_0 as V2_0
+from compose.const import COMPOSEFILE_V2_1 as V2_1
+from compose.const import COMPOSEFILE_V2_2 as V2_2
+from compose.const import COMPOSEFILE_V3_1 as V3_1
from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE
from compose.container import Container
+from compose.errors import HealthCheckFailed
+from compose.errors import NoHealthCheckConfigured
from compose.project import Project
from compose.project import ProjectError
from compose.service import ConvergenceStrategy
+from tests.integration.testcases import is_cluster
+from tests.integration.testcases import no_cluster
+from tests.integration.testcases import v2_1_only
+from tests.integration.testcases import v2_2_only
from tests.integration.testcases import v2_only
+from tests.integration.testcases import v3_only
+
+
+def build_config(**kwargs):
+ return config.Config(
+ version=kwargs.get('version'),
+ services=kwargs.get('services'),
+ volumes=kwargs.get('volumes'),
+ networks=kwargs.get('networks'),
+ secrets=kwargs.get('secrets'),
+ configs=kwargs.get('configs'),
+ )
class ProjectTest(DockerClientTestCase):
@@ -36,6 +62,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')
@@ -66,7 +106,7 @@ class ProjectTest(DockerClientTestCase):
def test_volumes_from_service(self):
project = Project.from_config(
name='composetest',
- config_data=build_config({
+ config_data=load_config({
'data': {
'image': 'busybox:latest',
'volumes': ['/var/data'],
@@ -89,10 +129,11 @@ class ProjectTest(DockerClientTestCase):
volumes=['/var/data'],
name='composetest_data_container',
labels={LABEL_PROJECT: 'composetest'},
+ host_config={},
)
project = Project.from_config(
name='composetest',
- config_data=build_config({
+ config_data=load_config({
'db': {
'image': 'busybox:latest',
'volumes_from': ['composetest_data_container'],
@@ -104,12 +145,13 @@ 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',
client=self.client,
- config_data=build_config({
- 'version': V2_0,
+ config_data=load_config({
+ 'version': str(V2_0),
'services': {
'net': {
'image': 'busybox:latest',
@@ -131,12 +173,13 @@ 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(
name='composetest',
- config_data=build_config({
- 'version': V2_0,
+ config_data=load_config({
+ 'version': str(V2_0),
'services': {
'web': {
'image': 'busybox:latest',
@@ -158,6 +201,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
+ host_config={},
)
net_container.start()
@@ -167,10 +211,11 @@ 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',
- config_data=build_config({
+ config_data=load_config({
'net': {
'image': 'busybox:latest',
'command': ["top"]
@@ -190,11 +235,12 @@ 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(
name='composetest',
- config_data=build_config({
+ config_data=load_config({
'web': {
'image': 'busybox:latest',
'net': 'container:composetest_net_container'
@@ -214,6 +260,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
+ host_config={},
)
net_container.start()
@@ -239,12 +286,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'])
@@ -264,10 +311,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'])
@@ -282,11 +331,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')
@@ -295,12 +346,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')
@@ -308,12 +361,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')
@@ -322,11 +374,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')
@@ -335,11 +387,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')
@@ -465,7 +517,7 @@ class ProjectTest(DockerClientTestCase):
def test_project_up_starts_depends(self):
project = Project.from_config(
name='composetest',
- config_data=build_config({
+ config_data=load_config({
'console': {
'image': 'busybox:latest',
'command': ["top"],
@@ -500,7 +552,7 @@ class ProjectTest(DockerClientTestCase):
def test_project_up_with_no_deps(self):
project = Project.from_config(
name='composetest',
- config_data=build_config({
+ config_data=load_config({
'console': {
'image': 'busybox:latest',
'command': ["top"],
@@ -529,10 +581,28 @@ 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):
+ # https://github.com/docker/compose/issues/4751
+ project = Project.from_config(
+ name='composetest',
+ config_data=load_config({
+ 'version': '2.1',
+ 'services': {
+ 'foo': {
+ 'image': 'busybox:latest',
+ 'tmpfs': ['/dev/shm'],
+ 'volumes': ['/dev/shm']
+ }
+ }
+ }), client=self.client
+ )
+ project.up()
+ project.up(strategy=ConvergenceStrategy.always)
+
def test_unscale_after_restart(self):
web = self.create_service('web')
project = Project('composetest', [web], self.client)
@@ -546,12 +616,12 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(service.containers()), 3)
project.up()
service = project.get_service('web')
- self.assertEqual(len(service.containers()), 3)
+ self.assertEqual(len(service.containers()), 1)
service.scale(1)
self.assertEqual(len(service.containers()), 1)
- project.up()
+ project.up(scale_override={'web': 3})
service = project.get_service('web')
- self.assertEqual(len(service.containers()), 1)
+ self.assertEqual(len(service.containers()), 3)
# does scale=0 ,makes any sense? after recreating at least 1 container is running
service.scale(0)
project.up()
@@ -560,7 +630,7 @@ class ProjectTest(DockerClientTestCase):
@v2_only()
def test_project_up_networks(self):
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -572,7 +642,6 @@ class ProjectTest(DockerClientTestCase):
'baz': {'aliases': ['extra']},
},
}],
- volumes={},
networks={
'foo': {'driver': 'bridge'},
'bar': {'driver': None},
@@ -606,14 +675,13 @@ class ProjectTest(DockerClientTestCase):
@v2_only()
def test_up_with_ipam_config(self):
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': 'busybox:latest',
'networks': {'front': None},
}],
- volumes={},
networks={
'front': {
'driver': 'bridge',
@@ -666,12 +734,47 @@ class ProjectTest(DockerClientTestCase):
}
@v2_only()
- def test_up_with_network_static_addresses(self):
- config_data = config.Config(
+ def test_up_with_ipam_options(self):
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': 'busybox:latest',
+ 'networks': {'front': None},
+ }],
+ networks={
+ 'front': {
+ 'driver': 'bridge',
+ 'ipam': {
+ 'driver': 'default',
+ 'options': {
+ "com.docker.compose.network.test": "9-29-045"
+ }
+ },
+ },
+ },
+ )
+
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data,
+ )
+ project.up()
+
+ network = self.client.networks(names=['composetest_front'])[0]
+
+ assert network['IPAM']['Options'] == {
+ "com.docker.compose.network.test": "9-29-045"
+ }
+
+ @v2_1_only()
+ def test_up_with_network_static_addresses(self):
+ config_data = build_config(
+ version=V2_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
'command': 'top',
'networks': {
'static_test': {
@@ -680,7 +783,6 @@ class ProjectTest(DockerClientTestCase):
}
},
}],
- volumes={},
networks={
'static_test': {
'driver': 'bridge',
@@ -695,7 +797,8 @@ class ProjectTest(DockerClientTestCase):
{"subnet": "fe80::/64",
"gateway": "fe80::1001:1"}
]
- }
+ },
+ 'enable_ipv6': True,
}
}
)
@@ -706,22 +809,61 @@ 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', {}))
assert IPAMConfig.get('IPv4Address') == '172.16.100.100'
assert IPAMConfig.get('IPv6Address') == 'fe80::1001:102'
+ @v2_1_only()
+ def test_up_with_enable_ipv6(self):
+ self.require_api_version('1.23')
+ config_data = build_config(
+ version=V2_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'networks': {
+ 'static_test': {
+ 'ipv6_address': 'fe80::1001:102'
+ }
+ },
+ }],
+ networks={
+ 'static_test': {
+ 'driver': 'bridge',
+ 'enable_ipv6': True,
+ 'ipam': {
+ 'driver': 'default',
+ 'config': [
+ {"subnet": "fe80::/64",
+ "gateway": "fe80::1001:1"}
+ ]
+ }
+ }
+ }
+ )
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data,
+ )
+ project.up(detached=True)
+ 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
+ ipam_config = (service_container.inspect().get('NetworkSettings', {}).
+ get('Networks', {}).get('composetest_static_test', {}).
+ get('IPAMConfig', {}))
+ assert ipam_config.get('IPv6Address') == 'fe80::1001:102'
+
@v2_only()
def test_up_with_network_static_addresses_missing_subnet(self):
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -733,7 +875,6 @@ class ProjectTest(DockerClientTestCase):
}
},
}],
- volumes={},
networks={
'static_test': {
'driver': 'bridge',
@@ -756,11 +897,146 @@ class ProjectTest(DockerClientTestCase):
with self.assertRaises(ProjectError):
project.up()
+ @v2_1_only()
+ def test_up_with_network_link_local_ips(self):
+ config_data = build_config(
+ version=V2_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'networks': {
+ 'linklocaltest': {
+ 'link_local_ips': ['169.254.8.8']
+ }
+ }
+ }],
+ networks={
+ 'linklocaltest': {'driver': 'bridge'}
+ }
+ )
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data
+ )
+ project.up(detached=True)
+
+ service_container = project.get_service('web').containers(stopped=True)[0]
+ ipam_config = service_container.inspect().get(
+ 'NetworkSettings', {}
+ ).get(
+ 'Networks', {}
+ ).get(
+ 'composetest_linklocaltest', {}
+ ).get('IPAMConfig', {})
+ assert 'LinkLocalIPs' in ipam_config
+ assert ipam_config['LinkLocalIPs'] == ['169.254.8.8']
+
+ @v2_1_only()
+ def test_up_with_isolation(self):
+ self.require_api_version('1.24')
+ config_data = build_config(
+ version=V2_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'isolation': 'default'
+ }],
+ )
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data
+ )
+ project.up(detached=True)
+ service_container = project.get_service('web').containers(stopped=True)[0]
+ assert service_container.inspect()['HostConfig']['Isolation'] == 'default'
+
+ @v2_1_only()
+ def test_up_with_invalid_isolation(self):
+ self.require_api_version('1.24')
+ config_data = build_config(
+ version=V2_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'isolation': 'foobar'
+ }],
+ )
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data
+ )
+ with self.assertRaises(ProjectError):
+ project.up()
+
+ @v2_only()
+ def test_project_up_with_network_internal(self):
+ self.require_api_version('1.23')
+ config_data = build_config(
+ version=V2_0,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'networks': {'internal': None},
+ }],
+ networks={
+ 'internal': {'driver': 'bridge', 'internal': True},
+ },
+ )
+
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data,
+ )
+ project.up()
+
+ network = self.client.networks(names=['composetest_internal'])[0]
+
+ assert network['Internal'] is True
+
+ @v2_1_only()
+ def test_project_up_with_network_label(self):
+ self.require_api_version('1.23')
+
+ network_name = 'network_with_label'
+
+ config_data = build_config(
+ version=V2_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'networks': {network_name: None}
+ }],
+ networks={
+ network_name: {'labels': {'label_key': 'label_val'}}
+ }
+ )
+
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data
+ )
+
+ project.up()
+
+ networks = [
+ n for n in self.client.networks()
+ if n['Name'].startswith('composetest_')
+ ]
+
+ assert [n['Name'] for n in networks] == ['composetest_{}'.format(network_name)]
+ assert 'label_key' in networks[0]['Labels']
+ assert networks[0]['Labels']['label_key'] == 'label_val'
+
@v2_only()
def test_project_up_volumes(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -768,7 +1044,6 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={vol_name: {'driver': 'local'}},
- networks={},
)
project = Project.from_config(
@@ -778,16 +1053,58 @@ 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()
+ def test_project_up_with_volume_labels(self):
+ self.require_api_version('1.23')
+
+ volume_name = 'volume_with_label'
+
+ config_data = build_config(
+ version=V2_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'volumes': [VolumeSpec.parse('{}:/data'.format(volume_name))]
+ }],
+ volumes={
+ volume_name: {
+ 'labels': {
+ 'label_key': 'label_val'
+ }
+ }
+ },
+ )
+
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data,
+ )
+
+ project.up()
+
+ volumes = [
+ v for v in self.client.volumes().get('Volumes', [])
+ if v['Name'].split('/')[-1].startswith('composetest_')
+ ]
+
+ 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'
+
@v2_only()
def test_project_up_logging_with_multiple_files(self):
base_file = config.ConfigFile(
'base.yml',
{
- 'version': V2_0,
+ 'version': str(V2_0),
'services': {
'simple': {'image': 'busybox:latest', 'command': 'top'},
'another': {
@@ -806,7 +1123,7 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile(
'override.yml',
{
- 'version': V2_0,
+ 'version': str(V2_0),
'services': {
'another': {
'logging': {
@@ -839,7 +1156,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile(
'base.yml',
{
- 'version': V2_0,
+ 'version': str(V2_0),
'services': {
'simple': {
'image': 'busybox:latest',
@@ -852,7 +1169,7 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile(
'override.yml',
{
- 'version': V2_0,
+ 'version': str(V2_0),
'services': {
'simple': {
'ports': ['1234:1234']
@@ -870,11 +1187,39 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers()
self.assertEqual(len(containers), 1)
+ @v2_2_only()
+ def test_project_up_config_scale(self):
+ config_data = build_config(
+ version=V2_2,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'scale': 3
+ }]
+ )
+
+ project = Project.from_config(
+ name='composetest', config_data=config_data, client=self.client
+ )
+ project.up()
+ assert len(project.containers()) == 3
+
+ project.up(scale_override={'web': 2})
+ assert len(project.containers()) == 2
+
+ project.up(scale_override={'web': 4})
+ assert len(project.containers()) == 4
+
+ project.stop()
+ project.up()
+ assert len(project.containers()) == 3
+
@v2_only()
def test_initialize_volumes(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -882,7 +1227,6 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={vol_name: {}},
- networks={},
)
project = Project.from_config(
@@ -891,15 +1235,15 @@ class ProjectTest(DockerClientTestCase):
)
project.volumes.initialize()
- volume_data = self.client.inspect_volume(full_vol_name)
- self.assertEqual(volume_data['Name'], full_vol_name)
- self.assertEqual(volume_data['Driver'], 'local')
+ 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()
def test_project_up_implicit_volume_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -907,7 +1251,6 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={vol_name: {}},
- networks={},
)
project = Project.from_config(
@@ -916,15 +1259,52 @@ 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()
+ def test_project_up_with_secrets(self):
+ node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
+
+ config_data = build_config(
+ version=V3_1,
+ services=[{
+ 'name': 'web',
+ 'image': 'busybox:latest',
+ 'command': 'cat /run/secrets/special',
+ 'secrets': [
+ types.ServiceSecret.parse({'source': 'super', 'target': 'special'}),
+ ],
+ 'environment': ['constraint:node=={}'.format(node if node is not None else '*')]
+ }],
+ secrets={
+ 'super': {
+ 'file': os.path.abspath('tests/fixtures/secrets/default'),
+ },
+ },
+ )
+
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data,
+ )
+ project.up()
+ project.stop()
+
+ containers = project.containers(stopped=True)
+ assert len(containers) == 1
+ container, = containers
+
+ output = container.logs()
+ assert output == b"This is the secret\n"
+
@v2_only()
def test_initialize_volumes_invalid_volume_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -932,22 +1312,22 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={vol_name: {'driver': 'foobar'}},
- networks={},
)
project = Project.from_config(
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)
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -955,7 +1335,6 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={vol_name: {'driver': 'local'}},
- networks={},
)
project = Project.from_config(
name='composetest',
@@ -963,8 +1342,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(
@@ -986,7 +1365,7 @@ class ProjectTest(DockerClientTestCase):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -994,7 +1373,6 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={vol_name: {'driver': 'local'}},
- networks={},
)
project = Project.from_config(
name='composetest',
@@ -1002,8 +1380,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(
@@ -1015,17 +1393,18 @@ 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))
full_vol_name = 'composetest_{0}'.format(vol_name)
self.client.create_volume(vol_name)
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -1033,9 +1412,8 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={
- vol_name: {'external': True, 'external_name': vol_name}
+ vol_name: {'external': True, 'name': vol_name}
},
- networks=None,
)
project = Project.from_config(
name='composetest',
@@ -1050,7 +1428,7 @@ class ProjectTest(DockerClientTestCase):
def test_initialize_volumes_inexistent_external_volume(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
- config_data = config.Config(
+ config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
@@ -1058,9 +1436,8 @@ class ProjectTest(DockerClientTestCase):
'command': 'top'
}],
volumes={
- vol_name: {'external': True, 'external_name': vol_name}
+ vol_name: {'external': True, 'name': vol_name}
},
- networks=None,
)
project = Project.from_config(
name='composetest',
@@ -1080,7 +1457,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile(
'base.yml',
{
- 'version': V2_0,
+ 'version': str(V2_0),
'services': {
'simple': {
'image': 'busybox:latest',
@@ -1117,7 +1494,7 @@ class ProjectTest(DockerClientTestCase):
}
}
- config_data = build_config(config_dict)
+ config_data = load_config(config_dict)
project = Project.from_config(
name='composetest', config_data=config_data, client=self.client
)
@@ -1125,7 +1502,7 @@ class ProjectTest(DockerClientTestCase):
config_dict['service2'] = config_dict['service1']
del config_dict['service1']
- config_data = build_config(config_dict)
+ config_data = load_config(config_dict)
project = Project.from_config(
name='composetest', config_data=config_data, client=self.client
)
@@ -1145,3 +1522,115 @@ class ProjectTest(DockerClientTestCase):
ctnr for ctnr in project._labeled_containers()
if ctnr.labels.get(LABEL_SERVICE) == 'service1'
]) == 0
+
+ @v2_1_only()
+ def test_project_up_healthy_dependency(self):
+ config_dict = {
+ 'version': '2.1',
+ 'services': {
+ 'svc1': {
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'healthcheck': {
+ 'test': 'exit 0',
+ 'retries': 1,
+ 'timeout': '10s',
+ 'interval': '1s'
+ },
+ },
+ 'svc2': {
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'depends_on': {
+ 'svc1': {'condition': 'service_healthy'},
+ }
+ }
+ }
+ }
+ config_data = load_config(config_dict)
+ project = Project.from_config(
+ name='composetest', config_data=config_data, client=self.client
+ )
+ project.up()
+ containers = project.containers()
+ assert len(containers) == 2
+
+ svc1 = project.get_service('svc1')
+ svc2 = project.get_service('svc2')
+ assert 'svc1' in svc2.get_dependency_names()
+ assert svc1.is_healthy()
+
+ @v2_1_only()
+ def test_project_up_unhealthy_dependency(self):
+ config_dict = {
+ 'version': '2.1',
+ 'services': {
+ 'svc1': {
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'healthcheck': {
+ 'test': 'exit 1',
+ 'retries': 1,
+ 'timeout': '10s',
+ 'interval': '1s'
+ },
+ },
+ 'svc2': {
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'depends_on': {
+ 'svc1': {'condition': 'service_healthy'},
+ }
+ }
+ }
+ }
+ config_data = load_config(config_dict)
+ project = Project.from_config(
+ name='composetest', config_data=config_data, client=self.client
+ )
+ with pytest.raises(ProjectError):
+ project.up()
+ containers = project.containers()
+ assert len(containers) == 1
+
+ svc1 = project.get_service('svc1')
+ svc2 = project.get_service('svc2')
+ assert 'svc1' in svc2.get_dependency_names()
+ with pytest.raises(HealthCheckFailed):
+ svc1.is_healthy()
+
+ @v2_1_only()
+ def test_project_up_no_healthcheck_dependency(self):
+ config_dict = {
+ 'version': '2.1',
+ 'services': {
+ 'svc1': {
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'healthcheck': {
+ 'disable': True
+ },
+ },
+ 'svc2': {
+ 'image': 'busybox:latest',
+ 'command': 'top',
+ 'depends_on': {
+ 'svc1': {'condition': 'service_healthy'},
+ }
+ }
+ }
+ }
+ config_data = load_config(config_dict)
+ project = Project.from_config(
+ name='composetest', config_data=config_data, client=self.client
+ )
+ with pytest.raises(ProjectError):
+ project.up()
+ containers = project.containers()
+ assert len(containers) == 1
+
+ svc1 = project.get_service('svc1')
+ svc2 = project.get_service('svc2')
+ assert 'svc1' in svc2.get_dependency_names()
+ with pytest.raises(NoHealthCheckConfigured):
+ svc1.is_healthy()
diff --git a/tests/integration/resilience_test.py b/tests/integration/resilience_test.py
index b544783a..2a2d1b56 100644
--- a/tests/integration/resilience_test.py
+++ b/tests/integration/resilience_test.py
@@ -20,6 +20,11 @@ class ResilienceTest(DockerClientTestCase):
self.db.start_container(container)
self.host_path = container.get_mount('/var/db')['Source']
+ def tearDown(self):
+ del self.project
+ del self.db
+ super(ResilienceTest, self).tearDown()
+
def test_successful_recreate(self):
self.project.up(strategy=ConvergenceStrategy.always)
container = self.db.containers()[0]
diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py
index 053dee1b..3ddf991b 100644
--- a/tests/integration/service_test.py
+++ b/tests/integration/service_test.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
import os
import shutil
import tempfile
+from distutils.spawn import find_executable
from os import path
import pytest
@@ -15,9 +16,12 @@ from .. import mock
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
+from compose.const import IS_WINDOWS_PLATFORM
from compose.const import LABEL_CONFIG_HASH
from compose.const import LABEL_CONTAINER_NUMBER
from compose.const import LABEL_ONE_OFF
@@ -25,12 +29,21 @@ from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE
from compose.const import LABEL_VERSION
from compose.container import Container
+from compose.errors import OperationFailedError
from compose.project import OneOffFilter
from compose.service import ConvergencePlan
from compose.service import ConvergenceStrategy
from compose.service import NetworkMode
+from compose.service import PidMode
from compose.service import Service
+from compose.utils import parse_nanoseconds_int
+from tests.integration.testcases import is_cluster
+from tests.integration.testcases import no_cluster
+from tests.integration.testcases import v2_1_only
+from tests.integration.testcases import v2_2_only
+from tests.integration.testcases import v2_3_only
from tests.integration.testcases import v2_only
+from tests.integration.testcases import v3_only
def create_and_start_container(service, **override_options):
@@ -39,6 +52,7 @@ def create_and_start_container(service, **override_options):
class ServiceTest(DockerClientTestCase):
+
def test_containers(self):
foo = self.create_service('foo')
bar = self.create_service('bar')
@@ -93,6 +107,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()
@@ -105,6 +120,31 @@ class ServiceTest(DockerClientTestCase):
container.start()
self.assertEqual(container.get('HostConfig.CpuQuota'), 40000)
+ @v2_2_only()
+ def test_create_container_with_cpu_count(self):
+ self.require_api_version('1.25')
+ service = self.create_service('db', cpu_count=2)
+ container = service.create_container()
+ service.start_container(container)
+ self.assertEqual(container.get('HostConfig.CpuCount'), 2)
+
+ @v2_2_only()
+ @pytest.mark.skipif(not IS_WINDOWS_PLATFORM, reason='cpu_percent is not supported for Linux')
+ def test_create_container_with_cpu_percent(self):
+ self.require_api_version('1.25')
+ service = self.create_service('db', cpu_percent=12)
+ container = service.create_container()
+ service.start_container(container)
+ self.assertEqual(container.get('HostConfig.CpuPercent'), 12)
+
+ @v2_2_only()
+ def test_create_container_with_cpus(self):
+ self.require_api_version('1.25')
+ service = self.create_service('db', cpus=1)
+ container = service.create_container()
+ service.start_container(container)
+ self.assertEqual(container.get('HostConfig.NanoCpus'), 1000000000)
+
def test_create_container_with_shm_size(self):
self.require_api_version('1.22')
service = self.create_service('db', shm_size=67108864)
@@ -112,6 +152,30 @@ class ServiceTest(DockerClientTestCase):
service.start_container(container)
self.assertEqual(container.get('HostConfig.ShmSize'), 67108864)
+ def test_create_container_with_init_bool(self):
+ self.require_api_version('1.25')
+ service = self.create_service('db', init=True)
+ container = service.create_container()
+ 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')
+ service = self.create_service('db', init=docker_init_path)
+ container = service.create_container()
+ service.start_container(container)
+ assert container.get('HostConfig.InitPath') == docker_init_path
+
+ @pytest.mark.xfail(True, reason='Some kernels/configs do not support pids_limit')
+ def test_create_container_with_pids_limit(self):
+ self.require_api_version('1.23')
+ service = self.create_service('db', pids_limit=10)
+ container = service.create_container()
+ service.start_container(container)
+ assert container.get('HostConfig.PidsLimit') == 10
+
def test_create_container_with_extra_hosts_list(self):
extra_hosts = ['somehost:162.242.195.82', 'otherhost:50.31.209.229']
service = self.create_service('db', extra_hosts=extra_hosts)
@@ -140,6 +204,34 @@ class ServiceTest(DockerClientTestCase):
service.start_container(container)
assert container.get('HostConfig.ReadonlyRootfs') == read_only
+ def test_create_container_with_blkio_config(self):
+ blkio_config = {
+ 'weight': 300,
+ 'weight_device': [{'path': '/dev/sda', 'weight': 200}],
+ 'device_read_bps': [{'path': '/dev/sda', 'rate': 1024 * 1024 * 100}],
+ 'device_read_iops': [{'path': '/dev/sda', 'rate': 1000}],
+ 'device_write_bps': [{'path': '/dev/sda', 'rate': 1024 * 1024}],
+ 'device_write_iops': [{'path': '/dev/sda', 'rate': 800}]
+ }
+ service = self.create_service('web', blkio_config=blkio_config)
+ container = service.create_container()
+ assert container.get('HostConfig.BlkioWeight') == 300
+ assert container.get('HostConfig.BlkioWeightDevice') == [{
+ 'Path': '/dev/sda', 'Weight': 200
+ }]
+ assert container.get('HostConfig.BlkioDeviceReadBps') == [{
+ 'Path': '/dev/sda', 'Rate': 1024 * 1024 * 100
+ }]
+ assert container.get('HostConfig.BlkioDeviceWriteBps') == [{
+ 'Path': '/dev/sda', 'Rate': 1024 * 1024
+ }]
+ assert container.get('HostConfig.BlkioDeviceReadIOps') == [{
+ 'Path': '/dev/sda', 'Rate': 1000
+ }]
+ assert container.get('HostConfig.BlkioDeviceWriteIOps') == [{
+ 'Path': '/dev/sda', 'Rate': 800
+ }]
+
def test_create_container_with_security_opt(self):
security_opt = ['label:disable']
service = self.create_service('db', security_opt=security_opt)
@@ -147,6 +239,15 @@ class ServiceTest(DockerClientTestCase):
service.start_container(container)
self.assertEqual(set(container.get('HostConfig.SecurityOpt')), set(security_opt))
+ # @pytest.mark.xfail(True, reason='Not supported on most drivers')
+ @pytest.mark.skipif(True, reason='https://github.com/moby/moby/issues/34270')
+ def test_create_container_with_storage_opt(self):
+ storage_opt = {'size': '1G'}
+ service = self.create_service('db', storage_opt=storage_opt)
+ container = service.create_container()
+ service.start_container(container)
+ self.assertEqual(container.get('HostConfig.StorageOpt'), storage_opt)
+
def test_create_container_with_mac_address(self):
service = self.create_service('db', mac_address='02:42:ac:11:65:43')
container = service.create_container()
@@ -170,6 +271,24 @@ class ServiceTest(DockerClientTestCase):
self.assertTrue(path.basename(actual_host_path) == path.basename(host_path),
msg=("Last component differs: %s, %s" % (actual_host_path, host_path)))
+ def test_create_container_with_healthcheck_config(self):
+ one_second = parse_nanoseconds_int('1s')
+ healthcheck = {
+ 'test': ['true'],
+ 'interval': 2 * one_second,
+ 'timeout': 5 * one_second,
+ 'retries': 5,
+ 'start_period': 2 * one_second
+ }
+ service = self.create_service('db', healthcheck=healthcheck)
+ container = service.create_container()
+ remote_healthcheck = container.get('Config.Healthcheck')
+ assert remote_healthcheck['Test'] == healthcheck['test']
+ assert remote_healthcheck['Interval'] == healthcheck['interval']
+ assert remote_healthcheck['Timeout'] == healthcheck['timeout']
+ assert remote_healthcheck['Retries'] == healthcheck['retries']
+ assert remote_healthcheck['StartPeriod'] == healthcheck['start_period']
+
def test_recreate_preserves_volume_with_trailing_slash(self):
"""When the Compose file specifies a trailing slash in the container path, make
sure we copy the volume over when recreating.
@@ -194,6 +313,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']
@@ -223,13 +343,16 @@ class ServiceTest(DockerClientTestCase):
image='busybox:latest',
command=["top"],
labels={LABEL_PROJECT: 'composetest'},
+ host_config={},
+ environment=['affinity:container=={}'.format(volume_container_1.id)],
)
host_service = self.create_service(
'host',
volumes_from=[
VolumeFromSpec(volume_service, 'rw', 'service'),
VolumeFromSpec(volume_container_2, 'rw', 'container')
- ]
+ ],
+ environment=['affinity:container=={}'.format(volume_container_1.id)],
)
host_container = host_service.create_container()
host_service.start_container(host_container)
@@ -266,9 +389,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)
@@ -295,8 +424,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
@@ -409,18 +543,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')
@@ -432,6 +569,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)])
@@ -448,6 +586,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')])
@@ -464,6 +603,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',
@@ -482,6 +622,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')
@@ -491,6 +632,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')
@@ -517,7 +659,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')
@@ -544,7 +686,10 @@ class ServiceTest(DockerClientTestCase):
with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
f.write("FROM busybox\n")
- self.create_service('web', build={'context': base_dir}).build()
+ service = self.create_service('web', build={'context': base_dir})
+ service.build()
+ self.addCleanup(self.client.remove_image, service.image_name)
+
assert self.client.inspect_image('composetest_web')
def test_build_non_ascii_filename(self):
@@ -557,7 +702,9 @@ class ServiceTest(DockerClientTestCase):
with open(os.path.join(base_dir.encode('utf8'), b'foo\xE2bar'), 'w') as f:
f.write("hello world\n")
- self.create_service('web', build={'context': text_type(base_dir)}).build()
+ service = self.create_service('web', build={'context': text_type(base_dir)})
+ service.build()
+ self.addCleanup(self.client.remove_image, service.image_name)
assert self.client.inspect_image('composetest_web')
def test_build_with_image_name(self):
@@ -586,19 +733,107 @@ class ServiceTest(DockerClientTestCase):
with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
f.write("FROM busybox\n")
f.write("ARG build_version\n")
+ f.write("RUN echo ${build_version}\n")
service = self.create_service('buildwithargs',
build={'context': text_type(base_dir),
'args': {"build_version": "1"}})
service.build()
+ self.addCleanup(self.client.remove_image, service.image_name)
+ assert service.image()
+ assert "build_version=1" in service.image()['ContainerConfig']['Cmd']
+
+ def test_build_with_build_args_override(self):
+ base_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base_dir)
+
+ with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
+ f.write("FROM busybox\n")
+ f.write("ARG build_version\n")
+ f.write("RUN echo ${build_version}\n")
+
+ service = self.create_service('buildwithargs',
+ build={'context': text_type(base_dir),
+ 'args': {"build_version": "1"}})
+ service.build(build_args_override={'build_version': '2'})
+ self.addCleanup(self.client.remove_image, service.image_name)
+
+ assert service.image()
+ assert "build_version=2" in service.image()['ContainerConfig']['Cmd']
+
+ def test_build_with_build_labels(self):
+ base_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base_dir)
+
+ with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
+ f.write('FROM busybox\n')
+
+ service = self.create_service('buildlabels', build={
+ 'context': text_type(base_dir),
+ 'labels': {'com.docker.compose.test': 'true'}
+ })
+ service.build()
+ self.addCleanup(self.client.remove_image, service.image_name)
+
+ assert service.image()
+ assert service.image()['Config']['Labels']['com.docker.compose.test'] == 'true'
+
+ @no_cluster('Container networks not on Swarm')
+ def test_build_with_network(self):
+ base_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base_dir)
+ with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
+ f.write('FROM busybox\n')
+ f.write('RUN ping -c1 google.local\n')
+
+ net_container = self.client.create_container(
+ 'busybox', 'top', host_config=self.client.create_host_config(
+ extra_hosts={'google.local': '127.0.0.1'}
+ ), name='composetest_build_network'
+ )
+
+ self.addCleanup(self.client.remove_container, net_container, force=True)
+ self.client.start(net_container)
+
+ service = self.create_service('buildwithnet', build={
+ 'context': text_type(base_dir),
+ 'network': 'container:{}'.format(net_container['Id'])
+ })
+
+ service.build()
+ self.addCleanup(self.client.remove_image, service.image_name)
+
+ assert service.image()
+
+ @v2_3_only()
+ @no_cluster('Not supported on UCP 2.2.0-beta1') # FIXME: remove once support is added
+ def test_build_with_target(self):
+ self.require_api_version('1.30')
+ base_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base_dir)
+
+ with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
+ f.write('FROM busybox as one\n')
+ f.write('LABEL com.docker.compose.test=true\n')
+ f.write('LABEL com.docker.compose.test.target=one\n')
+ f.write('FROM busybox as two\n')
+ f.write('LABEL com.docker.compose.test.target=two\n')
+
+ service = self.create_service('buildtarget', build={
+ 'context': text_type(base_dir),
+ 'target': 'one'
+ })
+
+ service.build()
assert service.image()
+ assert service.image()['Config']['Labels']['com.docker.compose.test.target'] == 'one'
- def test_start_container_stays_unpriviliged(self):
+ def test_start_container_stays_unprivileged(self):
service = self.create_service('web')
container = create_and_start_container(service).inspect()
self.assertEqual(container['HostConfig']['Privileged'], False)
- def test_start_container_becomes_priviliged(self):
+ def test_start_container_becomes_privileged(self):
service = self.create_service('web', privileged=True)
container = create_and_start_container(service).inspect()
self.assertEqual(container['HostConfig']['Privileged'], True)
@@ -631,20 +866,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
@@ -672,6 +914,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
@@ -732,15 +978,15 @@ class ServiceTest(DockerClientTestCase):
message="testing",
response={},
explanation="Boom")):
-
with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
- service.scale(3)
+ with pytest.raises(OperationFailedError):
+ service.scale(3)
- self.assertEqual(len(service.containers()), 1)
- self.assertTrue(service.containers()[0].is_running)
- self.assertIn(
- "ERROR: for composetest_web_2 Cannot create container for service web: Boom",
- mock_stderr.getvalue()
+ assert len(service.containers()) == 1
+ assert service.containers()[0].is_running
+ assert (
+ "ERROR: for composetest_web_2 Cannot create container for service"
+ " web: Boom" in mock_stderr.getvalue()
)
def test_scale_with_unexpected_exception(self):
@@ -792,7 +1038,8 @@ class ServiceTest(DockerClientTestCase):
service = self.create_service('app', container_name='custom-container')
self.assertEqual(service.custom_container_name, 'custom-container')
- service.scale(3)
+ with pytest.raises(OperationFailedError):
+ service.scale(3)
captured_output = mock_log.warn.call_args[0][0]
@@ -833,15 +1080,27 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(container.get('HostConfig.NetworkMode'), 'host')
def test_pid_mode_none_defined(self):
- service = self.create_service('web', pid=None)
+ service = self.create_service('web', pid_mode=None)
container = create_and_start_container(service)
self.assertEqual(container.get('HostConfig.PidMode'), '')
def test_pid_mode_host(self):
- service = self.create_service('web', pid='host')
+ service = self.create_service('web', pid_mode=PidMode('host'))
container = create_and_start_container(service)
self.assertEqual(container.get('HostConfig.PidMode'), 'host')
+ @v2_1_only()
+ def test_userns_mode_none_defined(self):
+ service = self.create_service('web', userns_mode=None)
+ container = create_and_start_container(service)
+ self.assertEqual(container.get('HostConfig.UsernsMode'), '')
+
+ @v2_1_only()
+ def test_userns_mode_host(self):
+ service = self.create_service('web', userns_mode='host')
+ container = create_and_start_container(service)
+ self.assertEqual(container.get('HostConfig.UsernsMode'), 'host')
+
def test_dns_no_value(self):
service = self.create_service('web')
container = create_and_start_container(service)
@@ -852,11 +1111,42 @@ class ServiceTest(DockerClientTestCase):
container = create_and_start_container(service)
self.assertEqual(container.get('HostConfig.Dns'), ['8.8.8.8', '9.9.9.9'])
+ def test_mem_swappiness(self):
+ service = self.create_service('web', mem_swappiness=11)
+ container = create_and_start_container(service)
+ self.assertEqual(container.get('HostConfig.MemorySwappiness'), 11)
+
+ def test_mem_reservation(self):
+ service = self.create_service('web', mem_reservation='20m')
+ container = create_and_start_container(service)
+ assert container.get('HostConfig.MemoryReservation') == 20 * 1024 * 1024
+
def test_restart_always_value(self):
service = self.create_service('web', restart={'Name': 'always'})
container = create_and_start_container(service)
self.assertEqual(container.get('HostConfig.RestartPolicy.Name'), 'always')
+ def test_oom_score_adj_value(self):
+ service = self.create_service('web', oom_score_adj=500)
+ container = create_and_start_container(service)
+ self.assertEqual(container.get('HostConfig.OomScoreAdj'), 500)
+
+ def test_group_add_value(self):
+ service = self.create_service('web', group_add=["root", "1"])
+ container = create_and_start_container(service)
+
+ host_container_groupadd = container.get('HostConfig.GroupAdd')
+ assert "root" in host_container_groupadd
+ assert "1" in host_container_groupadd
+
+ def test_dns_opt_value(self):
+ service = self.create_service('web', dns_opt=["use-vc", "no-tld-query"])
+ container = create_and_start_container(service)
+
+ dns_opt = container.get('HostConfig.DnsOptions')
+ assert 'use-vc' in dns_opt
+ assert 'no-tld-query' in dns_opt
+
def test_restart_on_failure_value(self):
service = self.create_service('web', restart={
'Name': 'on-failure',
@@ -915,6 +1205,22 @@ class ServiceTest(DockerClientTestCase):
}.items():
self.assertEqual(env[k], v)
+ @v3_only()
+ def test_build_with_cachefrom(self):
+ base_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base_dir)
+
+ with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
+ f.write("FROM busybox\n")
+
+ service = self.create_service('cache_from',
+ build={'context': base_dir,
+ 'cache_from': ['build1']})
+ service.build()
+ self.addCleanup(self.client.remove_image, service.image_name)
+
+ assert service.image()
+
@mock.patch.dict(os.environ)
def test_resolve_env(self):
os.environ['FILE_DEF'] = 'E1'
@@ -943,7 +1249,7 @@ class ServiceTest(DockerClientTestCase):
with mock.patch.object(self.client, '_version', '1.20'):
service = self.create_service('web')
service_config = service._get_container_host_config({})
- self.assertEquals(service_config['NetworkMode'], 'default')
+ self.assertEqual(service_config['NetworkMode'], 'default')
def test_labels(self):
labels_dict = {
@@ -989,7 +1295,7 @@ class ServiceTest(DockerClientTestCase):
one_off_container = service.create_container(one_off=True)
self.assertNotEqual(one_off_container.name, 'my-web-container')
- @pytest.mark.skipif(True, reason="Broken on 1.11.0rc1")
+ @pytest.mark.skipif(True, reason="Broken on 1.11.0 - 17.03.0")
def test_log_drive_invalid(self):
service = self.create_service('web', logging={'driver': 'xxx'})
expected_error_msg = "logger: no log driver named 'xxx' is registered"
@@ -1047,6 +1353,7 @@ def converge(service, strategy=ConvergenceStrategy.changed):
class ConfigHashTest(DockerClientTestCase):
+
def test_no_config_hash_when_one_off(self):
web = self.create_service('web')
container = web.create_container(one_off=True)
diff --git a/tests/integration/state_test.py b/tests/integration/state_test.py
index 07b28e78..047dc704 100644
--- a/tests/integration/state_test.py
+++ b/tests/integration/state_test.py
@@ -6,9 +6,11 @@ from __future__ import absolute_import
from __future__ import unicode_literals
import py
+from docker.errors import ImageNotFound
from .testcases import DockerClientTestCase
from .testcases import get_links
+from .testcases import no_cluster
from compose.config import config
from compose.project import Project
from compose.service import ConvergenceStrategy
@@ -243,21 +245,34 @@ class ServiceStateTest(DockerClientTestCase):
tag = 'latest'
image = '{}:{}'.format(repo, tag)
+ def safe_remove_image(image):
+ try:
+ self.client.remove_image(image)
+ except ImageNotFound:
+ pass
+
image_id = self.client.images(name='busybox')[0]['Id']
self.client.tag(image_id, repository=repo, tag=tag)
- self.addCleanup(self.client.remove_image, image)
+ self.addCleanup(safe_remove_image, image)
web = self.create_service('web', image=image)
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={})
+
+ # In the case of a cluster, there's a chance we pick up the old image when
+ # calculating the new hash. To circumvent that, untag the old image first
+ # See also: https://github.com/moby/moby/issues/26852
+ self.client.remove_image(image, force=True)
+
self.client.commit(c, repository=repo, tag=tag)
self.client.remove_container(c)
web = self.create_service('web', image=image)
self.assertEqual(('recreate', [container]), web.convergence_plan())
+ @no_cluster('Can not guarantee the build will be run on the same node the service is deployed')
def test_trigger_recreate_with_build(self):
context = py.test.ensuretemp('test_trigger_recreate_with_build')
self.addCleanup(context.remove)
diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py
index 8d69d531..b72fb53a 100644
--- a/tests/integration/testcases.py
+++ b/tests/integration/testcases.py
@@ -4,20 +4,32 @@ from __future__ import unicode_literals
import functools
import os
+import pytest
+from docker.errors import APIError
from docker.utils import version_lt
-from pytest import skip
from .. import unittest
from compose.cli.docker_client import docker_client
from compose.config.config import resolve_environment
-from compose.config.config import V1
-from compose.config.config import V2_0
from compose.config.environment import Environment
from compose.const import API_VERSIONS
+from compose.const import COMPOSEFILE_V1 as V1
+from compose.const import COMPOSEFILE_V2_0 as V2_0
+from compose.const import COMPOSEFILE_V2_0 as V2_1
+from compose.const import COMPOSEFILE_V2_2 as V2_2
+from compose.const import COMPOSEFILE_V2_3 as V2_3
+from compose.const import COMPOSEFILE_V3_0 as V3_0
+from compose.const import COMPOSEFILE_V3_2 as V3_2
+from compose.const import COMPOSEFILE_V3_3 as V3_3
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'
+SWARM_ASSUME_MULTINODE = os.environ.get('SWARM_ASSUME_MULTINODE', '0') != '0'
+
def pull_busybox(client):
client.pull('busybox:latest', stream=False)
@@ -33,36 +45,58 @@ def get_links(container):
return [format_link(link) for link in links]
-def engine_version_too_low_for_v2():
+def engine_max_version():
if 'DOCKER_VERSION' not in os.environ:
- return False
+ return V3_3
version = os.environ['DOCKER_VERSION'].partition('-')[0]
- return version_lt(version, '1.10')
+ if version_lt(version, '1.10'):
+ return V1
+ if version_lt(version, '1.12'):
+ return V2_0
+ if version_lt(version, '1.13'):
+ return V2_1
+ if version_lt(version, '17.06'):
+ return V3_2
+ return V3_3
+
+
+def min_version_skip(version):
+ return pytest.mark.skipif(
+ engine_max_version() < version,
+ reason="Engine version %s is too low" % version
+ )
def v2_only():
- def decorator(f):
- @functools.wraps(f)
- def wrapper(self, *args, **kwargs):
- if engine_version_too_low_for_v2():
- skip("Engine version is too low")
- return
- return f(self, *args, **kwargs)
- return wrapper
+ return min_version_skip(V2_0)
- return decorator
+
+def v2_1_only():
+ return min_version_skip(V2_1)
+
+
+def v2_2_only():
+ return min_version_skip(V2_2)
+
+
+def v2_3_only():
+ return min_version_skip(V2_3)
+
+
+def v3_only():
+ return min_version_skip(V3_0)
class DockerClientTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
- if engine_version_too_low_for_v2():
- version = API_VERSIONS[V1]
- else:
- version = API_VERSIONS[V2_0]
-
+ version = API_VERSIONS[engine_max_version()]
cls.client = docker_client(Environment(), version)
+ @classmethod
+ def tearDownClass(cls):
+ del cls.client
+
def tearDown(self):
for c in self.client.containers(
all=True,
@@ -71,7 +105,11 @@ class DockerClientTestCase(unittest.TestCase):
for i in self.client.images(
filters={'label': 'com.docker.compose.test_image'}):
- self.client.remove_image(i)
+ try:
+ self.client.remove_image(i, force=True)
+ except APIError as e:
+ if e.is_server_error():
+ pass
volumes = self.client.volumes().get('Volumes') or []
for v in volumes:
@@ -106,4 +144,44 @@ class DockerClientTestCase(unittest.TestCase):
def require_api_version(self, minimum):
api_version = self.client.version()['ApiVersion']
if version_lt(api_version, minimum):
- skip("API version is too low ({} < {})".format(api_version, minimum))
+ pytest.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'])
+
+
+def is_cluster(client):
+ if SWARM_ASSUME_MULTINODE:
+ return True
+
+ def get_nodes_number():
+ try:
+ return len(client.nodes())
+ except APIError:
+ # If the Engine is not part of a Swarm, the SDK will raise
+ # an APIError
+ return 0
+
+ if not hasattr(is_cluster, 'nodes') or is_cluster.nodes is None:
+ # Only make the API call if the value hasn't been cached yet
+ is_cluster.nodes = get_nodes_number()
+
+ return is_cluster.nodes > 1
+
+
+def no_cluster(reason):
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(self, *args, **kwargs):
+ if is_cluster(self.client):
+ pytest.skip("Test will not be run in cluster mode: %s" % reason)
+ return
+ return f(self, *args, **kwargs)
+ return wrapper
+
+ return decorator
diff --git a/tests/integration/volume_test.py b/tests/integration/volume_test.py
index 706179ed..2a521d4c 100644
--- a/tests/integration/volume_test.py
+++ b/tests/integration/volume_test.py
@@ -1,9 +1,13 @@
from __future__ import absolute_import
from __future__ import unicode_literals
+import six
from docker.errors import DockerException
from .testcases import DockerClientTestCase
+from .testcases import no_cluster
+from compose.const import LABEL_PROJECT
+from compose.const import LABEL_VOLUME
from compose.volume import Volume
@@ -17,13 +21,18 @@ class VolumeTest(DockerClientTestCase):
self.client.remove_volume(volume.full_name)
except DockerException:
pass
+ del self.tmp_volumes
+ super(VolumeTest, self).tearDown()
+
+ def create_volume(self, name, driver=None, opts=None, external=None, custom_name=False):
+ if external:
+ custom_name = True
+ if isinstance(external, six.text_type):
+ name = external
- def create_volume(self, name, driver=None, opts=None, external=None):
- if external and isinstance(external, bool):
- external = name
vol = Volume(
self.client, 'composetest', name, driver=driver, driver_opts=opts,
- external_name=external
+ external=bool(external), custom_name=custom_name
)
self.tmp_volumes.append(vol)
return vol
@@ -31,26 +40,35 @@ 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_create_volume_custom_name(self):
+ vol = self.create_volume('volume01', custom_name=True)
+ assert vol.name == vol.full_name
+ vol.create()
+ info = self.get_volume_data(vol.full_name)
+ assert info['Name'].split('/')[-1] == vol.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()
@@ -58,6 +76,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
@@ -66,6 +85,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)
@@ -75,20 +95,32 @@ 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()
+ vol_data = vol.inspect()
+ labels = vol_data['Labels']
+ assert labels[LABEL_VOLUME] == vol.name
+ assert labels[LABEL_PROJECT] == vol.project