Preserve container numbers, add slug to prevent name collisions

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2018-08-31 16:18:19 -07:00
parent 4e2de3c1ff
commit 5916639383
11 changed files with 157 additions and 148 deletions

View File

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

View File

@ -15,6 +15,7 @@ LABEL_PROJECT = 'com.docker.compose.project'
LABEL_SERVICE = 'com.docker.compose.service'
LABEL_NETWORK = 'com.docker.compose.network'
LABEL_VERSION = 'com.docker.compose.version'
LABEL_SLUG = 'com.docker.compose.slug'
LABEL_VOLUME = 'com.docker.compose.volume'
LABEL_CONFIG_HASH = 'com.docker.compose.config-hash'
NANOCPUS_SCALE = 1000000000

View File

@ -9,6 +9,7 @@ from docker.errors import ImageNotFound
from .const import LABEL_CONTAINER_NUMBER
from .const import LABEL_PROJECT
from .const import LABEL_SERVICE
from .const import LABEL_SLUG
from .const import LABEL_VERSION
from .utils import truncate_id
from .version import ComposeVersion
@ -81,7 +82,7 @@ class Container(object):
@property
def name_without_project(self):
if self.name.startswith('{0}_{1}'.format(self.project, self.service)):
return '{0}_{1}'.format(self.service, self.short_number)
return '{0}_{1}{2}'.format(self.service, self.number, '_' + self.slug if self.slug else '')
else:
return self.name
@ -91,11 +92,15 @@ class Container(object):
if not number:
raise ValueError("Container {0} does not have a {1} label".format(
self.short_id, LABEL_CONTAINER_NUMBER))
return number
return int(number)
@property
def short_number(self):
return truncate_id(self.number)
def slug(self):
return truncate_id(self.full_slug)
@property
def full_slug(self):
return self.labels.get(LABEL_SLUG)
@property
def ports(self):

View File

@ -31,7 +31,6 @@ from .service import ConvergenceStrategy
from .service import NetworkMode
from .service import PidMode
from .service import Service
from .service import ServiceName
from .service import ServiceNetworkMode
from .service import ServicePidMode
from .utils import microseconds_from_time_nano
@ -198,25 +197,6 @@ class Project(object):
service.remove_duplicate_containers()
return services
def get_scaled_services(self, services, scale_override):
"""
Returns a list of this project's services as scaled ServiceName objects.
services: a list of Service objects
scale_override: a dict with the scale to apply to each service (k: service_name, v: scale)
"""
service_names = []
for service in services:
if service.name in scale_override:
scale = scale_override[service.name]
else:
scale = service.scale_num
for i in range(1, scale + 1):
service_names.append(ServiceName(self.name, service.name, i))
return service_names
def get_links(self, service_dict):
links = []
if 'links' in service_dict:
@ -494,7 +474,6 @@ class Project(object):
svc.ensure_image_exists(do_build=do_build, silent=silent)
plans = self._get_convergence_plans(
services, strategy, always_recreate_deps=always_recreate_deps)
scaled_services = self.get_scaled_services(services, scale_override)
def do(service):
@ -505,7 +484,6 @@ class Project(object):
scale_override=scale_override.get(service.name),
rescale=rescale,
start=start,
project_services=scaled_services,
reset_container_image=reset_container_image,
renew_anonymous_volumes=renew_anonymous_volumes,
)

View File

