Replace sequential container indexes with randomly generated IDs

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2018-08-13 19:29:29 -07:00
parent bd8b2dfbbc
commit 4e2de3c1ff
11 changed files with 230 additions and 174 deletions

View File

@ -474,15 +474,16 @@ class TopLevelCommand(object):
-u, --user USER Run the command as this user. -u, --user USER Run the command as this user.
-T Disable pseudo-tty allocation. By default `docker-compose exec` -T Disable pseudo-tty allocation. By default `docker-compose exec`
allocates a TTY. allocates a TTY.
--index=index index of the container if there are multiple --index=index "index" of the container if there are multiple
instances of a service [default: 1] instances of a service. If missing, Compose will pick an
arbitrary container.
-e, --env KEY=VAL Set environment variables (can be used multiple times, -e, --env KEY=VAL Set environment variables (can be used multiple times,
not supported in API < 1.25) not supported in API < 1.25)
-w, --workdir DIR Path to workdir directory for this command. -w, --workdir DIR Path to workdir directory for this command.
""" """
environment = Environment.from_env_file(self.project_dir) environment = Environment.from_env_file(self.project_dir)
use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI')
index = int(options.get('--index')) index = options.get('--index')
service = self.project.get_service(options['SERVICE']) service = self.project.get_service(options['SERVICE'])
detach = options.get('--detach') detach = options.get('--detach')
@ -659,10 +660,11 @@ class TopLevelCommand(object):
Options: Options:
--protocol=proto tcp or udp [default: tcp] --protocol=proto tcp or udp [default: tcp]
--index=index index of the container if there are multiple --index=index "index" of the container if there are multiple
instances of a service [default: 1] instances of a service. If missing, Compose will pick an
arbitrary container.
""" """
index = int(options.get('--index')) index = options.get('--index')
service = self.project.get_service(options['SERVICE']) service = self.project.get_service(options['SERVICE'])
try: try:
container = service.get_container(number=index) container = service.get_container(number=index)

View File

@ -10,6 +10,7 @@ from .const import LABEL_CONTAINER_NUMBER
from .const import LABEL_PROJECT from .const import LABEL_PROJECT
from .const import LABEL_SERVICE from .const import LABEL_SERVICE
from .const import LABEL_VERSION from .const import LABEL_VERSION
from .utils import truncate_id
from .version import ComposeVersion from .version import ComposeVersion
@ -80,7 +81,7 @@ class Container(object):
@property @property
def name_without_project(self): def name_without_project(self):
if self.name.startswith('{0}_{1}'.format(self.project, self.service)): if self.name.startswith('{0}_{1}'.format(self.project, self.service)):
return '{0}_{1}'.format(self.service, self.number) return '{0}_{1}'.format(self.service, self.short_number)
else: else:
return self.name return self.name
@ -90,7 +91,11 @@ class Container(object):
if not number: if not number:
raise ValueError("Container {0} does not have a {1} label".format( raise ValueError("Container {0} does not have a {1} label".format(
self.short_id, LABEL_CONTAINER_NUMBER)) self.short_id, LABEL_CONTAINER_NUMBER))
return int(number) return number
@property
def short_number(self):
return truncate_id(self.number)
@property @property
def ports(self): def ports(self):

View File

