mirror of
https://github.com/docker/compose.git
synced 2025-07-23 05:34:36 +02:00
Merge pull request #6140 from docker/4688_no_sequential_ids
Add randomly generated slug to container names to prevent collisions
This commit is contained in:
commit
f80630ffcf
@ -15,6 +15,7 @@ LABEL_PROJECT = 'com.docker.compose.project'
|
|||||||
LABEL_SERVICE = 'com.docker.compose.service'
|
LABEL_SERVICE = 'com.docker.compose.service'
|
||||||
LABEL_NETWORK = 'com.docker.compose.network'
|
LABEL_NETWORK = 'com.docker.compose.network'
|
||||||
LABEL_VERSION = 'com.docker.compose.version'
|
LABEL_VERSION = 'com.docker.compose.version'
|
||||||
|
LABEL_SLUG = 'com.docker.compose.slug'
|
||||||
LABEL_VOLUME = 'com.docker.compose.volume'
|
LABEL_VOLUME = 'com.docker.compose.volume'
|
||||||
LABEL_CONFIG_HASH = 'com.docker.compose.config-hash'
|
LABEL_CONFIG_HASH = 'com.docker.compose.config-hash'
|
||||||
NANOCPUS_SCALE = 1000000000
|
NANOCPUS_SCALE = 1000000000
|
||||||
|
@ -9,7 +9,9 @@ from docker.errors import ImageNotFound
|
|||||||
from .const import LABEL_CONTAINER_NUMBER
|
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_SLUG
|
||||||
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 +82,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}{2}'.format(self.service, self.number, '_' + self.slug if self.slug else '')
|
||||||
else:
|
else:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@ -92,6 +94,14 @@ class Container(object):
|
|||||||
self.short_id, LABEL_CONTAINER_NUMBER))
|
self.short_id, LABEL_CONTAINER_NUMBER))
|
||||||
return int(number)
|
return int(number)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def slug(self):
|
||||||
|
return truncate_id(self.full_slug)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_slug(self):
|
||||||
|
return self.labels.get(LABEL_SLUG)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self):
|
def ports(self):
|
||||||
self.inspect_if_not_inspected()
|
self.inspect_if_not_inspected()
|
||||||
|
@ -31,7 +31,6 @@ from .service import ConvergenceStrategy
|
|||||||
from .service import NetworkMode
|
from .service import NetworkMode
|
||||||
from .service import PidMode
|
from .service import PidMode
|
||||||
from .service import Service
|
from .service import Service
|
||||||
from .service import ServiceName
|
|
||||||
from .service import ServiceNetworkMode
|
from .service import ServiceNetworkMode
|
||||||
from .service import ServicePidMode
|
from .service import ServicePidMode
|
||||||
from .utils import microseconds_from_time_nano
|
from .utils import microseconds_from_time_nano
|
||||||
@ -198,25 +197,6 @@ class Project(object):
|
|||||||
service.remove_duplicate_containers()
|
service.remove_duplicate_containers()
|
||||||
return services
|
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):
|
def get_links(self, service_dict):
|
||||||
links = []
|
links = []
|
||||||
if 'links' in service_dict:
|
if 'links' in service_dict:
|
||||||
@ -494,7 +474,6 @@ class Project(object):
|
|||||||
svc.ensure_image_exists(do_build=do_build, silent=silent)
|
svc.ensure_image_exists(do_build=do_build, silent=silent)
|
||||||
plans = self._get_convergence_plans(
|
plans = self._get_convergence_plans(
|
||||||
services, strategy, always_recreate_deps=always_recreate_deps)
|
services, strategy, always_recreate_deps=always_recreate_deps)
|
||||||
scaled_services = self.get_scaled_services(services, scale_override)
|
|
||||||
|
|
||||||
def do(service):
|
def do(service):
|
||||||
|
|
||||||
@ -505,7 +484,6 @@ class Project(object):
|
|||||||
scale_override=scale_override.get(service.name),
|
scale_override=scale_override.get(service.name),
|
||||||
rescale=rescale,
|
rescale=rescale,
|
||||||
start=start,
|
start=start,
|
||||||
project_services=scaled_services,
|
|
||||||
reset_container_image=reset_container_image,
|
reset_container_image=reset_container_image,
|
||||||
renew_anonymous_volumes=renew_anonymous_volumes,
|
renew_anonymous_volumes=renew_anonymous_volumes,
|
||||||
)
|
)
|
||||||
|
@ -40,6 +40,7 @@ from .const import LABEL_CONTAINER_NUMBER
|
|||||||
from .const import LABEL_ONE_OFF
|
from .const import LABEL_ONE_OFF
|
||||||
from .const import LABEL_PROJECT
|
from .const import LABEL_PROJECT
|
||||||
from .const import LABEL_SERVICE
|
from .const import LABEL_SERVICE
|
||||||
|
from .const import LABEL_SLUG
|
||||||
from .const import LABEL_VERSION
|
from .const import LABEL_VERSION
|
||||||
from .const import NANOCPUS_SCALE
|
from .const import NANOCPUS_SCALE
|
||||||
from .container import Container
|
from .container import Container
|
||||||
@ -49,9 +50,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__)
|
||||||
@ -122,7 +125,7 @@ class NoSuchImageError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
ServiceName = namedtuple('ServiceName', 'project service number')
|
ServiceName = namedtuple('ServiceName', 'project service number slug')
|
||||||
|
|
||||||
|
|
||||||
ConvergencePlan = namedtuple('ConvergencePlan', 'action containers')
|
ConvergencePlan = namedtuple('ConvergencePlan', 'action containers')
|
||||||
@ -219,7 +222,6 @@ class Service(object):
|
|||||||
"""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`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -425,27 +427,33 @@ 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):
|
||||||
i = self._next_container_number()
|
|
||||||
|
|
||||||
def create_and_start(service, n):
|
i = self._next_container_number()
|
||||||
container = service.create_container(number=n, quiet=True)
|
|
||||||
if not detached:
|
|
||||||
container.attach_log_stream()
|
|
||||||
if start:
|
|
||||||
self.start_container(container)
|
|
||||||
return container
|
|
||||||
|
|
||||||
containers, errors = parallel_execute(
|
def create_and_start(service, n):
|
||||||
[ServiceName(self.project, self.name, index) for index in range(i, i + scale)],
|
container = service.create_container(number=n, quiet=True)
|
||||||
lambda service_name: create_and_start(self, service_name.number),
|
if not detached:
|
||||||
lambda service_name: self.get_container_name(service_name.service, service_name.number),
|
container.attach_log_stream()
|
||||||
"Creating"
|
if start:
|
||||||
)
|
self.start_container(container)
|
||||||
for error in errors.values():
|
return container
|
||||||
raise OperationFailedError(error)
|
|
||||||
|
|
||||||
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,
|
def _execute_convergence_recreate(self, containers, scale, timeout, detached, start,
|
||||||
renew_anonymous_volumes):
|
renew_anonymous_volumes):
|
||||||
@ -508,8 +516,8 @@ class Service(object):
|
|||||||
|
|
||||||
def execute_convergence_plan(self, plan, timeout=None, detached=False,
|
def execute_convergence_plan(self, plan, timeout=None, detached=False,
|
||||||
start=True, scale_override=None,
|
start=True, scale_override=None,
|
||||||
rescale=True, project_services=None,
|
rescale=True, reset_container_image=False,
|
||||||
reset_container_image=False, renew_anonymous_volumes=False):
|
renew_anonymous_volumes=False):
|
||||||
(action, containers) = plan
|
(action, containers) = plan
|
||||||
scale = scale_override if scale_override is not None else self.scale_num
|
scale = scale_override if scale_override is not None else self.scale_num
|
||||||
containers = sorted(containers, key=attrgetter('number'))
|
containers = sorted(containers, key=attrgetter('number'))
|
||||||
@ -518,7 +526,7 @@ class Service(object):
|
|||||||
|
|
||||||
if action == 'create':
|
if action == 'create':
|
||||||
return self._execute_convergence_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,
|
# The create action needs always needs an initial scale, but otherwise,
|
||||||
@ -568,7 +576,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,8 +731,6 @@ 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(
|
containers = itertools.chain(
|
||||||
self._fetch_containers(
|
self._fetch_containers(
|
||||||
@ -813,6 +819,7 @@ class Service(object):
|
|||||||
one_off=False,
|
one_off=False,
|
||||||
previous_container=None):
|
previous_container=None):
|
||||||
add_config_hash = (not one_off and not override_options)
|
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(
|
container_options = dict(
|
||||||
(k, self.options[k])
|
(k, self.options[k])
|
||||||
@ -821,7 +828,7 @@ class Service(object):
|
|||||||
container_options.update(override_options)
|
container_options.update(override_options)
|
||||||
|
|
||||||
if not container_options.get('name'):
|
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)
|
container_options.setdefault('detach', True)
|
||||||
|
|
||||||
@ -873,7 +880,9 @@ class Service(object):
|
|||||||
container_options.get('labels', {}),
|
container_options.get('labels', {}),
|
||||||
self.labels(one_off=one_off),
|
self.labels(one_off=one_off),
|
||||||
number,
|
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
|
# Delete options which are only used in HostConfig
|
||||||
for key in HOST_CONFIG_KEYS:
|
for key in HOST_CONFIG_KEYS:
|
||||||
@ -1111,12 +1120,12 @@ class Service(object):
|
|||||||
def custom_container_name(self):
|
def custom_container_name(self):
|
||||||
return self.options.get('container_name')
|
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:
|
if self.custom_container_name and not one_off:
|
||||||
return self.custom_container_name
|
return self.custom_container_name
|
||||||
|
|
||||||
container_name = build_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', [])]
|
ext_links_origins = [l.split(':')[0] for l in self.options.get('external_links', [])]
|
||||||
if container_name in ext_links_origins:
|
if container_name in ext_links_origins:
|
||||||
@ -1373,11 +1382,13 @@ class ServiceNetworkMode(object):
|
|||||||
# Names
|
# 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]
|
bits = [project.lstrip('-_'), service]
|
||||||
if one_off:
|
if one_off:
|
||||||
bits.append('run')
|
bits.append('run')
|
||||||
return '_'.join(bits + [str(number)])
|
return '_'.join(
|
||||||
|
bits + ([str(number), truncate_id(slug)] if slug else [str(number)])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Images
|
# Images
|
||||||
@ -1558,10 +1569,11 @@ def build_mount(mount_spec):
|
|||||||
# Labels
|
# 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 = dict(label_options or {})
|
||||||
labels.update(label.split('=', 1) for label in service_labels)
|
labels.update(label.split('=', 1) for label in service_labels)
|
||||||
labels[LABEL_CONTAINER_NUMBER] = str(number)
|
labels[LABEL_CONTAINER_NUMBER] = str(number)
|
||||||
|
labels[LABEL_SLUG] = slug
|
||||||
labels[LABEL_VERSION] = __version__
|
labels[LABEL_VERSION] = __version__
|
||||||
|
|
||||||
if config_hash:
|
if config_hash:
|
||||||
|
@ -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
|
||||||
|
@ -50,7 +50,6 @@ class Version(namedtuple('_Version', 'major minor patch stage edition')):
|
|||||||
stage = None
|
stage = None
|
||||||
elif '-' in stage:
|
elif '-' in stage:
|
||||||
edition, stage = stage.split('-')
|
edition, stage = stage.split('-')
|
||||||
|
|
||||||
major, minor, patch = version.split('.', 3)
|
major, minor, patch = version.split('.', 3)
|
||||||
return cls(major, minor, patch, stage, edition)
|
return cls(major, minor, patch, stage, edition)
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -1019,11 +1026,15 @@ 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_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_1 | simple' in result.stdout
|
assert '{} | simple'.format(simple_name) in result.stdout
|
||||||
assert 'another_1 | another' in result.stdout
|
assert '{} | another'.format(another_name) in result.stdout
|
||||||
assert 'simple_1 exited with code 0' in result.stdout
|
assert '{} exited with code 0'.format(simple_name) in result.stdout
|
||||||
assert 'another_1 exited with code 0' in result.stdout
|
assert '{} exited with code 0'.format(another_name) in result.stdout
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_up(self):
|
def test_up(self):
|
||||||
@ -1727,11 +1738,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 +2066,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 +2298,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_name = self.project.get_service('another').get_container().name_without_project
|
||||||
self.project.client,
|
wait_on_condition(
|
||||||
'logs-composefile_another_1',
|
ContainerStateCondition(
|
||||||
'exited'))
|
self.project.client,
|
||||||
|
'logs-composefile_another_*',
|
||||||
|
'exited'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
simple_name = self.project.get_service('simple').get_container().name_without_project
|
||||||
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 '{} exited with code 0'.format(another_name) in result.stdout
|
||||||
assert 'logs-composefile_simple_1 exited with code 137' in result.stdout
|
assert '{} exited with code 137'.format(simple_name) 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_1_[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 +2368,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 +2545,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 +2583,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 +2665,11 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
|
|
||||||
assert len(containers) == 2
|
assert len(containers) == 2
|
||||||
web = containers[1]
|
web = containers[1]
|
||||||
|
db_name = containers[0].name_without_project
|
||||||
|
|
||||||
assert set(get_links(web)) == set(['db', 'mydb_1', 'extends_mydb_1'])
|
assert set(get_links(web)) == set(
|
||||||
|
['db', db_name, 'extends_{}'.format(db_name)]
|
||||||
|
)
|
||||||
|
|
||||||
expected_env = set([
|
expected_env = set([
|
||||||
"FOO=1",
|
"FOO=1",
|
||||||
@ -2677,11 +2702,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_1_[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 +2715,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
|
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):
|
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'
|
||||||
|
@ -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"
|
||||||
|
@ -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'
|
||||||
|
@ -32,6 +32,7 @@ from compose.const import LABEL_CONTAINER_NUMBER
|
|||||||
from compose.const import LABEL_ONE_OFF
|
from compose.const import LABEL_ONE_OFF
|
||||||
from compose.const import LABEL_PROJECT
|
from compose.const import LABEL_PROJECT
|
||||||
from compose.const import LABEL_SERVICE
|
from compose.const import LABEL_SERVICE
|
||||||
|
from compose.const import LABEL_SLUG
|
||||||
from compose.const import LABEL_VERSION
|
from compose.const import LABEL_VERSION
|
||||||
from compose.container import Container
|
from compose.container import Container
|
||||||
from compose.errors import OperationFailedError
|
from compose.errors import OperationFailedError
|
||||||
@ -67,7 +68,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 +78,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 +90,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 +490,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 +504,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 +837,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 +852,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=[
|
||||||
|
db_ctnrs[0].name,
|
||||||
|
db_ctnrs[1].name,
|
||||||
|
'{}:db_3'.format(db_ctnrs[2].name)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
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',
|
db_ctnrs[0].name,
|
||||||
'composetest_db_2',
|
db_ctnrs[1].name,
|
||||||
'db_3'
|
'db_3'
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -892,14 +896,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 +1253,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 +1313,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,18 +1581,20 @@ 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',
|
||||||
LABEL_VERSION: __version__,
|
LABEL_VERSION: __version__,
|
||||||
|
LABEL_CONTAINER_NUMBER: '1'
|
||||||
}
|
}
|
||||||
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_SLUG] == ctnr.full_slug
|
||||||
|
|
||||||
def test_empty_labels(self):
|
def test_empty_labels(self):
|
||||||
labels_dict = {'foo': '', 'bar': ''}
|
labels_dict = {'foo': '', 'bar': ''}
|
||||||
@ -1655,7 +1658,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])
|
||||||
|
@ -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.number, db.slug),
|
||||||
|
'db',
|
||||||
|
'db_{}_{}'.format(db.number, db.slug)
|
||||||
|
}
|
||||||
|
assert set(get_links(nginx)) == {
|
||||||
|
'composetest_web_{}_{}'.format(web.number, web.slug),
|
||||||
|
'web',
|
||||||
|
'web_{}_{}'.format(web.number, web.slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ServiceStateTest(DockerClientTestCase):
|
class ServiceStateTest(DockerClientTestCase):
|
||||||
|
@ -30,7 +30,8 @@ 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": "7",
|
||||||
|
"com.docker.compose.slug": "092cd63296fdc446ad432d3905dd1fcbe12a2ba6b52"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +89,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_7_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"
|
||||||
|
@ -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_1_bdfa3ed91e2c']
|
||||||
)
|
)
|
||||||
with pytest.raises(DependencyError):
|
with pytest.raises(DependencyError):
|
||||||
service.get_container_name('foo', 1)
|
service.get_container_name('foo', 1, '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 = {}
|
||||||
@ -317,13 +317,14 @@ class ServiceTest(unittest.TestCase):
|
|||||||
self.mock_client.inspect_image.return_value = {'Id': 'abcd'}
|
self.mock_client.inspect_image.return_value = {'Id': 'abcd'}
|
||||||
prev_container = mock.Mock(
|
prev_container = mock.Mock(
|
||||||
id='ababab',
|
id='ababab',
|
||||||
image_config={'ContainerConfig': {}})
|
image_config={'ContainerConfig': {}}
|
||||||
|
)
|
||||||
|
prev_container.full_slug = 'abcdefff1234'
|
||||||
prev_container.get.return_value = None
|
prev_container.get.return_value = None
|
||||||
|
|
||||||
opts = service._get_container_create_options(
|
opts = service._get_container_create_options(
|
||||||
{},
|
{}, 1, 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
|
||||||
@ -355,11 +356,13 @@ class ServiceTest(unittest.TestCase):
|
|||||||
}.get(key, None)
|
}.get(key, None)
|
||||||
|
|
||||||
prev_container.get.side_effect = container_get
|
prev_container.get.side_effect = container_get
|
||||||
|
prev_container.full_slug = 'abcdefff1234'
|
||||||
|
|
||||||
opts = service._get_container_create_options(
|
opts = service._get_container_create_options(
|
||||||
{},
|
{},
|
||||||
1,
|
1,
|
||||||
previous_container=prev_container)
|
previous_container=prev_container
|
||||||
|
)
|
||||||
|
|
||||||
assert opts['environment'] == ['affinity:container==ababab']
|
assert opts['environment'] == ['affinity:container==ababab']
|
||||||
|
|
||||||
@ -370,6 +373,7 @@ class ServiceTest(unittest.TestCase):
|
|||||||
id='ababab',
|
id='ababab',
|
||||||
image_config={'ContainerConfig': {}})
|
image_config={'ContainerConfig': {}})
|
||||||
prev_container.get.return_value = None
|
prev_container.get.return_value = None
|
||||||
|
prev_container.full_slug = 'abcdefff1234'
|
||||||
|
|
||||||
opts = service._get_container_create_options(
|
opts = service._get_container_create_options(
|
||||||
{},
|
{},
|
||||||
@ -386,7 +390,7 @@ 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_2_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)
|
||||||
|
|
||||||
@ -463,6 +467,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.full_slug = 'abcdefff1234'
|
||||||
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 +481,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.full_slug = 'abcdefff1234'
|
||||||
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 +717,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
|
{}, 1
|
||||||
)
|
)['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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user