@ -1,6 +1,7 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import itertools
import logging
import os
import re
@ -39,6 +40,7 @@ from .const import LABEL_CONTAINER_NUMBER
from .const import LABEL_ONE_OFF
from .const import LABEL_PROJECT
from .const import LABEL_SERVICE
from .const import LABEL_SLUG
from .const import LABEL_VERSION
from .const import NANOCPUS_SCALE
from .container import Container
@ -123,7 +125,7 @@ class NoSuchImageError(Exception):
pass
ServiceName = namedtuple('ServiceName', 'project service number')
ServiceName = namedtuple('ServiceName', 'project service number slug')
ConvergencePlan = namedtuple('ConvergencePlan', 'action containers')
@ -216,17 +218,12 @@ class Service(object):
)
)
def get_container(self, number=None):
def get_container(self, number=1):
"""Return a :class:`compose.container.Container` for this service. The
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)]):
return container
else:
for container in self.containers():
if number is None or container.number.startswith(number):
return container
for container in self.containers(labels=['{0}={1}'.format(LABEL_CONTAINER_NUMBER, number)]):
return container
raise ValueError("No container found for %s_%s" % (self.name, number))
@ -430,28 +427,33 @@ class Service(object):
return has_diverged
def _execute_convergence_create(self, scale, detached, start, project_services=None):
def _execute_convergence_create(self, scale, detached, start):
def create_and_start(service, n):
container = service.create_container(number=n, quiet=True)
if not detached:
container.attach_log_stream()
if start:
self.start_container(container)
return container
i = self._next_container_number()
containers, errors = parallel_execute(
[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: self.get_container_name(service_name.service, service_name.number),
"Creating"
)
for error in errors.values():
raise OperationFailedError(error)
def create_and_start(service, n):
container = service.create_container(number=n, quiet=True)
if not detached:
container.attach_log_stream()
if start:
self.start_container(container)
return container
return containers
containers, errors = parallel_execute(
[
ServiceName(self.project, self.name, index, generate_random_id())
for index in range(i, i + scale)
],
lambda service_name: create_and_start(self, service_name.number),
lambda service_name: self.get_container_name(
service_name.service, service_name.number, service_name.slug
),
"Creating"
)
for error in errors.values():
raise OperationFailedError(error)
return containers
def _execute_convergence_recreate(self, containers, scale, timeout, detached, start,
renew_anonymous_volumes):
@ -514,8 +516,8 @@ class Service(object):
def execute_convergence_plan(self, plan, timeout=None, detached=False,
start=True, scale_override=None,
rescale=True, project_services=None,
reset_container_image=False, renew_anonymous_volumes=False):
rescale=True, reset_container_image=False,
renew_anonymous_volumes=False):
(action, containers) = plan
scale = scale_override if scale_override is not None else self.scale_num
containers = sorted(containers, key=attrgetter('number'))
@ -524,7 +526,7 @@ class Service(object):
if action == 'create':
return self._execute_convergence_create(
scale, detached, start, project_services
scale, detached, start
)
# The create action needs always needs an initial scale, but otherwise,
@ -730,7 +732,17 @@ class Service(object):
return [s.source.name for s in self.volumes_from if isinstance(s.source, Service)]
def _next_container_number(self, one_off=False):
return generate_random_id()
containers = itertools.chain(
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):
# Account for containers that might have been removed since we fetched
@ -807,6 +819,7 @@ class Service(object):
one_off=False,
previous_container=None):
add_config_hash = (not one_off and not override_options)
slug = generate_random_id() if previous_container is None else previous_container.full_slug
container_options = dict(
(k, self.options[k])
@ -815,7 +828,7 @@ class Service(object):
container_options.update(override_options)
if not container_options.get('name'):
container_options['name'] = self.get_container_name(self.name, number, one_off)
container_options['name'] = self.get_container_name(self.name, number, slug, one_off)
container_options.setdefault('detach', True)
@ -867,7 +880,9 @@ class Service(object):
container_options.get('labels', {}),
self.labels(one_off=one_off),
number,
self.config_hash if add_config_hash else None)
self.config_hash if add_config_hash else None,
slug
)
# Delete options which are only used in HostConfig
for key in HOST_CONFIG_KEYS:
@ -1105,12 +1120,12 @@ class Service(object):
def custom_container_name(self):
return self.options.get('container_name')
def get_container_name(self, service_name, number, one_off=False):
def get_container_name(self, service_name, number, slug, one_off=False):
if self.custom_container_name and not one_off:
return self.custom_container_name
container_name = build_container_name(
self.project, service_name, number, one_off,
self.project, service_name, number, slug, one_off,
)
ext_links_origins = [l.split(':')[0] for l in self.options.get('external_links', [])]
if container_name in ext_links_origins:
@ -1367,11 +1382,13 @@ class ServiceNetworkMode(object):
# Names
def build_container_name(project, service, number, one_off=False):
def build_container_name(project, service, number, slug, one_off=False):
bits = [project.lstrip('-_'), service]
if one_off:
bits.append('run')
return '_'.join(bits + [truncate_id(number)])
return '_'.join(
bits + ([str(number), truncate_id(slug)] if slug else [str(number)])
)
# Images
@ -1552,10 +1569,11 @@ def build_mount(mount_spec):
# Labels
def build_container_labels(label_options, service_labels, number, config_hash):
def build_container_labels(label_options, service_labels, number, config_hash, slug):
labels = dict(label_options or {})
labels.update(label.split('=', 1) for label in service_labels)
labels[LABEL_CONTAINER_NUMBER] = str(number)
labels[LABEL_SLUG] = slug
labels[LABEL_VERSION] = __version__
if config_hash:

View File

@ -50,7 +50,6 @@ class Version(namedtuple('_Version', 'major minor patch stage edition')):
stage = None
elif '-' in stage:
edition, stage = stage.split('-')
major, minor, patch = version.split('.', 3)
return cls(major, minor, patch, stage, edition)

View File

@ -547,16 +547,16 @@ class CLITestCase(DockerClientTestCase):
def test_ps(self):
self.project.get_service('simple').create_container()
result = self.dispatch(['ps'])
assert 'simple-composefile_simple_' in result.stdout
assert 'simple-composefile_simple_1' in result.stdout
def test_ps_default_composefile(self):
self.base_dir = 'tests/fixtures/multiple-composefiles'
self.dispatch(['up', '-d'])
result = self.dispatch(['ps'])
assert 'multiple-composefiles_simple_' in result.stdout
assert 'multiple-composefiles_another_' in result.stdout
assert 'multiple-composefiles_yetanother_' not in result.stdout
assert 'multiple-composefiles_simple_1' in result.stdout
assert 'multiple-composefiles_another_1' in result.stdout
assert 'multiple-composefiles_yetanother_1' not in result.stdout
def test_ps_alternate_composefile(self):
config_path = os.path.abspath(
@ -567,9 +567,9 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['-f', 'compose2.yml', 'up', '-d'])
result = self.dispatch(['-f', 'compose2.yml', 'ps'])
assert 'multiple-composefiles_simple_' not in result.stdout
assert 'multiple-composefiles_another_' not in result.stdout
assert 'multiple-composefiles_yetanother_' in result.stdout
assert 'multiple-composefiles_simple_1' not in result.stdout
assert 'multiple-composefiles_another_1' not in result.stdout
assert 'multiple-composefiles_yetanother_1' in result.stdout
def test_ps_services_filter_option(self):
self.base_dir = 'tests/fixtures/ps-services-filter'
@ -963,13 +963,13 @@ class CLITestCase(DockerClientTestCase):
assert len(self.project.containers(one_off=OneOffFilter.only, stopped=True)) == 2
result = self.dispatch(['down', '--rmi=local', '--volumes'])
assert 'Stopping v2-full_web_' in result.stderr
assert 'Stopping v2-full_other_' in result.stderr
assert 'Stopping v2-full_web_run_' in result.stderr
assert 'Removing v2-full_web_' in result.stderr
assert 'Removing v2-full_other_' in result.stderr
assert 'Removing v2-full_web_run_' in result.stderr
assert 'Removing v2-full_web_run_' in result.stderr
assert 'Stopping v2-full_web_1' in result.stderr
assert 'Stopping v2-full_other_1' in result.stderr
assert 'Stopping v2-full_web_run_2' in result.stderr
assert 'Removing v2-full_web_1' in result.stderr
assert 'Removing v2-full_other_1' in result.stderr
assert 'Removing v2-full_web_run_1' in result.stderr
assert 'Removing v2-full_web_run_2' in result.stderr
assert 'Removing volume v2-full_data' in result.stderr
assert 'Removing image v2-full_web' in result.stderr
assert 'Removing image busybox' not in result.stderr
@ -1026,13 +1026,15 @@ class CLITestCase(DockerClientTestCase):
def test_up_attached(self):
self.base_dir = 'tests/fixtures/echo-services'
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
simple_name = self.project.get_service('simple').containers(stopped=True)[0].name_without_project
another_name = self.project.get_service('another').containers(
stopped=True
)[0].name_without_project
assert 'simple_{} | simple'.format(simple_num) in result.stdout
assert 'another_{} | another'.format(another_num) in result.stdout
assert 'simple_{} exited with code 0'.format(simple_num) in result.stdout
assert 'another_{} exited with code 0'.format(another_num) in result.stdout
assert '{} | simple'.format(simple_name) in result.stdout
assert '{} | another'.format(another_name) in result.stdout
assert '{} exited with code 0'.format(simple_name) in result.stdout
assert '{} exited with code 0'.format(another_name) in result.stdout
@v2_only()
def test_up(self):
@ -2296,24 +2298,24 @@ class CLITestCase(DockerClientTestCase):
proc = start_process(self.base_dir, ['logs', '-f'])
self.dispatch(['up', '-d', 'another'])
another_num = self.project.get_service('another').get_container().short_number
another_name = self.project.get_service('another').get_container().name_without_project
wait_on_condition(
ContainerStateCondition(
self.project.client,
'logs-composefile_another_{}'.format(another_num),
'logs-composefile_another_*',
'exited'
)
)
simple_num = self.project.get_service('simple').get_container().short_number
simple_name = self.project.get_service('simple').get_container().name_without_project
self.dispatch(['kill', 'simple'])
result = wait_on_process(proc)
assert 'hello' in result.stdout
assert 'test' in result.stdout
assert 'logs-composefile_another_{} exited with code 0'.format(another_num) in result.stdout
assert 'logs-composefile_simple_{} exited with code 137'.format(simple_num) in result.stdout
assert '{} exited with code 0'.format(another_name) in result.stdout
assert '{} exited with code 137'.format(simple_name) in result.stdout
def test_logs_follow_logs_from_restarted_containers(self):
self.base_dir = 'tests/fixtures/logs-restart-composefile'
@ -2331,7 +2333,7 @@ class CLITestCase(DockerClientTestCase):
result = wait_on_process(proc)
assert len(re.findall(
r'logs-restart-composefile_another_[a-f0-9]{12} exited with code 1',
r'logs-restart-composefile_another_1_[a-f0-9]{12} exited with code 1',
result.stdout
)) == 3
assert result.stdout.count('world') == 3
@ -2663,10 +2665,10 @@ class CLITestCase(DockerClientTestCase):
assert len(containers) == 2
web = containers[1]
db_num = containers[0].short_number
db_name = containers[0].name_without_project
assert set(get_links(web)) == set(
['db', 'mydb_{}'.format(db_num), 'extends_mydb_{}'.format(db_num)]
['db', db_name, 'extends_{}'.format(db_name)]
)
expected_env = set([
@ -2704,7 +2706,7 @@ class CLITestCase(DockerClientTestCase):
)
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 re.findall(r'exit-code-from_another_1_[a-f0-9]{12} exited with code 1', result.stdout)
def test_exit_code_from_signal_stop(self):
self.base_dir = 'tests/fixtures/exit-code-from'
@ -2713,8 +2715,8 @@ class CLITestCase(DockerClientTestCase):
['up', '--abort-on-container-exit', '--exit-code-from', 'simple']
)
result = wait_on_process(proc, returncode=137) # SIGKILL
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
name = self.project.get_service('another').containers(stopped=True)[0].name_without_project
assert '{} exited with code 1'.format(name) in result.stdout
def test_images(self):
self.project.get_service('simple').create_container()
@ -2728,8 +2730,8 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['images'])
assert 'busybox' in result.stdout
assert 'multiple-composefiles_another_' in result.stdout
assert 'multiple-composefiles_simple_' in result.stdout
assert 'multiple-composefiles_another_1' in result.stdout
assert 'multiple-composefiles_simple_1' in result.stdout
@mock.patch.dict(os.environ)
def test_images_tagless_image(self):
@ -2749,7 +2751,7 @@ class CLITestCase(DockerClientTestCase):
self.project.get_service('foo').create_container()
result = self.dispatch(['images'])
assert '<none>' in result.stdout
assert 'tagless-image_foo_' in result.stdout
assert 'tagless-image_foo_1' in result.stdout
def test_up_with_override_yaml(self):
self.base_dir = 'tests/fixtures/override-yaml-files'

View File

@ -32,6 +32,7 @@ from compose.const import LABEL_CONTAINER_NUMBER
from compose.const import LABEL_ONE_OFF
from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE
from compose.const import LABEL_SLUG
from compose.const import LABEL_VERSION
from compose.container import Container
from compose.errors import OperationFailedError
@ -867,17 +868,17 @@ class ServiceTest(DockerClientTestCase):
db_ctnrs = [create_and_start_container(db) for _ in range(3)]
web = self.create_service(
'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)
db_ctnrs[0].name,
db_ctnrs[1].name,
'{}:db_3'.format(db_ctnrs[2].name)
]
)
create_and_start_container(web)
assert set(get_links(web.containers()[0])) == set([
'composetest_db_{}'.format(db_ctnrs[0].short_number),
'composetest_db_{}'.format(db_ctnrs[1].short_number),
db_ctnrs[0].name,
db_ctnrs[1].name,
'db_3'
])
@ -1584,6 +1585,7 @@ class ServiceTest(DockerClientTestCase):
LABEL_PROJECT: 'composetest',
LABEL_SERVICE: 'web',
LABEL_VERSION: __version__,
LABEL_CONTAINER_NUMBER: '1'
}
expected = dict(labels_dict, **compose_labels)
@ -1592,7 +1594,7 @@ class ServiceTest(DockerClientTestCase):
labels = ctnr.labels.items()
for pair in expected.items():
assert pair in labels
assert ctnr.labels[LABEL_CONTAINER_NUMBER] == ctnr.number
assert ctnr.labels[LABEL_SLUG] == ctnr.full_slug
def test_empty_labels(self):
labels_dict = {'foo': '', 'bar': ''}

View File

@ -198,14 +198,14 @@ class ProjectWithDependenciesTest(ProjectTestCase):
db, = [c for c in containers if c.service == 'db']
assert set(get_links(web)) == {
'composetest_db_{}'.format(db.short_number),
'composetest_db_{}_{}'.format(db.number, db.slug),
'db',
'db_{}'.format(db.short_number)
'db_{}_{}'.format(db.number, db.slug)
}
assert set(get_links(nginx)) == {
'composetest_web_{}'.format(web.short_number),
'composetest_web_{}_{}'.format(web.number, web.slug),
'web',
'web_{}'.format(web.short_number)
'web_{}_{}'.format(web.number, web.slug)
}

View File

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

View File

@ -41,7 +41,6 @@ from compose.service import parse_repository_tag
from compose.service import Service
from compose.service import ServiceNetworkMode
from compose.service import warn_on_masked_volume
from compose.utils import generate_random_id as generate_id
class ServiceTest(unittest.TestCase):
@ -82,7 +81,8 @@ class ServiceTest(unittest.TestCase):
service = Service('db', self.mock_client, 'myproject', image='foo')
assert [c.id for c in service.containers()] == ['1']
assert service.get_container().id == '1'
assert service._next_container_number() == 2
assert service.get_container(1).id == '1'
def test_get_volumes_from_container(self):
container_id = 'aabbccddee'
@ -164,7 +164,7 @@ class ServiceTest(unittest.TestCase):
client=self.mock_client,
mem_limit=1000000000,
memswap_limit=2000000000)
service._get_container_create_options({'some': 'overrides'}, generate_id())
service._get_container_create_options({'some': 'overrides'}, 1)
assert self.mock_client.create_host_config.called
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):
service = Service(
name='foo',
external_links=['default_foo_bdfa3ed91e2c']
external_links=['default_foo_1_bdfa3ed91e2c']
)
with pytest.raises(DependencyError):
service.get_container_name('foo', 'bdfa3ed91e2c')
service.get_container_name('foo', 1, 'bdfa3ed91e2c')
def test_mem_reservation(self):
self.mock_client.create_host_config.return_value = {}
@ -188,7 +188,7 @@ class ServiceTest(unittest.TestCase):
client=self.mock_client,
mem_reservation='512m'
)
service._get_container_create_options({'some': 'overrides'}, generate_id())
service._get_container_create_options({'some': 'overrides'}, 1)
assert self.mock_client.create_host_config.called is True
assert self.mock_client.create_host_config.call_args[1]['mem_reservation'] == '512m'
@ -201,7 +201,7 @@ class ServiceTest(unittest.TestCase):
hostname='name',
client=self.mock_client,
cgroup_parent='test')
service._get_container_create_options({'some': 'overrides'}, generate_id())
service._get_container_create_options({'some': 'overrides'}, 1)
assert self.mock_client.create_host_config.called
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,
log_driver='syslog',
logging=logging)
service._get_container_create_options({'some': 'overrides'}, generate_id())
service._get_container_create_options({'some': 'overrides'}, 1)
assert self.mock_client.create_host_config.called
assert self.mock_client.create_host_config.call_args[1]['log_config'] == {
@ -233,7 +233,7 @@ class ServiceTest(unittest.TestCase):
image='foo',
client=self.mock_client,
stop_grace_period="1m35s")
opts = service._get_container_create_options({'image': 'foo'}, generate_id())
opts = service._get_container_create_options({'image': 'foo'}, 1)
assert opts['stop_timeout'] == 95
def test_split_domainname_none(self):
@ -242,7 +242,7 @@ class ServiceTest(unittest.TestCase):
image='foo',
hostname='name.domain.tld',
client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, generate_id())
opts = service._get_container_create_options({'image': 'foo'}, 1)
assert opts['hostname'] == 'name.domain.tld', 'hostname'
assert not ('domainname' in opts), 'domainname'
@ -253,7 +253,7 @@ class ServiceTest(unittest.TestCase):
hostname='name.domain.tld',
image='foo',
client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, generate_id())
opts = service._get_container_create_options({'image': 'foo'}, 1)
assert opts['hostname'] == 'name', 'hostname'
assert opts['domainname'] == 'domain.tld', 'domainname'
@ -265,7 +265,7 @@ class ServiceTest(unittest.TestCase):
image='foo',
domainname='domain.tld',
client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, generate_id())
opts = service._get_container_create_options({'image': 'foo'}, 1)
assert opts['hostname'] == 'name', 'hostname'
assert opts['domainname'] == 'domain.tld', 'domainname'
@ -277,7 +277,7 @@ class ServiceTest(unittest.TestCase):
domainname='domain.tld',
image='foo',
client=self.mock_client)
opts = service._get_container_create_options({'image': 'foo'}, generate_id())
opts = service._get_container_create_options({'image': 'foo'}, 1)
assert opts['hostname'] == 'name.sub', 'hostname'
assert opts['domainname'] == 'domain.tld', 'domainname'
@ -288,7 +288,7 @@ class ServiceTest(unittest.TestCase):
use_networking=False,
client=self.mock_client,
)
opts = service._get_container_create_options({'image': 'foo'}, generate_id())
opts = service._get_container_create_options({'image': 'foo'}, 1)
assert opts.get('hostname') is None
def test_get_container_create_options_with_name_option(self):
@ -317,11 +317,13 @@ class ServiceTest(unittest.TestCase):
self.mock_client.inspect_image.return_value = {'Id': 'abcd'}
prev_container = mock.Mock(
id='ababab',
image_config={'ContainerConfig': {}})
image_config={'ContainerConfig': {}}
)
prev_container.full_slug = 'abcdefff1234'
prev_container.get.return_value = None
opts = service._get_container_create_options(
{}, generate_id(), previous_container=prev_container
{}, 1, previous_container=prev_container
)
assert service.options['labels'] == labels
@ -354,11 +356,13 @@ class ServiceTest(unittest.TestCase):
}.get(key, None)
prev_container.get.side_effect = container_get
prev_container.full_slug = 'abcdefff1234'
opts = service._get_container_create_options(
{},
generate_id(),
previous_container=prev_container)
1,
previous_container=prev_container
)
assert opts['environment'] == ['affinity:container==ababab']
@ -369,10 +373,11 @@ class ServiceTest(unittest.TestCase):
id='ababab',
image_config={'ContainerConfig': {}})
prev_container.get.return_value = None
prev_container.full_slug = 'abcdefff1234'
opts = service._get_container_create_options(
{},
generate_id(),
1,
previous_container=prev_container)
assert opts['environment'] == []
@ -385,11 +390,11 @@ class ServiceTest(unittest.TestCase):
@mock.patch('compose.service.Container', autospec=True)
def test_get_container(self, mock_container_class):
container_dict = dict(Name='default_foo_bdfa3ed91e2c')
container_dict = dict(Name='default_foo_2_bdfa3ed91e2c')
self.mock_client.containers.return_value = [container_dict]
service = Service('foo', image='foo', client=self.mock_client)
container = service.get_container(number="bdfa3ed91e2c")
container = service.get_container(number=2)
assert container == mock_container_class.from_ps.return_value
mock_container_class.from_ps.assert_called_once_with(
self.mock_client, container_dict)
@ -462,7 +467,7 @@ class ServiceTest(unittest.TestCase):
@mock.patch('compose.service.Container', autospec=True)
def test_recreate_container(self, _):
mock_container = mock.create_autospec(Container)
mock_container.number = generate_id()
mock_container.full_slug = 'abcdefff1234'
service = Service('foo', client=self.mock_client, image='someimage')
service.image = lambda: {'Id': 'abc123'}
new_container = service.recreate_container(mock_container)
@ -476,7 +481,7 @@ class ServiceTest(unittest.TestCase):
@mock.patch('compose.service.Container', autospec=True)
def test_recreate_container_with_timeout(self, _):
mock_container = mock.create_autospec(Container)
mock_container.number = generate_id()
mock_container.full_slug = 'abcdefff1234'
self.mock_client.inspect_image.return_value = {'Id': 'abc123'}
service = Service('foo', client=self.mock_client, image='someimage')
service.recreate_container(mock_container, timeout=1)
@ -713,7 +718,7 @@ class ServiceTest(unittest.TestCase):
for api_version in set(API_VERSIONS.values()):
self.mock_client.api_version = api_version
assert service._get_container_create_options(
{}, generate_id()
{}, 1
)['labels'][LABEL_CONFIG_HASH] == config_hash
def test_remove_image_none(self):
@ -972,7 +977,7 @@ class ServiceTest(unittest.TestCase):
service = Service('foo', client=self.mock_client, environment=environment)
create_opts = service._get_container_create_options(override_options, generate_id())
create_opts = service._get_container_create_options(override_options, 1)
assert set(create_opts['environment']) == set(format_environment({
'HTTP_PROXY': default_proxy_config['httpProxy'],
'http_proxy': default_proxy_config['httpProxy'],
@ -1297,7 +1302,7 @@ class ServiceVolumesTest(unittest.TestCase):
service._get_container_create_options(
override_options={},
number=generate_id(),
number=1,
)
assert set(self.mock_client.create_host_config.call_args[1]['binds']) == set([
@ -1340,7 +1345,7 @@ class ServiceVolumesTest(unittest.TestCase):
service._get_container_create_options(
override_options={},
number=generate_id(),
number=1,
previous_container=Container(self.mock_client, {'Id': '123123123'}),
)