@ -1,7 +1,6 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import itertools
import logging import logging
import os import os
import re import re
@ -49,9 +48,11 @@ from .errors import OperationFailedError
from .parallel import parallel_execute from .parallel import parallel_execute
from .progress_stream import stream_output from .progress_stream import stream_output
from .progress_stream import StreamOutputError from .progress_stream import StreamOutputError
from .utils import generate_random_id
from .utils import json_hash from .utils import json_hash
from .utils import parse_bytes from .utils import parse_bytes
from .utils import parse_seconds_float from .utils import parse_seconds_float
from .utils import truncate_id
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -215,13 +216,17 @@ class Service(object):
) )
) )
def get_container(self, number=1): def get_container(self, number=None):
"""Return a :class:`compose.container.Container` for this service. The """Return a :class:`compose.container.Container` for this service. The
container must be active, and match `number`. container must be active, and match `number`.
""" """
if number is not None and len(number) == 64:
for container in self.containers(labels=['{0}={1}'.format(LABEL_CONTAINER_NUMBER, number)]): for container in self.containers(labels=['{0}={1}'.format(LABEL_CONTAINER_NUMBER, number)]):
return container return container
else:
for container in self.containers():
if number is None or container.number.startswith(number):
return container
raise ValueError("No container found for %s_%s" % (self.name, number)) raise ValueError("No container found for %s_%s" % (self.name, number))
@ -426,7 +431,6 @@ class Service(object):
return has_diverged return has_diverged
def _execute_convergence_create(self, scale, detached, start, project_services=None): def _execute_convergence_create(self, scale, detached, start, project_services=None):
i = self._next_container_number()
def create_and_start(service, n): def create_and_start(service, n):
container = service.create_container(number=n, quiet=True) container = service.create_container(number=n, quiet=True)
@ -437,7 +441,9 @@ class Service(object):
return container return container
containers, errors = parallel_execute( containers, errors = parallel_execute(
[ServiceName(self.project, self.name, index) for index in range(i, i + scale)], [ServiceName(self.project, self.name, number) for number in [
self._next_container_number() for _ in range(scale)
]],
lambda service_name: create_and_start(self, service_name.number), lambda service_name: create_and_start(self, service_name.number),
lambda service_name: self.get_container_name(service_name.service, service_name.number), lambda service_name: self.get_container_name(service_name.service, service_name.number),
"Creating" "Creating"
@ -568,7 +574,7 @@ class Service(object):
container.rename_to_tmp_name() container.rename_to_tmp_name()
new_container = self.create_container( new_container = self.create_container(
previous_container=container if not renew_anonymous_volumes else None, previous_container=container if not renew_anonymous_volumes else None,
number=container.labels.get(LABEL_CONTAINER_NUMBER), number=container.number,
quiet=True, quiet=True,
) )
if attach_logs: if attach_logs:
@ -723,20 +729,8 @@ class Service(object):
def get_volumes_from_names(self): def get_volumes_from_names(self):
return [s.source.name for s in self.volumes_from if isinstance(s.source, Service)] return [s.source.name for s in self.volumes_from if isinstance(s.source, Service)]
# TODO: this would benefit from github.com/docker/docker/pull/14699
# to remove the need to inspect every container
def _next_container_number(self, one_off=False): def _next_container_number(self, one_off=False):
containers = itertools.chain( return generate_random_id()
self._fetch_containers(
all=True,
filters={'label': self.labels(one_off=one_off)}
), self._fetch_containers(
all=True,
filters={'label': self.labels(one_off=one_off, legacy=True)}
)
)
numbers = [c.number for c in containers]
return 1 if not numbers else max(numbers) + 1
def _fetch_containers(self, **fetch_options): def _fetch_containers(self, **fetch_options):
# Account for containers that might have been removed since we fetched # Account for containers that might have been removed since we fetched
@ -1377,7 +1371,7 @@ def build_container_name(project, service, number, one_off=False):
bits = [project.lstrip('-_'), service] bits = [project.lstrip('-_'), service]
if one_off: if one_off:
bits.append('run') bits.append('run')
return '_'.join(bits + [str(number)]) return '_'.join(bits + [truncate_id(number)])
# Images # Images

View File

@ -7,6 +7,7 @@ import json
import json.decoder import json.decoder
import logging import logging
import ntpath import ntpath
import random
import six import six
from docker.errors import DockerException from docker.errors import DockerException
@ -151,3 +152,21 @@ def unquote_path(s):
if s[0] == '"' and s[-1] == '"': if s[0] == '"' and s[-1] == '"':
return s[1:-1] return s[1:-1]
return s return s
def generate_random_id():
while True:
val = hex(random.getrandbits(32 * 8))[2:-1]
try:
int(truncate_id(val))
continue
except ValueError:
return val
def truncate_id(value):
if ':' in value:
value = value[value.index(':') + 1:]
if len(value) > 12:
return value[:12]
return value

View File

@ -99,7 +99,14 @@ class ContainerStateCondition(object):
def __call__(self): def __call__(self):
try: try:
container = self.client.inspect_container(self.name) if self.name.endswith('*'):
ctnrs = self.client.containers(all=True, filters={'name': self.name[:-1]})
if len(ctnrs) > 0:
container = self.client.inspect_container(ctnrs[0]['Id'])
else:
return False
else:
container = self.client.inspect_container(self.name)
return container['State']['Status'] == self.status return container['State']['Status'] == self.status
except errors.APIError: except errors.APIError:
return False return False
@ -540,16 +547,16 @@ class CLITestCase(DockerClientTestCase):
def test_ps(self): def test_ps(self):
self.project.get_service('simple').create_container() self.project.get_service('simple').create_container()
result = self.dispatch(['ps']) result = self.dispatch(['ps'])
assert 'simple-composefile_simple_1' in result.stdout assert 'simple-composefile_simple_' in result.stdout
def test_ps_default_composefile(self): def test_ps_default_composefile(self):
self.base_dir = 'tests/fixtures/multiple-composefiles' self.base_dir = 'tests/fixtures/multiple-composefiles'
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])
result = self.dispatch(['ps']) result = self.dispatch(['ps'])
assert 'multiple-composefiles_simple_1' in result.stdout assert 'multiple-composefiles_simple_' in result.stdout
assert 'multiple-composefiles_another_1' in result.stdout assert 'multiple-composefiles_another_' in result.stdout
assert 'multiple-composefiles_yetanother_1' not in result.stdout assert 'multiple-composefiles_yetanother_' not in result.stdout
def test_ps_alternate_composefile(self): def test_ps_alternate_composefile(self):
config_path = os.path.abspath( config_path = os.path.abspath(
@ -560,9 +567,9 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['-f', 'compose2.yml', 'up', '-d']) self.dispatch(['-f', 'compose2.yml', 'up', '-d'])
result = self.dispatch(['-f', 'compose2.yml', 'ps']) result = self.dispatch(['-f', 'compose2.yml', 'ps'])
assert 'multiple-composefiles_simple_1' not in result.stdout assert 'multiple-composefiles_simple_' not in result.stdout
assert 'multiple-composefiles_another_1' not in result.stdout assert 'multiple-composefiles_another_' not in result.stdout
assert 'multiple-composefiles_yetanother_1' in result.stdout assert 'multiple-composefiles_yetanother_' in result.stdout
def test_ps_services_filter_option(self): def test_ps_services_filter_option(self):
self.base_dir = 'tests/fixtures/ps-services-filter' self.base_dir = 'tests/fixtures/ps-services-filter'
@ -956,13 +963,13 @@ class CLITestCase(DockerClientTestCase):
assert len(self.project.containers(one_off=OneOffFilter.only, stopped=True)) == 2 assert len(self.project.containers(one_off=OneOffFilter.only, stopped=True)) == 2
result = self.dispatch(['down', '--rmi=local', '--volumes']) result = self.dispatch(['down', '--rmi=local', '--volumes'])
assert 'Stopping v2-full_web_1' in result.stderr assert 'Stopping v2-full_web_' in result.stderr
assert 'Stopping v2-full_other_1' in result.stderr assert 'Stopping v2-full_other_' in result.stderr
assert 'Stopping v2-full_web_run_2' in result.stderr assert 'Stopping v2-full_web_run_' in result.stderr
assert 'Removing v2-full_web_1' in result.stderr assert 'Removing v2-full_web_' in result.stderr
assert 'Removing v2-full_other_1' in result.stderr assert 'Removing v2-full_other_' in result.stderr
assert 'Removing v2-full_web_run_1' in result.stderr assert 'Removing v2-full_web_run_' in result.stderr
assert 'Removing v2-full_web_run_2' in result.stderr assert 'Removing v2-full_web_run_' in result.stderr
assert 'Removing volume v2-full_data' in result.stderr assert 'Removing volume v2-full_data' in result.stderr
assert 'Removing image v2-full_web' in result.stderr assert 'Removing image v2-full_web' in result.stderr
assert 'Removing image busybox' not in result.stderr assert 'Removing image busybox' not in result.stderr
@ -1019,11 +1026,13 @@ class CLITestCase(DockerClientTestCase):
def test_up_attached(self): def test_up_attached(self):
self.base_dir = 'tests/fixtures/echo-services' self.base_dir = 'tests/fixtures/echo-services'
result = self.dispatch(['up', '--no-color']) result = self.dispatch(['up', '--no-color'])
simple_num = self.project.get_service('simple').containers(stopped=True)[0].short_number
another_num = self.project.get_service('another').containers(stopped=True)[0].short_number
assert 'simple_1 | simple' in result.stdout assert 'simple_{} | simple'.format(simple_num) in result.stdout
assert 'another_1 | another' in result.stdout assert 'another_{} | another'.format(another_num) in result.stdout
assert 'simple_1 exited with code 0' in result.stdout assert 'simple_{} exited with code 0'.format(simple_num) in result.stdout
assert 'another_1 exited with code 0' in result.stdout assert 'another_{} exited with code 0'.format(another_num) in result.stdout
@v2_only() @v2_only()
def test_up(self): def test_up(self):
@ -1727,11 +1736,12 @@ class CLITestCase(DockerClientTestCase):
def test_run_rm(self): def test_run_rm(self):
self.base_dir = 'tests/fixtures/volume' self.base_dir = 'tests/fixtures/volume'
proc = start_process(self.base_dir, ['run', '--rm', 'test']) proc = start_process(self.base_dir, ['run', '--rm', 'test'])
service = self.project.get_service('test')
wait_on_condition(ContainerStateCondition( wait_on_condition(ContainerStateCondition(
self.project.client, self.project.client,
'volume_test_run_1', 'volume_test_run_*',
'running')) 'running')
service = self.project.get_service('test') )
containers = service.containers(one_off=OneOffFilter.only) containers = service.containers(one_off=OneOffFilter.only)
assert len(containers) == 1 assert len(containers) == 1
mounts = containers[0].get('Mounts') mounts = containers[0].get('Mounts')
@ -2054,39 +2064,39 @@ class CLITestCase(DockerClientTestCase):
proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top']) proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top'])
wait_on_condition(ContainerStateCondition( wait_on_condition(ContainerStateCondition(
self.project.client, self.project.client,
'simple-composefile_simple_run_1', 'simple-composefile_simple_run_*',
'running')) 'running'))
os.kill(proc.pid, signal.SIGINT) os.kill(proc.pid, signal.SIGINT)
wait_on_condition(ContainerStateCondition( wait_on_condition(ContainerStateCondition(
self.project.client, self.project.client,
'simple-composefile_simple_run_1', 'simple-composefile_simple_run_*',
'exited')) 'exited'))
def test_run_handles_sigterm(self): def test_run_handles_sigterm(self):
proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top']) proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top'])
wait_on_condition(ContainerStateCondition( wait_on_condition(ContainerStateCondition(
self.project.client, self.project.client,
'simple-composefile_simple_run_1', 'simple-composefile_simple_run_*',
'running')) 'running'))
os.kill(proc.pid, signal.SIGTERM) os.kill(proc.pid, signal.SIGTERM)
wait_on_condition(ContainerStateCondition( wait_on_condition(ContainerStateCondition(
self.project.client, self.project.client,
'simple-composefile_simple_run_1', 'simple-composefile_simple_run_*',
'exited')) 'exited'))
def test_run_handles_sighup(self): def test_run_handles_sighup(self):
proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top']) proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top'])
wait_on_condition(ContainerStateCondition( wait_on_condition(ContainerStateCondition(
self.project.client, self.project.client,
'simple-composefile_simple_run_1', 'simple-composefile_simple_run_*',
'running')) 'running'))
os.kill(proc.pid, signal.SIGHUP) os.kill(proc.pid, signal.SIGHUP)
wait_on_condition(ContainerStateCondition( wait_on_condition(ContainerStateCondition(
self.project.client, self.project.client,
'simple-composefile_simple_run_1', 'simple-composefile_simple_run_*',
'exited')) 'exited'))
@mock.patch.dict(os.environ) @mock.patch.dict(os.environ)
@ -2286,34 +2296,44 @@ class CLITestCase(DockerClientTestCase):
proc = start_process(self.base_dir, ['logs', '-f']) proc = start_process(self.base_dir, ['logs', '-f'])
self.dispatch(['up', '-d', 'another']) self.dispatch(['up', '-d', 'another'])
wait_on_condition(ContainerStateCondition( another_num = self.project.get_service('another').get_container().short_number
self.project.client, wait_on_condition(
'logs-composefile_another_1', ContainerStateCondition(
'exited')) self.project.client,
'logs-composefile_another_{}'.format(another_num),
'exited'
)
)
simple_num = self.project.get_service('simple').get_container().short_number
self.dispatch(['kill', 'simple']) self.dispatch(['kill', 'simple'])
result = wait_on_process(proc) result = wait_on_process(proc)
assert 'hello' in result.stdout assert 'hello' in result.stdout
assert 'test' in result.stdout assert 'test' in result.stdout
assert 'logs-composefile_another_1 exited with code 0' in result.stdout assert 'logs-composefile_another_{} exited with code 0'.format(another_num) in result.stdout
assert 'logs-composefile_simple_1 exited with code 137' in result.stdout assert 'logs-composefile_simple_{} exited with code 137'.format(simple_num) in result.stdout
def test_logs_follow_logs_from_restarted_containers(self): def test_logs_follow_logs_from_restarted_containers(self):
self.base_dir = 'tests/fixtures/logs-restart-composefile' self.base_dir = 'tests/fixtures/logs-restart-composefile'
proc = start_process(self.base_dir, ['up']) proc = start_process(self.base_dir, ['up'])
wait_on_condition(ContainerStateCondition( wait_on_condition(
self.project.client, ContainerStateCondition(
'logs-restart-composefile_another_1', self.project.client,
'exited')) 'logs-restart-composefile_another_*',
'exited'
)
)
self.dispatch(['kill', 'simple']) self.dispatch(['kill', 'simple'])
result = wait_on_process(proc) result = wait_on_process(proc)
assert result.stdout.count('logs-restart-composefile_another_1 exited with code 1') == 3 assert len(re.findall(
r'logs-restart-composefile_another_[a-f0-9]{12} exited with code 1',
result.stdout
)) == 3
assert result.stdout.count('world') == 3 assert result.stdout.count('world') == 3
def test_logs_default(self): def test_logs_default(self):
@ -2346,10 +2366,10 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['up']) self.dispatch(['up'])
result = self.dispatch(['logs', '--tail', '2']) result = self.dispatch(['logs', '--tail', '2'])
assert 'c\n' in result.stdout assert 'y\n' in result.stdout
assert 'd\n' in result.stdout assert 'z\n' in result.stdout
assert 'a\n' not in result.stdout assert 'w\n' not in result.stdout
assert 'b\n' not in result.stdout assert 'x\n' not in result.stdout
def test_kill(self): def test_kill(self):
self.dispatch(['up', '-d'], None) self.dispatch(['up', '-d'], None)
@ -2523,9 +2543,9 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['port', '--index=' + str(index), 'simple', str(number)]) result = self.dispatch(['port', '--index=' + str(index), 'simple', str(number)])
return result.stdout.rstrip() return result.stdout.rstrip()
assert get_port(3000) == containers[0].get_local_port(3000) assert get_port(3000) in (containers[0].get_local_port(3000), containers[1].get_local_port(3000))
assert get_port(3000, index=1) == containers[0].get_local_port(3000) assert get_port(3000, index=containers[0].number) == containers[0].get_local_port(3000)
assert get_port(3000, index=2) == containers[1].get_local_port(3000) assert get_port(3000, index=containers[1].number) == containers[1].get_local_port(3000)
assert get_port(3002) == "" assert get_port(3002) == ""
def test_events_json(self): def test_events_json(self):
@ -2561,7 +2581,7 @@ class CLITestCase(DockerClientTestCase):
container, = self.project.containers() container, = self.project.containers()
expected_template = ' container {} {}' expected_template = ' container {} {}'
expected_meta_info = ['image=busybox:latest', 'name=simple-composefile_simple_1'] expected_meta_info = ['image=busybox:latest', 'name=simple-composefile_simple_']
assert expected_template.format('create', container.id) in lines[0] assert expected_template.format('create', container.id) in lines[0]
assert expected_template.format('start', container.id) in lines[1] assert expected_template.format('start', container.id) in lines[1]
@ -2643,8 +2663,11 @@ class CLITestCase(DockerClientTestCase):
assert len(containers) == 2 assert len(containers) == 2
web = containers[1] web = containers[1]
db_num = containers[0].short_number
assert set(get_links(web)) == set(['db', 'mydb_1', 'extends_mydb_1']) assert set(get_links(web)) == set(
['db', 'mydb_{}'.format(db_num), 'extends_mydb_{}'.format(db_num)]
)
expected_env = set([ expected_env = set([
"FOO=1", "FOO=1",
@ -2677,11 +2700,11 @@ class CLITestCase(DockerClientTestCase):
self.base_dir = 'tests/fixtures/exit-code-from' self.base_dir = 'tests/fixtures/exit-code-from'
proc = start_process( proc = start_process(
self.base_dir, self.base_dir,
['up', '--abort-on-container-exit', '--exit-code-from', 'another']) ['up', '--abort-on-container-exit', '--exit-code-from', 'another']
)
result = wait_on_process(proc, returncode=1) result = wait_on_process(proc, returncode=1)
assert re.findall(r'exit-code-from_another_[a-f0-9]{12} exited with code 1', result.stdout)
assert 'exit-code-from_another_1 exited with code 1' in result.stdout
def test_exit_code_from_signal_stop(self): def test_exit_code_from_signal_stop(self):
self.base_dir = 'tests/fixtures/exit-code-from' self.base_dir = 'tests/fixtures/exit-code-from'
@ -2690,13 +2713,14 @@ class CLITestCase(DockerClientTestCase):
['up', '--abort-on-container-exit', '--exit-code-from', 'simple'] ['up', '--abort-on-container-exit', '--exit-code-from', 'simple']
) )
result = wait_on_process(proc, returncode=137) # SIGKILL result = wait_on_process(proc, returncode=137) # SIGKILL
assert 'exit-code-from_another_1 exited with code 1' in result.stdout num = self.project.get_service('another').containers(stopped=True)[0].short_number
assert 'exit-code-from_another_{} exited with code 1'.format(num) in result.stdout
def test_images(self): def test_images(self):
self.project.get_service('simple').create_container() self.project.get_service('simple').create_container()
result = self.dispatch(['images']) result = self.dispatch(['images'])
assert 'busybox' in result.stdout assert 'busybox' in result.stdout
assert 'simple-composefile_simple_1' in result.stdout assert 'simple-composefile_simple_' in result.stdout
def test_images_default_composefile(self): def test_images_default_composefile(self):
self.base_dir = 'tests/fixtures/multiple-composefiles' self.base_dir = 'tests/fixtures/multiple-composefiles'
@ -2704,8 +2728,8 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['images']) result = self.dispatch(['images'])
assert 'busybox' in result.stdout assert 'busybox' in result.stdout
assert 'multiple-composefiles_another_1' in result.stdout assert 'multiple-composefiles_another_' in result.stdout
assert 'multiple-composefiles_simple_1' in result.stdout assert 'multiple-composefiles_simple_' in result.stdout
@mock.patch.dict(os.environ) @mock.patch.dict(os.environ)
def test_images_tagless_image(self): def test_images_tagless_image(self):
@ -2725,7 +2749,7 @@ class CLITestCase(DockerClientTestCase):
self.project.get_service('foo').create_container() self.project.get_service('foo').create_container()
result = self.dispatch(['images']) result = self.dispatch(['images'])
assert '<none>' in result.stdout assert '<none>' in result.stdout
assert 'tagless-image_foo_1' in result.stdout assert 'tagless-image_foo_' in result.stdout
def test_up_with_override_yaml(self): def test_up_with_override_yaml(self):
self.base_dir = 'tests/fixtures/override-yaml-files' self.base_dir = 'tests/fixtures/override-yaml-files'

View File

@ -1,3 +1,3 @@
simple: simple:
image: busybox:latest image: busybox:latest
command: sh -c "echo a && echo b && echo c && echo d" command: sh -c "echo w && echo x && echo y && echo z"

View File

@ -90,7 +90,8 @@ class ProjectTest(DockerClientTestCase):
project.up() project.up()
containers = project.containers(['web']) containers = project.containers(['web'])
assert [c.name for c in containers] == ['composetest_web_1'] assert len(containers) == 1
assert containers[0].name.startswith('composetest_web_')
def test_containers_with_extra_service(self): def test_containers_with_extra_service(self):
web = self.create_service('web') web = self.create_service('web')
@ -464,14 +465,14 @@ class ProjectTest(DockerClientTestCase):
project.up(['db']) project.up(['db'])
assert len(project.containers()) == 1 assert len(project.containers()) == 1
old_db_id = project.containers()[0].id
container, = project.containers() container, = project.containers()
old_db_id = container.id
db_volume_path = container.get_mount('/var/db')['Source'] db_volume_path = container.get_mount('/var/db')['Source']
project.up(strategy=ConvergenceStrategy.never) project.up(strategy=ConvergenceStrategy.never)
assert len(project.containers()) == 2 assert len(project.containers()) == 2
db_container = [c for c in project.containers() if 'db' in c.name][0] db_container = [c for c in project.containers() if c.name == container.name][0]
assert db_container.id == old_db_id assert db_container.id == old_db_id
assert db_container.get_mount('/var/db')['Source'] == db_volume_path assert db_container.get_mount('/var/db')['Source'] == db_volume_path
@ -1944,7 +1945,7 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers(stopped=True) containers = project.containers(stopped=True)
assert len(containers) == 1 assert len(containers) == 1
assert containers[0].name == 'underscoretest_svc1_1' assert containers[0].name.startswith('underscoretest_svc1_')
assert containers[0].project == '_underscoretest' assert containers[0].project == '_underscoretest'
full_vol_name = 'underscoretest_foo' full_vol_name = 'underscoretest_foo'
@ -1965,7 +1966,7 @@ class ProjectTest(DockerClientTestCase):
containers = project2.containers(stopped=True) containers = project2.containers(stopped=True)
assert len(containers) == 1 assert len(containers) == 1
assert containers[0].name == 'dashtest_svc1_1' assert containers[0].name.startswith('dashtest_svc1_')
assert containers[0].project == '-dashtest' assert containers[0].project == '-dashtest'
full_vol_name = 'dashtest_foo' full_vol_name = 'dashtest_foo'

View File

@ -67,7 +67,7 @@ class ServiceTest(DockerClientTestCase):
create_and_start_container(foo) create_and_start_container(foo)
assert len(foo.containers()) == 1 assert len(foo.containers()) == 1
assert foo.containers()[0].name == 'composetest_foo_1' assert foo.containers()[0].name.startswith('composetest_foo_')
assert len(bar.containers()) == 0 assert len(bar.containers()) == 0
create_and_start_container(bar) create_and_start_container(bar)
@ -77,8 +77,8 @@ class ServiceTest(DockerClientTestCase):
assert len(bar.containers()) == 2 assert len(bar.containers()) == 2
names = [c.name for c in bar.containers()] names = [c.name for c in bar.containers()]
assert 'composetest_bar_1' in names assert len(names) == 2
assert 'composetest_bar_2' in names assert all(name.startswith('composetest_bar_') for name in names)
def test_containers_one_off(self): def test_containers_one_off(self):
db = self.create_service('db') db = self.create_service('db')
@ -89,18 +89,18 @@ class ServiceTest(DockerClientTestCase):
def test_project_is_added_to_container_name(self): def test_project_is_added_to_container_name(self):
service = self.create_service('web') service = self.create_service('web')
create_and_start_container(service) create_and_start_container(service)
assert service.containers()[0].name == 'composetest_web_1' assert service.containers()[0].name.startswith('composetest_web_')
def test_create_container_with_one_off(self): def test_create_container_with_one_off(self):
db = self.create_service('db') db = self.create_service('db')
container = db.create_container(one_off=True) container = db.create_container(one_off=True)
assert container.name == 'composetest_db_run_1' assert container.name.startswith('composetest_db_run_')
def test_create_container_with_one_off_when_existing_container_is_running(self): def test_create_container_with_one_off_when_existing_container_is_running(self):
db = self.create_service('db') db = self.create_service('db')
db.start() db.start()
container = db.create_container(one_off=True) container = db.create_container(one_off=True)
assert container.name == 'composetest_db_run_1' assert container.name.startswith('composetest_db_run_')
def test_create_container_with_unspecified_volume(self): def test_create_container_with_unspecified_volume(self):
service = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')]) service = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
@ -489,7 +489,7 @@ class ServiceTest(DockerClientTestCase):
assert old_container.get('Config.Entrypoint') == ['top'] assert old_container.get('Config.Entrypoint') == ['top']
assert old_container.get('Config.Cmd') == ['-d', '1'] assert old_container.get('Config.Cmd') == ['-d', '1']
assert 'FOO=1' in old_container.get('Config.Env') assert 'FOO=1' in old_container.get('Config.Env')
assert old_container.name == 'composetest_db_1' assert old_container.name.startswith('composetest_db_')
service.start_container(old_container) service.start_container(old_container)
old_container.inspect() # reload volume data old_container.inspect() # reload volume data
volume_path = old_container.get_mount('/etc')['Source'] volume_path = old_container.get_mount('/etc')['Source']
@ -503,7 +503,7 @@ class ServiceTest(DockerClientTestCase):
assert new_container.get('Config.Entrypoint') == ['top'] assert new_container.get('Config.Entrypoint') == ['top']
assert new_container.get('Config.Cmd') == ['-d', '1'] assert new_container.get('Config.Cmd') == ['-d', '1']
assert 'FOO=2' in new_container.get('Config.Env') assert 'FOO=2' in new_container.get('Config.Env')
assert new_container.name == 'composetest_db_1' assert new_container.name.startswith('composetest_db_')
assert new_container.get_mount('/etc')['Source'] == volume_path assert new_container.get_mount('/etc')['Source'] == volume_path
if not is_cluster(self.client): if not is_cluster(self.client):
assert ( assert (
@ -836,13 +836,13 @@ class ServiceTest(DockerClientTestCase):
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', links=[(db, None)]) web = self.create_service('web', links=[(db, None)])
create_and_start_container(db) db1 = create_and_start_container(db)
create_and_start_container(db) db2 = create_and_start_container(db)
create_and_start_container(web) create_and_start_container(web)
assert set(get_links(web.containers()[0])) == set([ assert set(get_links(web.containers()[0])) == set([
'composetest_db_1', 'db_1', db1.name, db1.name_without_project,
'composetest_db_2', 'db_2', db2.name, db2.name_without_project,
'db' 'db'
]) ])
@ -851,30 +851,33 @@ class ServiceTest(DockerClientTestCase):
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', links=[(db, 'custom_link_name')]) web = self.create_service('web', links=[(db, 'custom_link_name')])
create_and_start_container(db) db1 = create_and_start_container(db)
create_and_start_container(db) db2 = create_and_start_container(db)
create_and_start_container(web) create_and_start_container(web)
assert set(get_links(web.containers()[0])) == set([ assert set(get_links(web.containers()[0])) == set([
'composetest_db_1', 'db_1', db1.name, db1.name_without_project,
'composetest_db_2', 'db_2', db2.name, db2.name_without_project,
'custom_link_name' 'custom_link_name'
]) ])
@no_cluster('No legacy links support in Swarm') @no_cluster('No legacy links support in Swarm')
def test_start_container_with_external_links(self): def test_start_container_with_external_links(self):
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', external_links=['composetest_db_1', db_ctnrs = [create_and_start_container(db) for _ in range(3)]
'composetest_db_2', web = self.create_service(
'composetest_db_3:db_3']) 'web', external_links=[
'composetest_db_{}'.format(db_ctnrs[0].short_number),
'composetest_db_{}'.format(db_ctnrs[1].short_number),
'composetest_db_{}:db_3'.format(db_ctnrs[2].short_number)
]
)
for _ in range(3):
create_and_start_container(db)
create_and_start_container(web) create_and_start_container(web)
assert set(get_links(web.containers()[0])) == set([ assert set(get_links(web.containers()[0])) == set([
'composetest_db_1', 'composetest_db_{}'.format(db_ctnrs[0].short_number),
'composetest_db_2', 'composetest_db_{}'.format(db_ctnrs[1].short_number),
'db_3' 'db_3'
]) ])
@ -892,14 +895,14 @@ class ServiceTest(DockerClientTestCase):
def test_start_one_off_container_creates_links_to_its_own_service(self): def test_start_one_off_container_creates_links_to_its_own_service(self):
db = self.create_service('db') db = self.create_service('db')
create_and_start_container(db) db1 = create_and_start_container(db)
create_and_start_container(db) db2 = create_and_start_container(db)
c = create_and_start_container(db, one_off=OneOffFilter.only) c = create_and_start_container(db, one_off=OneOffFilter.only)
assert set(get_links(c)) == set([ assert set(get_links(c)) == set([
'composetest_db_1', 'db_1', db1.name, db1.name_without_project,
'composetest_db_2', 'db_2', db2.name, db2.name_without_project,
'db' 'db'
]) ])
@ -1249,10 +1252,9 @@ class ServiceTest(DockerClientTestCase):
test that those containers are restarted and not removed/recreated. test that those containers are restarted and not removed/recreated.
""" """
service = self.create_service('web') service = self.create_service('web')
next_number = service._next_container_number() valid_numbers = [service._next_container_number(), service._next_container_number()]
valid_numbers = [next_number, next_number + 1] service.create_container(number=valid_numbers[0])
service.create_container(number=next_number) service.create_container(number=valid_numbers[1])
service.create_container(number=next_number + 1)
ParallelStreamWriter.instance = None ParallelStreamWriter.instance = None
with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr: with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
@ -1310,10 +1312,8 @@ class ServiceTest(DockerClientTestCase):
assert len(service.containers()) == 1 assert len(service.containers()) == 1
assert service.containers()[0].is_running assert service.containers()[0].is_running
assert ( assert "ERROR: for composetest_web_" in mock_stderr.getvalue()
"ERROR: for composetest_web_2 Cannot create container for service" assert "Cannot create container for service web: Boom" in mock_stderr.getvalue()
" web: Boom" in mock_stderr.getvalue()
)
def test_scale_with_unexpected_exception(self): def test_scale_with_unexpected_exception(self):
"""Test that when scaling if the API returns an error, that is not of type """Test that when scaling if the API returns an error, that is not of type
@ -1580,7 +1580,6 @@ class ServiceTest(DockerClientTestCase):
} }
compose_labels = { compose_labels = {
LABEL_CONTAINER_NUMBER: '1',
LABEL_ONE_OFF: 'False', LABEL_ONE_OFF: 'False',
LABEL_PROJECT: 'composetest', LABEL_PROJECT: 'composetest',
LABEL_SERVICE: 'web', LABEL_SERVICE: 'web',
@ -1589,9 +1588,11 @@ class ServiceTest(DockerClientTestCase):
expected = dict(labels_dict, **compose_labels) expected = dict(labels_dict, **compose_labels)
service = self.create_service('web', labels=labels_dict) service = self.create_service('web', labels=labels_dict)
labels = create_and_start_container(service).labels.items() ctnr = create_and_start_container(service)
labels = ctnr.labels.items()
for pair in expected.items(): for pair in expected.items():
assert pair in labels assert pair in labels
assert ctnr.labels[LABEL_CONTAINER_NUMBER] == ctnr.number
def test_empty_labels(self): def test_empty_labels(self):
labels_dict = {'foo': '', 'bar': ''} labels_dict = {'foo': '', 'bar': ''}
@ -1655,7 +1656,7 @@ class ServiceTest(DockerClientTestCase):
def test_duplicate_containers(self): def test_duplicate_containers(self):
service = self.create_service('web') service = self.create_service('web')
options = service._get_container_create_options({}, 1) options = service._get_container_create_options({}, service._next_container_number())
original = Container.create(service.client, **options) original = Container.create(service.client, **options)
assert set(service.containers(stopped=True)) == set([original]) assert set(service.containers(stopped=True)) == set([original])

View File

@ -55,8 +55,8 @@ class BasicProjectTest(ProjectTestCase):
def test_partial_change(self): def test_partial_change(self):
old_containers = self.run_up(self.cfg) old_containers = self.run_up(self.cfg)
old_db = [c for c in old_containers if c.name_without_project == 'db_1'][0] old_db = [c for c in old_containers if c.name_without_project.startswith('db_')][0]
old_web = [c for c in old_containers if c.name_without_project == 'web_1'][0] old_web = [c for c in old_containers if c.name_without_project.startswith('web_')][0]
self.cfg['web']['command'] = '/bin/true' self.cfg['web']['command'] = '/bin/true'
@ -71,7 +71,7 @@ class BasicProjectTest(ProjectTestCase):
created = list(new_containers - old_containers) created = list(new_containers - old_containers)
assert len(created) == 1 assert len(created) == 1
assert created[0].name_without_project == 'web_1' assert created[0].name_without_project == old_web.name_without_project
assert created[0].get('Config.Cmd') == ['/bin/true'] assert created[0].get('Config.Cmd') == ['/bin/true']
def test_all_change(self): def test_all_change(self):
@ -114,7 +114,7 @@ class ProjectWithDependenciesTest(ProjectTestCase):
def test_up(self): def test_up(self):
containers = self.run_up(self.cfg) containers = self.run_up(self.cfg)
assert set(c.name_without_project for c in containers) == set(['db_1', 'web_1', 'nginx_1']) assert set(c.service for c in containers) == set(['db', 'web', 'nginx'])
def test_change_leaf(self): def test_change_leaf(self):
old_containers = self.run_up(self.cfg) old_containers = self.run_up(self.cfg)
@ -122,7 +122,7 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.cfg['nginx']['environment'] = {'NEW_VAR': '1'} self.cfg['nginx']['environment'] = {'NEW_VAR': '1'}
new_containers = self.run_up(self.cfg) new_containers = self.run_up(self.cfg)
assert set(c.name_without_project for c in new_containers - old_containers) == set(['nginx_1']) assert set(c.service for c in new_containers - old_containers) == set(['nginx'])
def test_change_middle(self): def test_change_middle(self):
old_containers = self.run_up(self.cfg) old_containers = self.run_up(self.cfg)
@ -130,7 +130,7 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.cfg['web']['environment'] = {'NEW_VAR': '1'} self.cfg['web']['environment'] = {'NEW_VAR': '1'}
new_containers = self.run_up(self.cfg) new_containers = self.run_up(self.cfg)
assert set(c.name_without_project for c in new_containers - old_containers) == set(['web_1']) assert set(c.service for c in new_containers - old_containers) == set(['web'])
def test_change_middle_always_recreate_deps(self): def test_change_middle_always_recreate_deps(self):
old_containers = self.run_up(self.cfg, always_recreate_deps=True) old_containers = self.run_up(self.cfg, always_recreate_deps=True)
@ -138,8 +138,7 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.cfg['web']['environment'] = {'NEW_VAR': '1'} self.cfg['web']['environment'] = {'NEW_VAR': '1'}
new_containers = self.run_up(self.cfg, always_recreate_deps=True) new_containers = self.run_up(self.cfg, always_recreate_deps=True)
assert set(c.name_without_project assert set(c.service for c in new_containers - old_containers) == {'web', 'nginx'}
for c in new_containers - old_containers) == {'web_1', 'nginx_1'}
def test_change_root(self): def test_change_root(self):
old_containers = self.run_up(self.cfg) old_containers = self.run_up(self.cfg)
@ -147,7 +146,7 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.cfg['db']['environment'] = {'NEW_VAR': '1'} self.cfg['db']['environment'] = {'NEW_VAR': '1'}
new_containers = self.run_up(self.cfg) new_containers = self.run_up(self.cfg)
assert set(c.name_without_project for c in new_containers - old_containers) == set(['db_1']) assert set(c.service for c in new_containers - old_containers) == set(['db'])
def test_change_root_always_recreate_deps(self): def test_change_root_always_recreate_deps(self):
old_containers = self.run_up(self.cfg, always_recreate_deps=True) old_containers = self.run_up(self.cfg, always_recreate_deps=True)
@ -155,8 +154,9 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.cfg['db']['environment'] = {'NEW_VAR': '1'} self.cfg['db']['environment'] = {'NEW_VAR': '1'}
new_containers = self.run_up(self.cfg, always_recreate_deps=True) new_containers = self.run_up(self.cfg, always_recreate_deps=True)
assert set(c.name_without_project assert set(c.service for c in new_containers - old_containers) == {
for c in new_containers - old_containers) == {'db_1', 'web_1', 'nginx_1'} 'db', 'web', 'nginx'
}
def test_change_root_no_recreate(self): def test_change_root_no_recreate(self):
old_containers = self.run_up(self.cfg) old_containers = self.run_up(self.cfg)
@ -195,9 +195,18 @@ class ProjectWithDependenciesTest(ProjectTestCase):
web, = [c for c in containers if c.service == 'web'] web, = [c for c in containers if c.service == 'web']
nginx, = [c for c in containers if c.service == 'nginx'] nginx, = [c for c in containers if c.service == 'nginx']
db, = [c for c in containers if c.service == 'db']
assert set(get_links(web)) == {'composetest_db_1', 'db', 'db_1'} assert set(get_links(web)) == {
assert set(get_links(nginx)) == {'composetest_web_1', 'web', 'web_1'} 'composetest_db_{}'.format(db.short_number),
'db',
'db_{}'.format(db.short_number)
}
assert set(get_links(nginx)) == {
'composetest_web_{}'.format(web.short_number),
'web',
'web_{}'.format(web.short_number)
}
class ServiceStateTest(DockerClientTestCase): class ServiceStateTest(DockerClientTestCase):

View File

@ -30,7 +30,7 @@ class ContainerTest(unittest.TestCase):
"Labels": { "Labels": {
"com.docker.compose.project": "composetest", "com.docker.compose.project": "composetest",
"com.docker.compose.service": "web", "com.docker.compose.service": "web",
"com.docker.compose.container-number": 7, "com.docker.compose.container-number": "092cd63296fdc446ad432d3905dd1fcbe12a2ba6b52",
}, },
} }
} }
@ -77,7 +77,7 @@ class ContainerTest(unittest.TestCase):
def test_number(self): def test_number(self):
container = Container(None, self.container_dict, has_been_inspected=True) container = Container(None, self.container_dict, has_been_inspected=True)
assert container.number == 7 assert container.number == "092cd63296fdc446ad432d3905dd1fcbe12a2ba6b52"
def test_name(self): def test_name(self):
container = Container.from_ps(None, container = Container.from_ps(None,
@ -88,7 +88,7 @@ class ContainerTest(unittest.TestCase):
def test_name_without_project(self): def test_name_without_project(self):
self.container_dict['Name'] = "/composetest_web_7" self.container_dict['Name'] = "/composetest_web_7"
container = Container(None, self.container_dict, has_been_inspected=True) container = Container(None, self.container_dict, has_been_inspected=True)
assert container.name_without_project == "web_7" assert container.name_without_project == "web_092cd63296fd"
def test_name_without_project_custom_container_name(self): def test_name_without_project_custom_container_name(self):
self.container_dict['Name'] = "/custom_name_of_container" self.container_dict['Name'] = "/custom_name_of_container"

View File

@ -41,6 +41,7 @@ from compose.service import parse_repository_tag
from compose.service import Service from compose.service import Service
from compose.service import ServiceNetworkMode from compose.service import ServiceNetworkMode
from compose.service import warn_on_masked_volume from compose.service import warn_on_masked_volume
from compose.utils import generate_random_id as generate_id
class ServiceTest(unittest.TestCase): class ServiceTest(unittest.TestCase):
@ -81,8 +82,7 @@ class ServiceTest(unittest.TestCase):
service = Service('db', self.mock_client, 'myproject', image='foo') service = Service('db', self.mock_client, 'myproject', image='foo')
assert [c.id for c in service.containers()] == ['1'] assert [c.id for c in service.containers()] == ['1']
assert service._next_container_number() == 2 assert service.get_container().id == '1'
assert service.get_container(1).id == '1'
def test_get_volumes_from_container(self): def test_get_volumes_from_container(self):
container_id = 'aabbccddee' container_id = 'aabbccddee'
@ -164,7 +164,7 @@ class ServiceTest(unittest.TestCase):
client=self.mock_client, client=self.mock_client,
mem_limit=1000000000, mem_limit=1000000000,
memswap_limit=2000000000) memswap_limit=2000000000)
service._get_container_create_options({'some': 'overrides'}, 1) service._get_container_create_options({'some': 'overrides'}, generate_id())
assert self.mock_client.create_host_config.called assert self.mock_client.create_host_config.called
assert self.mock_client.create_host_config.call_args[1]['mem_limit'] == 1000000000 assert self.mock_client.create_host_config.call_args[1]['mem_limit'] == 1000000000
@ -173,10 +173,10 @@ class ServiceTest(unittest.TestCase):
def test_self_reference_external_link(self): def test_self_reference_external_link(self):
service = Service( service = Service(
name='foo', name='foo',
external_links=['default_foo_1'] external_links=['default_foo_bdfa3ed91e2c']
) )
with pytest.raises(DependencyError): with pytest.raises(DependencyError):
service.get_container_name('foo', 1) service.get_container_name('foo', 'bdfa3ed91e2c')
def test_mem_reservation(self): def test_mem_reservation(self):
self.mock_client.create_host_config.return_value = {} self.mock_client.create_host_config.return_value = {}
@ -188,7 +188,7 @@ class ServiceTest(unittest.TestCase):
client=self.mock_client, client=self.mock_client,
mem_reservation='512m' mem_reservation='512m'
) )
service._get_container_create_options({'some': 'overrides'}, 1) service._get_container_create_options({'some': 'overrides'}, generate_id())
assert self.mock_client.create_host_config.called is True assert self.mock_client.create_host_config.called is True
assert self.mock_client.create_host_config.call_args[1]['mem_reservation'] == '512m' assert self.mock_client.create_host_config.call_args[1]['mem_reservation'] == '512m'
@ -201,7 +201,7 @@ class ServiceTest(unittest.TestCase):
hostname='name', hostname='name',
client=self.mock_client, client=self.mock_client,
cgroup_parent='test') cgroup_parent='test')
service._get_container_create_options({'some': 'overrides'}, 1) service._get_container_create_options({'some': 'overrides'}, generate_id())
assert self.mock_client.create_host_config.called assert self.mock_client.create_host_config.called
assert self.mock_client.create_host_config.call_args[1]['cgroup_parent'] == 'test' assert self.mock_client.create_host_config.call_args[1]['cgroup_parent'] == 'test'
@ -218,7 +218,7 @@ class ServiceTest(unittest.TestCase):
client=self.mock_client, client=self.mock_client,
log_driver='syslog', log_driver='syslog',
logging=logging) logging=logging)
service._get_container_create_options({'some': 'overrides'}, 1) service._get_container_create_options({'some': 'overrides'}, generate_id())
assert self.mock_client.create_host_config.called assert self.mock_client.create_host_config.called
assert self.mock_client.create_host_config.call_args[1]['log_config'] == { assert self.mock_client.create_host_config.call_args[1]['log_config'] == {
@ -233,7 +233,7 @@ class ServiceTest(unittest.TestCase):
image='foo', image='foo',
client=self.mock_client, client=self.mock_client,
stop_grace_period="1m35s") stop_grace_period="1m35s")
opts = service._get_container_create_options({'image': 'foo'}, 1) opts = service._get_container_create_options({'image': 'foo'}, generate_id())
assert opts['stop_timeout'] == 95 assert opts['stop_timeout'] == 95
def test_split_domainname_none(self): def test_split_domainname_none(self):
@ -242,7 +242,7 @@ class ServiceTest(unittest.TestCase):
image='foo', image='foo',
hostname='name.domain.tld', hostname='name.domain.tld',
client=self.mock_client) client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, 1) opts = service._get_container_create_options({'image': 'foo'}, generate_id())
assert opts['hostname'] == 'name.domain.tld', 'hostname' assert opts['hostname'] == 'name.domain.tld', 'hostname'
assert not ('domainname' in opts), 'domainname' assert not ('domainname' in opts), 'domainname'
@ -253,7 +253,7 @@ class ServiceTest(unittest.TestCase):
hostname='name.domain.tld', hostname='name.domain.tld',
image='foo', image='foo',
client=self.mock_client) client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, 1) opts = service._get_container_create_options({'image': 'foo'}, generate_id())
assert opts['hostname'] == 'name', 'hostname' assert opts['hostname'] == 'name', 'hostname'
assert opts['domainname'] == 'domain.tld', 'domainname' assert opts['domainname'] == 'domain.tld', 'domainname'
@ -265,7 +265,7 @@ class ServiceTest(unittest.TestCase):
image='foo', image='foo',
domainname='domain.tld', domainname='domain.tld',
client=self.mock_client) client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, 1) opts = service._get_container_create_options({'image': 'foo'}, generate_id())
assert opts['hostname'] == 'name', 'hostname' assert opts['hostname'] == 'name', 'hostname'
assert opts['domainname'] == 'domain.tld', 'domainname' assert opts['domainname'] == 'domain.tld', 'domainname'
@ -277,7 +277,7 @@ class ServiceTest(unittest.TestCase):
domainname='domain.tld', domainname='domain.tld',
image='foo', image='foo',
client=self.mock_client) client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, 1) opts = service._get_container_create_options({'image': 'foo'}, generate_id())
assert opts['hostname'] == 'name.sub', 'hostname' assert opts['hostname'] == 'name.sub', 'hostname'
assert opts['domainname'] == 'domain.tld', 'domainname' assert opts['domainname'] == 'domain.tld', 'domainname'
@ -288,7 +288,7 @@ class ServiceTest(unittest.TestCase):
use_networking=False, use_networking=False,
client=self.mock_client, client=self.mock_client,
) )
opts = service._get_container_create_options({'image': 'foo'}, 1) opts = service._get_container_create_options({'image': 'foo'}, generate_id())
assert opts.get('hostname') is None assert opts.get('hostname') is None
def test_get_container_create_options_with_name_option(self): def test_get_container_create_options_with_name_option(self):
@ -321,9 +321,8 @@ class ServiceTest(unittest.TestCase):
prev_container.get.return_value = None prev_container.get.return_value = None
opts = service._get_container_create_options( opts = service._get_container_create_options(
{}, {}, generate_id(), previous_container=prev_container
1, )
previous_container=prev_container)
assert service.options['labels'] == labels assert service.options['labels'] == labels
assert service.options['environment'] == environment assert service.options['environment'] == environment
@ -358,7 +357,7 @@ class ServiceTest(unittest.TestCase):
opts = service._get_container_create_options( opts = service._get_container_create_options(
{}, {},
1, generate_id(),
previous_container=prev_container) previous_container=prev_container)
assert opts['environment'] == ['affinity:container==ababab'] assert opts['environment'] == ['affinity:container==ababab']
@ -373,7 +372,7 @@ class ServiceTest(unittest.TestCase):
opts = service._get_container_create_options( opts = service._get_container_create_options(
{}, {},
1, generate_id(),
previous_container=prev_container) previous_container=prev_container)
assert opts['environment'] == [] assert opts['environment'] == []
@ -386,11 +385,11 @@ class ServiceTest(unittest.TestCase):
@mock.patch('compose.service.Container', autospec=True) @mock.patch('compose.service.Container', autospec=True)
def test_get_container(self, mock_container_class): def test_get_container(self, mock_container_class):
container_dict = dict(Name='default_foo_2') container_dict = dict(Name='default_foo_bdfa3ed91e2c')
self.mock_client.containers.return_value = [container_dict] self.mock_client.containers.return_value = [container_dict]
service = Service('foo', image='foo', client=self.mock_client) service = Service('foo', image='foo', client=self.mock_client)
container = service.get_container(number=2) container = service.get_container(number="bdfa3ed91e2c")
assert container == mock_container_class.from_ps.return_value assert container == mock_container_class.from_ps.return_value
mock_container_class.from_ps.assert_called_once_with( mock_container_class.from_ps.assert_called_once_with(
self.mock_client, container_dict) self.mock_client, container_dict)
@ -463,6 +462,7 @@ class ServiceTest(unittest.TestCase):
@mock.patch('compose.service.Container', autospec=True) @mock.patch('compose.service.Container', autospec=True)
def test_recreate_container(self, _): def test_recreate_container(self, _):
mock_container = mock.create_autospec(Container) mock_container = mock.create_autospec(Container)
mock_container.number = generate_id()
service = Service('foo', client=self.mock_client, image='someimage') service = Service('foo', client=self.mock_client, image='someimage')
service.image = lambda: {'Id': 'abc123'} service.image = lambda: {'Id': 'abc123'}
new_container = service.recreate_container(mock_container) new_container = service.recreate_container(mock_container)
@ -476,6 +476,7 @@ class ServiceTest(unittest.TestCase):
@mock.patch('compose.service.Container', autospec=True) @mock.patch('compose.service.Container', autospec=True)
def test_recreate_container_with_timeout(self, _): def test_recreate_container_with_timeout(self, _):
mock_container = mock.create_autospec(Container) mock_container = mock.create_autospec(Container)
mock_container.number = generate_id()
self.mock_client.inspect_image.return_value = {'Id': 'abc123'} self.mock_client.inspect_image.return_value = {'Id': 'abc123'}
service = Service('foo', client=self.mock_client, image='someimage') service = Service('foo', client=self.mock_client, image='someimage')
service.recreate_container(mock_container, timeout=1) service.recreate_container(mock_container, timeout=1)
@ -711,9 +712,9 @@ class ServiceTest(unittest.TestCase):
for api_version in set(API_VERSIONS.values()): for api_version in set(API_VERSIONS.values()):
self.mock_client.api_version = api_version self.mock_client.api_version = api_version
assert service._get_container_create_options({}, 1)['labels'][LABEL_CONFIG_HASH] == ( assert service._get_container_create_options(
config_hash {}, generate_id()
) )['labels'][LABEL_CONFIG_HASH] == config_hash
def test_remove_image_none(self): def test_remove_image_none(self):
web = Service('web', image='example', client=self.mock_client) web = Service('web', image='example', client=self.mock_client)
@ -971,7 +972,7 @@ class ServiceTest(unittest.TestCase):
service = Service('foo', client=self.mock_client, environment=environment) service = Service('foo', client=self.mock_client, environment=environment)
create_opts = service._get_container_create_options(override_options, 1) create_opts = service._get_container_create_options(override_options, generate_id())
assert set(create_opts['environment']) == set(format_environment({ assert set(create_opts['environment']) == set(format_environment({
'HTTP_PROXY': default_proxy_config['httpProxy'], 'HTTP_PROXY': default_proxy_config['httpProxy'],
'http_proxy': default_proxy_config['httpProxy'], 'http_proxy': default_proxy_config['httpProxy'],
@ -1296,7 +1297,7 @@ class ServiceVolumesTest(unittest.TestCase):
service._get_container_create_options( service._get_container_create_options(
override_options={}, override_options={},
number=1, number=generate_id(),
) )
assert set(self.mock_client.create_host_config.call_args[1]['binds']) == set([ assert set(self.mock_client.create_host_config.call_args[1]['binds']) == set([
@ -1339,7 +1340,7 @@ class ServiceVolumesTest(unittest.TestCase):
service._get_container_create_options( service._get_container_create_options(
override_options={}, override_options={},
number=1, number=generate_id(),
previous_container=Container(self.mock_client, {'Id': '123123123'}), previous_container=Container(self.mock_client, {'Id': '123123123'}),
) )