Skip to content

Commit

Permalink
Don't append slugs to containers created by "up"
Browse files Browse the repository at this point in the history
This change reverts the new naming convention introduced in 1.23 for service containers.
One-off containers will now use a slug instead of a sequential number as they do not
present addressability concerns and benefit from being capable of running in parallel.

Signed-off-by: Joffrey F <joffrey@docker.com>
  • Loading branch information
shin- committed Nov 28, 2018
1 parent eedbb28 commit 61bb1ea
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 48 deletions.
11 changes: 10 additions & 1 deletion compose/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from docker.errors import ImageNotFound

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
Expand Down Expand Up @@ -82,12 +83,16 @@ def service(self):
@property
def name_without_project(self):
if self.name.startswith('{0}_{1}'.format(self.project, self.service)):
return '{0}_{1}{2}'.format(self.service, self.number, '_' + self.slug if self.slug else '')
return '{0}_{1}'.format(self.service, self.number if self.number is not None else self.slug)
else:
return self.name

@property
def number(self):
if self.one_off:
# One-off containers are no longer assigned numbers and use slugs instead.
return None

number = self.labels.get(LABEL_CONTAINER_NUMBER)
if not number:
raise ValueError("Container {0} does not have a {1} label".format(
Expand All @@ -104,6 +109,10 @@ def slug(self):
def full_slug(self):
return self.labels.get(LABEL_SLUG)

@property
def one_off(self):
return self.labels.get(LABEL_ONE_OFF) == 'True'

@property
def ports(self):
self.inspect_if_not_inspected()
Expand Down
36 changes: 20 additions & 16 deletions compose/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,16 +738,18 @@ def get_volumes_from_names(self):
return [s.source.name for s in self.volumes_from if isinstance(s.source, Service)]

def _next_container_number(self, one_off=False):
if one_off:
return None
containers = itertools.chain(
self._fetch_containers(
all=True,
filters={'label': self.labels(one_off=one_off)}
filters={'label': self.labels(one_off=False)}
), self._fetch_containers(
all=True,
filters={'label': self.labels(one_off=one_off, legacy=True)}
filters={'label': self.labels(one_off=False, legacy=True)}
)
)
numbers = [c.number for c in containers]
numbers = [c.number for c in containers if c.number is not None]
return 1 if not numbers else max(numbers) + 1

def _fetch_containers(self, **fetch_options):
Expand Down Expand Up @@ -825,7 +827,7 @@ def _get_container_create_options(
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
slug = generate_random_id() if one_off else None

container_options = dict(
(k, self.options[k])
Expand All @@ -834,7 +836,7 @@ def _get_container_create_options(
container_options.update(override_options)

if not container_options.get('name'):
container_options['name'] = self.get_container_name(self.name, number, slug, one_off)
container_options['name'] = self.get_container_name(self.name, number, slug)

container_options.setdefault('detach', True)

Expand Down Expand Up @@ -1122,12 +1124,12 @@ def labels(self, one_off=False, legacy=False):
def custom_container_name(self):
return self.options.get('container_name')

def get_container_name(self, service_name, number, slug, one_off=False):
if self.custom_container_name and not one_off:
def get_container_name(self, service_name, number, slug=None):
if self.custom_container_name and slug is None:
return self.custom_container_name

container_name = build_container_name(
self.project, service_name, number, slug, one_off,
self.project, service_name, number, slug,
)
ext_links_origins = [l.split(':')[0] for l in self.options.get('external_links', [])]
if container_name in ext_links_origins:
Expand Down Expand Up @@ -1384,13 +1386,13 @@ def mode(self):
# Names


def build_container_name(project, service, number, slug, one_off=False):
def build_container_name(project, service, number, slug=None):
bits = [project.lstrip('-_'), service]
if one_off:
bits.append('run')
return '_'.join(
bits + ([str(number), truncate_id(slug)] if slug else [str(number)])
)
if slug:
bits.extend(['run', truncate_id(slug)])
else:
bits.append(str(number))
return '_'.join(bits)


# Images
Expand Down Expand Up @@ -1579,8 +1581,10 @@ def build_mount(mount_spec):
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
if number is not None:
labels[LABEL_CONTAINER_NUMBER] = str(number)
if slug is not None:
labels[LABEL_SLUG] = slug
labels[LABEL_VERSION] = __version__

if config_hash:
Expand Down
23 changes: 11 additions & 12 deletions tests/acceptance/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,10 +602,10 @@ def test_ps_services_filter_status(self):
def test_ps_all(self):
self.project.get_service('simple').create_container(one_off='blahblah')
result = self.dispatch(['ps'])
assert 'simple-composefile_simple_run_1' not in result.stdout
assert 'simple-composefile_simple_run_' not in result.stdout

result2 = self.dispatch(['ps', '--all'])
assert 'simple-composefile_simple_run_1' in result2.stdout
assert 'simple-composefile_simple_run_' in result2.stdout

def test_pull(self):
result = self.dispatch(['pull'])
Expand Down Expand Up @@ -973,11 +973,11 @@ def test_down(self):
result = self.dispatch(['down', '--rmi=local', '--volumes'])
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 'Stopping v2-full_web_run_' 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 v2-full_web_run_' in result.stderr
assert 'Removing v2-full_web_run_' 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
Expand Down Expand Up @@ -1039,8 +1039,8 @@ def test_up_attached(self):
stopped=True
)[0].name_without_project

assert '{} | simple'.format(simple_name) in result.stdout
assert '{} | another'.format(another_name) 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

Expand Down Expand Up @@ -2340,10 +2340,9 @@ def test_logs_follow_logs_from_restarted_containers(self):

result = wait_on_process(proc)

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(
r'logs-restart-composefile_another_1 exited with code 1'
) == 3
assert result.stdout.count('world') == 3

def test_logs_default(self):
Expand Down Expand Up @@ -2714,7 +2713,7 @@ def test_forward_exitval(self):
)

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):
self.base_dir = 'tests/fixtures/exit-code-from'
Expand Down
9 changes: 3 additions & 6 deletions tests/integration/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
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
Expand Down Expand Up @@ -1269,16 +1268,15 @@ def test_scale_with_stopped_containers(self):
test that those containers are restarted and not removed/recreated.
"""
service = self.create_service('web')
valid_numbers = [service._next_container_number(), service._next_container_number()]
service.create_container(number=valid_numbers[0])
service.create_container(number=valid_numbers[1])
service.create_container(number=1)
service.create_container(number=2)

ParallelStreamWriter.instance = None
with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
service.scale(2)
for container in service.containers():
assert container.is_running
assert container.number in valid_numbers
assert container.number in [1, 2]

captured_output = mock_stderr.getvalue()
assert 'Creating' not in captured_output
Expand Down Expand Up @@ -1610,7 +1608,6 @@ def test_labels(self):
labels = ctnr.labels.items()
for pair in expected.items():
assert pair in labels
assert ctnr.labels[LABEL_SLUG] == ctnr.full_slug

def test_empty_labels(self):
labels_dict = {'foo': '', 'bar': ''}
Expand Down
8 changes: 4 additions & 4 deletions tests/integration/state_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,14 @@ def test_service_recreated_when_dependency_created(self):
db, = [c for c in containers if c.service == 'db']

assert set(get_links(web)) == {
'composetest_db_{}_{}'.format(db.number, db.slug),
'composetest_db_1',
'db',
'db_{}_{}'.format(db.number, db.slug)
'db_1',
}
assert set(get_links(nginx)) == {
'composetest_web_{}_{}'.format(web.number, web.slug),
'composetest_web_1',
'web',
'web_{}_{}'.format(web.number, web.slug)
'web_1',
}


Expand Down
17 changes: 10 additions & 7 deletions tests/unit/container_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .. import mock
from .. import unittest
from compose.const import LABEL_ONE_OFF
from compose.const import LABEL_SLUG
from compose.container import Container
from compose.container import get_container_name
Expand Down Expand Up @@ -32,7 +33,6 @@ def setUp(self):
"com.docker.compose.project": "composetest",
"com.docker.compose.service": "web",
"com.docker.compose.container-number": "7",
"com.docker.compose.slug": "092cd63296fdc446ad432d3905dd1fcbe12a2ba6b52"
},
}
}
Expand Down Expand Up @@ -88,20 +88,23 @@ def test_name(self):
assert container.name == "composetest_db_1"

def test_name_without_project(self):
self.container_dict['Name'] = "/composetest_web_7_092cd63296fd"
self.container_dict['Name'] = "/composetest_web_7"
container = Container(None, self.container_dict, has_been_inspected=True)
assert container.name_without_project == "web_7_092cd63296fd"
assert container.name_without_project == "web_7"

def test_name_without_project_custom_container_name(self):
self.container_dict['Name'] = "/custom_name_of_container"
container = Container(None, self.container_dict, has_been_inspected=True)
assert container.name_without_project == "custom_name_of_container"

def test_name_without_project_noslug(self):
self.container_dict['Name'] = "/composetest_web_7"
del self.container_dict['Config']['Labels'][LABEL_SLUG]
def test_name_without_project_one_off(self):
self.container_dict['Name'] = "/composetest_web_092cd63296f"
self.container_dict['Config']['Labels'][LABEL_SLUG] = (
"092cd63296fdc446ad432d3905dd1fcbe12a2ba6b52"
)
self.container_dict['Config']['Labels'][LABEL_ONE_OFF] = 'True'
container = Container(None, self.container_dict, has_been_inspected=True)
assert container.name_without_project == 'web_7'
assert container.name_without_project == 'web_092cd63296fd'

def test_inspect_if_not_inspected(self):
mock_client = mock.create_autospec(docker.APIClient)
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ def test_memory_swap_limit(self):
def test_self_reference_external_link(self):
service = Service(
name='foo',
external_links=['default_foo_1_bdfa3ed91e2c']
external_links=['default_foo_1']
)
with pytest.raises(DependencyError):
service.get_container_name('foo', 1, 'bdfa3ed91e2c')
service.get_container_name('foo', 1)

def test_mem_reservation(self):
self.mock_client.create_host_config.return_value = {}
Expand Down

0 comments on commit 61bb1ea

Please sign in to comment.