diff --git a/compose/cli/main.py b/compose/cli/main.py index 1a2f3c725d..8d21beafec 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -10,7 +10,9 @@ import sys from docker.errors import APIError import dockerpty -from .. import __version__, legacy +from .. import __version__ +from .. import legacy +from ..const import DEFAULT_TIMEOUT from ..project import NoSuchService, ConfigurationError from ..service import BuildError, NeedsBuildError from ..config import parse_environment @@ -394,9 +396,8 @@ class TopLevelCommand(Command): -t, --timeout TIMEOUT Specify a shutdown timeout in seconds. (default: 10) """ - timeout = options.get('--timeout') - params = {} if timeout is None else {'timeout': int(timeout)} - project.stop(service_names=options['SERVICE'], **params) + timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT) + project.stop(service_names=options['SERVICE'], timeout=timeout) def restart(self, project, options): """ @@ -408,9 +409,8 @@ class TopLevelCommand(Command): -t, --timeout TIMEOUT Specify a shutdown timeout in seconds. (default: 10) """ - timeout = options.get('--timeout') - params = {} if timeout is None else {'timeout': int(timeout)} - project.restart(service_names=options['SERVICE'], **params) + timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT) + project.restart(service_names=options['SERVICE'], timeout=timeout) def up(self, project, options): """ @@ -439,9 +439,9 @@ class TopLevelCommand(Command): image needs to be updated. (EXPERIMENTAL) --no-recreate If containers already exist, don't recreate them. --no-build Don't build an image, even if it's missing - -t, --timeout TIMEOUT When attached, use this timeout in seconds - for the shutdown. (default: 10) - + -t, --timeout TIMEOUT Use this timeout in seconds for container shutdown + when attached or when containers are already + running. (default: 10) """ insecure_registry = options['--allow-insecure-ssl'] detached = options['-d'] @@ -452,6 +452,7 @@ class TopLevelCommand(Command): allow_recreate = not options['--no-recreate'] smart_recreate = options['--x-smart-recreate'] service_names = options['SERVICE'] + timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT) project.up( service_names=service_names, @@ -460,6 +461,7 @@ class TopLevelCommand(Command): smart_recreate=smart_recreate, insecure_registry=insecure_registry, do_build=not options['--no-build'], + timeout=timeout ) to_attach = [c for s in project.get_services(service_names) for c in s.containers()] @@ -477,9 +479,7 @@ class TopLevelCommand(Command): signal.signal(signal.SIGINT, handler) print("Gracefully stopping... (press Ctrl+C again to force)") - timeout = options.get('--timeout') - params = {} if timeout is None else {'timeout': int(timeout)} - project.stop(service_names=service_names, **params) + project.stop(service_names=service_names, timeout=timeout) def migrate_to_labels(self, project, _options): """ diff --git a/compose/const.py b/compose/const.py index f76fb572cd..709c3a10d7 100644 --- a/compose/const.py +++ b/compose/const.py @@ -1,4 +1,5 @@ +DEFAULT_TIMEOUT = 10 LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number' LABEL_ONE_OFF = 'com.docker.compose.oneoff' LABEL_PROJECT = 'com.docker.compose.project' diff --git a/compose/project.py b/compose/project.py index bc093628c4..b4ed12ea4e 100644 --- a/compose/project.py +++ b/compose/project.py @@ -6,7 +6,7 @@ from functools import reduce from docker.errors import APIError from .config import get_service_name_from_net, ConfigurationError -from .const import LABEL_PROJECT, LABEL_SERVICE, LABEL_ONE_OFF +from .const import LABEL_PROJECT, LABEL_SERVICE, LABEL_ONE_OFF, DEFAULT_TIMEOUT from .service import Service from .container import Container from .legacy import check_for_legacy_containers @@ -211,7 +211,8 @@ class Project(object): allow_recreate=True, smart_recreate=False, insecure_registry=False, - do_build=True): + do_build=True, + timeout=DEFAULT_TIMEOUT): services = self.get_services(service_names, include_deps=start_deps) @@ -228,6 +229,7 @@ class Project(object): plans[service.name], insecure_registry=insecure_registry, do_build=do_build, + timeout=timeout ) ] diff --git a/compose/service.py b/compose/service.py index 1e91a9f233..53073ffbdb 100644 --- a/compose/service.py +++ b/compose/service.py @@ -13,6 +13,7 @@ from docker.utils import create_host_config, LogConfig from . import __version__ from .config import DOCKER_CONFIG_KEYS, merge_environment from .const import ( + DEFAULT_TIMEOUT, LABEL_CONTAINER_NUMBER, LABEL_ONE_OFF, LABEL_PROJECT, @@ -251,26 +252,6 @@ class Service(object): else: return self.options['image'] - def converge(self, - allow_recreate=True, - smart_recreate=False, - insecure_registry=False, - do_build=True): - """ - If a container for this service doesn't exist, create and start one. If there are - any, stop them, create+start new ones, and remove the old containers. - """ - plan = self.convergence_plan( - allow_recreate=allow_recreate, - smart_recreate=smart_recreate, - ) - - return self.execute_convergence_plan( - plan, - insecure_registry=insecure_registry, - do_build=do_build, - ) - def convergence_plan(self, allow_recreate=True, smart_recreate=False): @@ -311,7 +292,8 @@ class Service(object): def execute_convergence_plan(self, plan, insecure_registry=False, - do_build=True): + do_build=True, + timeout=DEFAULT_TIMEOUT): (action, containers) = plan if action == 'create': @@ -328,6 +310,7 @@ class Service(object): self.recreate_container( c, insecure_registry=insecure_registry, + timeout=timeout ) for c in containers ] @@ -349,7 +332,8 @@ class Service(object): def recreate_container(self, container, - insecure_registry=False): + insecure_registry=False, + timeout=DEFAULT_TIMEOUT): """Recreate a container. The original container is renamed to a temporary name so that data @@ -358,7 +342,7 @@ class Service(object): """ log.info("Recreating %s..." % container.name) try: - container.stop() + container.stop(timeout=timeout) except APIError as e: if (e.response.status_code == 500 and e.explanation diff --git a/tests/fixtures/build-ctx/Dockerfile b/tests/fixtures/build-ctx/Dockerfile index d1ceac6b74..dd864b8387 100644 --- a/tests/fixtures/build-ctx/Dockerfile +++ b/tests/fixtures/build-ctx/Dockerfile @@ -1,2 +1,3 @@ FROM busybox:latest +LABEL com.docker.compose.test_image=true CMD echo "success" diff --git a/tests/fixtures/dockerfile-with-volume/Dockerfile b/tests/fixtures/dockerfile-with-volume/Dockerfile index 6e5d0a55ee..0d376ec484 100644 --- a/tests/fixtures/dockerfile-with-volume/Dockerfile +++ b/tests/fixtures/dockerfile-with-volume/Dockerfile @@ -1,3 +1,4 @@ -FROM busybox +FROM busybox:latest +LABEL com.docker.compose.test_image=true VOLUME /data CMD top diff --git a/tests/fixtures/dockerfile_with_entrypoint/Dockerfile b/tests/fixtures/dockerfile_with_entrypoint/Dockerfile index 7d28d29332..e7454e59b0 100644 --- a/tests/fixtures/dockerfile_with_entrypoint/Dockerfile +++ b/tests/fixtures/dockerfile_with_entrypoint/Dockerfile @@ -1,2 +1,3 @@ FROM busybox:latest +LABEL com.docker.compose.test_image=true ENTRYPOINT echo "From prebuilt entrypoint" diff --git a/tests/fixtures/simple-dockerfile/Dockerfile b/tests/fixtures/simple-dockerfile/Dockerfile index d1ceac6b74..dd864b8387 100644 --- a/tests/fixtures/simple-dockerfile/Dockerfile +++ b/tests/fixtures/simple-dockerfile/Dockerfile @@ -1,2 +1,3 @@ FROM busybox:latest +LABEL com.docker.compose.test_image=true CMD echo "success" diff --git a/tests/integration/cli_test.py b/tests/integration/cli_test.py index cb7bc17fcb..f9b251e195 100644 --- a/tests/integration/cli_test.py +++ b/tests/integration/cli_test.py @@ -24,6 +24,7 @@ class CLITestCase(DockerClientTestCase): self.project.remove_stopped() for container in self.project.containers(stopped=True, one_off=True): container.remove(force=True) + super(CLITestCase, self).tearDown() @property def project(self): @@ -161,6 +162,19 @@ class CLITestCase(DockerClientTestCase): self.assertEqual(old_ids, new_ids) + def test_up_with_timeout(self): + self.command.dispatch(['up', '-d', '-t', '1'], None) + service = self.project.get_service('simple') + another = self.project.get_service('another') + self.assertEqual(len(service.containers()), 1) + self.assertEqual(len(another.containers()), 1) + + # Ensure containers don't have stdin and stdout connected in -d mode + config = service.containers()[0].inspect()['Config'] + self.assertFalse(config['AttachStderr']) + self.assertFalse(config['AttachStdout']) + self.assertFalse(config['AttachStdin']) + @patch('dockerpty.start') def test_run_service_without_links(self, mock_stdout): self.command.base_dir = 'tests/fixtures/links-composefile' @@ -207,13 +221,10 @@ class CLITestCase(DockerClientTestCase): self.assertEqual(old_ids, new_ids) @patch('dockerpty.start') - def test_run_without_command(self, __): + def test_run_without_command(self, _): self.command.base_dir = 'tests/fixtures/commands-composefile' self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test') - for c in self.project.containers(stopped=True, one_off=True): - c.remove() - self.command.dispatch(['run', 'implicit'], None) service = self.project.get_service('implicit') containers = service.containers(stopped=True, one_off=True) diff --git a/tests/integration/legacy_test.py b/tests/integration/legacy_test.py index 6c52b68d33..346c84f2e7 100644 --- a/tests/integration/legacy_test.py +++ b/tests/integration/legacy_test.py @@ -1,12 +1,15 @@ +from docker.errors import APIError + from compose import legacy from compose.project import Project from .testcases import DockerClientTestCase -class ProjectTest(DockerClientTestCase): +class LegacyTestCase(DockerClientTestCase): def setUp(self): - super(ProjectTest, self).setUp() + super(LegacyTestCase, self).setUp() + self.containers = [] db = self.create_service('db') web = self.create_service('web', links=[(db, 'db')]) @@ -23,12 +26,25 @@ class ProjectTest(DockerClientTestCase): **service.options ) self.client.start(container) + self.containers.append(container) # Create a single one-off legacy container - self.client.create_container( + self.containers.append(self.client.create_container( name='{}_{}_run_1'.format(self.project.name, self.services[0].name), **self.services[0].options - ) + )) + + def tearDown(self): + super(LegacyTestCase, self).tearDown() + for container in self.containers: + try: + self.client.kill(container) + except APIError: + pass + try: + self.client.remove_container(container) + except APIError: + pass def get_legacy_containers(self, **kwargs): return list(legacy.get_legacy_containers( diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 2976af823b..5e252526e5 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals + from compose import config +from compose.const import LABEL_PROJECT from compose.project import Project from compose.container import Container from .testcases import DockerClientTestCase @@ -55,6 +57,7 @@ class ProjectTest(DockerClientTestCase): image='busybox:latest', volumes=['/var/data'], name='composetest_data_container', + labels={LABEL_PROJECT: 'composetest'}, ) project = Project.from_dicts( name='composetest', @@ -69,9 +72,6 @@ class ProjectTest(DockerClientTestCase): db = project.get_service('db') self.assertEqual(db.volumes_from, [data_container]) - project.kill() - project.remove_stopped() - def test_net_from_service(self): project = Project.from_dicts( name='composetest', @@ -95,15 +95,13 @@ class ProjectTest(DockerClientTestCase): net = project.get_service('net') self.assertEqual(web._get_net(), 'container:' + net.containers()[0].id) - project.kill() - project.remove_stopped() - def test_net_from_container(self): net_container = Container.create( self.client, image='busybox:latest', name='composetest_net_container', - command='top' + command='top', + labels={LABEL_PROJECT: 'composetest'}, ) net_container.start() @@ -123,9 +121,6 @@ class ProjectTest(DockerClientTestCase): web = project.get_service('web') self.assertEqual(web._get_net(), 'container:' + net_container.id) - project.kill() - project.remove_stopped() - def test_start_stop_kill_remove(self): web = self.create_service('web') db = self.create_service('db') @@ -171,9 +166,6 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(len(db.containers()), 1) self.assertEqual(len(web.containers()), 0) - project.kill() - project.remove_stopped() - def test_project_up_starts_uncreated_services(self): db = self.create_service('db') web = self.create_service('web', links=[(db, 'db')]) @@ -205,9 +197,6 @@ class ProjectTest(DockerClientTestCase): self.assertNotEqual(db_container.id, old_db_id) self.assertEqual(db_container.get('Volumes./etc'), db_volume_path) - project.kill() - project.remove_stopped() - def test_project_up_with_no_recreate_running(self): web = self.create_service('web') db = self.create_service('db', volumes=['/var/db']) @@ -228,9 +217,6 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(db_container.inspect()['Volumes']['/var/db'], db_volume_path) - project.kill() - project.remove_stopped() - def test_project_up_with_no_recreate_stopped(self): web = self.create_service('web') db = self.create_service('db', volumes=['/var/db']) @@ -258,9 +244,6 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(db_container.inspect()['Volumes']['/var/db'], db_volume_path) - project.kill() - project.remove_stopped() - def test_project_up_without_all_services(self): console = self.create_service('console') db = self.create_service('db') @@ -273,9 +256,6 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(len(db.containers()), 1) self.assertEqual(len(console.containers()), 1) - project.kill() - project.remove_stopped() - def test_project_up_starts_links(self): console = self.create_service('console') db = self.create_service('db', volumes=['/var/db']) @@ -291,9 +271,6 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(len(db.containers()), 1) self.assertEqual(len(console.containers()), 0) - project.kill() - project.remove_stopped() - def test_project_up_starts_depends(self): project = Project.from_dicts( name='composetest', @@ -329,9 +306,6 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(len(project.get_service('data').containers()), 1) self.assertEqual(len(project.get_service('console').containers()), 0) - project.kill() - project.remove_stopped() - def test_project_up_with_no_deps(self): project = Project.from_dicts( name='composetest', @@ -368,9 +342,6 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1) self.assertEqual(len(project.get_service('console').containers()), 0) - project.kill() - project.remove_stopped() - def test_unscale_after_restart(self): web = self.create_service('web') project = Project('composetest', [web], self.client) @@ -395,5 +366,3 @@ class ProjectTest(DockerClientTestCase): project.up() service = project.get_service('web') self.assertEqual(len(service.containers()), 1) - project.kill() - project.remove_stopped() diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index 32de5fa478..3b3ac22f9b 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -2,8 +2,9 @@ from __future__ import unicode_literals from __future__ import absolute_import import os from os import path -import mock +from docker.errors import APIError +import mock import tempfile import shutil import six @@ -18,11 +19,11 @@ from compose.const import ( ) from compose.service import ( ConfigError, + ConvergencePlan, Service, build_extra_hosts, ) from compose.container import Container -from docker.errors import APIError from .testcases import DockerClientTestCase @@ -235,7 +236,12 @@ class ServiceTest(DockerClientTestCase): def test_create_container_with_volumes_from(self): volume_service = self.create_service('data') volume_container_1 = volume_service.create_container() - volume_container_2 = Container.create(self.client, image='busybox:latest', command=["top"]) + volume_container_2 = Container.create( + self.client, + image='busybox:latest', + command=["top"], + labels={LABEL_PROJECT: 'composetest'}, + ) host_service = self.create_service('host', volumes_from=[volume_service, volume_container_2]) host_container = host_service.create_container() host_service.start_container(host_container) @@ -244,7 +250,7 @@ class ServiceTest(DockerClientTestCase): self.assertIn(volume_container_2.id, host_container.get('HostConfig.VolumesFrom')) - def test_converge(self): + def test_execute_convergence_plan_recreate(self): service = self.create_service( 'db', environment={'FOO': '1'}, @@ -264,7 +270,8 @@ class ServiceTest(DockerClientTestCase): num_containers_before = len(self.client.containers(all=True)) service.options['environment']['FOO'] = '2' - new_container = service.converge()[0] + new_container, = service.execute_convergence_plan( + ConvergencePlan('recreate', [old_container])) self.assertEqual(new_container.get('Config.Entrypoint'), ['top']) self.assertEqual(new_container.get('Config.Cmd'), ['-d', '1']) @@ -281,7 +288,7 @@ class ServiceTest(DockerClientTestCase): self.client.inspect_container, old_container.id) - def test_converge_when_containers_are_stopped(self): + def test_execute_convergence_plan_when_containers_are_stopped(self): service = self.create_service( 'db', environment={'FOO': '1'}, @@ -290,11 +297,21 @@ class ServiceTest(DockerClientTestCase): command=['-d', '1'] ) service.create_container() - self.assertEqual(len(service.containers(stopped=True)), 1) - service.converge() - self.assertEqual(len(service.containers(stopped=True)), 1) - def test_converge_with_image_declared_volume(self): + containers = service.containers(stopped=True) + self.assertEqual(len(containers), 1) + container, = containers + self.assertFalse(container.is_running) + + service.execute_convergence_plan(ConvergencePlan('start', [container])) + + containers = service.containers() + self.assertEqual(len(containers), 1) + container.inspect() + self.assertEqual(container, containers[0]) + self.assertTrue(container.is_running) + + def test_execute_convergence_plan_with_image_declared_volume(self): service = Service( project='composetest', name='db', @@ -306,7 +323,8 @@ class ServiceTest(DockerClientTestCase): self.assertEqual(old_container.get('Volumes').keys(), ['/data']) volume_path = old_container.get('Volumes')['/data'] - new_container = service.converge()[0] + new_container, = service.execute_convergence_plan( + ConvergencePlan('recreate', [old_container])) self.assertEqual(new_container.get('Volumes').keys(), ['/data']) self.assertEqual(new_container.get('Volumes')['/data'], volume_path) @@ -408,7 +426,7 @@ class ServiceTest(DockerClientTestCase): self.assertEqual(len(self.client.images(name='composetest_test')), 1) def test_start_container_uses_tagged_image_if_it_exists(self): - self.client.build('tests/fixtures/simple-dockerfile', tag='composetest_test') + self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test') service = Service( name='test', client=self.client, diff --git a/tests/integration/state_test.py b/tests/integration/state_test.py index 7a7d2b58fc..cd59d13c92 100644 --- a/tests/integration/state_test.py +++ b/tests/integration/state_test.py @@ -12,8 +12,8 @@ from .testcases import DockerClientTestCase class ProjectTestCase(DockerClientTestCase): def run_up(self, cfg, **kwargs): - if 'smart_recreate' not in kwargs: - kwargs['smart_recreate'] = True + kwargs.setdefault('smart_recreate', True) + kwargs.setdefault('timeout', 0.1) project = self.make_project(cfg) project.up(**kwargs) @@ -153,7 +153,31 @@ class ProjectWithDependenciesTest(ProjectTestCase): self.assertEqual(new_containers - old_containers, set()) +def converge(service, + allow_recreate=True, + smart_recreate=False, + insecure_registry=False, + do_build=True): + """ + If a container for this service doesn't exist, create and start one. If there are + any, stop them, create+start new ones, and remove the old containers. + """ + plan = service.convergence_plan( + allow_recreate=allow_recreate, + smart_recreate=smart_recreate, + ) + + return service.execute_convergence_plan( + plan, + insecure_registry=insecure_registry, + do_build=do_build, + timeout=0.1, + ) + + class ServiceStateTest(DockerClientTestCase): + """Test cases for Service.convergence_plan.""" + def test_trigger_create(self): web = self.create_service('web') self.assertEqual(('create', []), web.convergence_plan(smart_recreate=True)) @@ -216,18 +240,19 @@ class ServiceStateTest(DockerClientTestCase): def test_trigger_recreate_with_build(self): context = tempfile.mkdtemp() + base_image = "FROM busybox\nLABEL com.docker.compose.test_image=true\n" try: dockerfile = os.path.join(context, 'Dockerfile') with open(dockerfile, 'w') as f: - f.write('FROM busybox\n') + f.write(base_image) web = self.create_service('web', build=context) container = web.create_container() with open(dockerfile, 'w') as f: - f.write('FROM busybox\nCMD echo hello world\n') + f.write(base_image + 'CMD echo hello world\n') web.build() web = self.create_service('web', build=context) @@ -249,15 +274,15 @@ class ConfigHashTest(DockerClientTestCase): def test_config_hash_with_custom_labels(self): web = self.create_service('web', labels={'foo': '1'}) - container = web.converge()[0] + container = converge(web)[0] self.assertIn(LABEL_CONFIG_HASH, container.labels) self.assertIn('foo', container.labels) def test_config_hash_sticks_around(self): web = self.create_service('web', command=["top"]) - container = web.converge()[0] + container = converge(web)[0] self.assertIn(LABEL_CONFIG_HASH, container.labels) web = self.create_service('web', command=["top", "-d", "1"]) - container = web.converge()[0] + container = converge(web)[0] self.assertIn(LABEL_CONFIG_HASH, container.labels) diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index 48fcf3ef29..98c5876eb1 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from __future__ import absolute_import from compose.service import Service from compose.config import make_service_dict +from compose.const import LABEL_PROJECT from compose.cli.docker_client import docker_client from compose.progress_stream import stream_output from .. import unittest @@ -12,15 +13,15 @@ class DockerClientTestCase(unittest.TestCase): def setUpClass(cls): cls.client = docker_client() - # TODO: update to use labels in #652 - def setUp(self): - for c in self.client.containers(all=True): - if c['Names'] and 'composetest' in c['Names'][0]: - self.client.kill(c['Id']) - self.client.remove_container(c['Id']) - for i in self.client.images(): - if isinstance(i.get('Tag'), basestring) and 'composetest' in i['Tag']: - self.client.remove_image(i) + def tearDown(self): + for c in self.client.containers( + all=True, + filters={'label': '%s=composetest' % LABEL_PROJECT}): + self.client.kill(c['Id']) + self.client.remove_container(c['Id']) + for i in self.client.images( + filters={'label': 'com.docker.compose.test_image'}): + self.client.remove_image(i) def create_service(self, name, **kwargs): if 'image' not in kwargs and 'build' not in kwargs: @@ -36,5 +37,6 @@ class DockerClientTestCase(unittest.TestCase): ) def check_build(self, *args, **kwargs): + kwargs.setdefault('rm', True) build_output = self.client.build(*args, **kwargs) stream_output(build_output, open('/dev/null', 'w')) diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index fb3a7fcbb9..82ea041019 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -246,7 +246,7 @@ class ServiceTest(unittest.TestCase): service.image = lambda: {'Id': 'abc123'} new_container = service.recreate_container(mock_container) - mock_container.stop.assert_called_once_with() + mock_container.stop.assert_called_once_with(timeout=10) self.mock_client.rename.assert_called_once_with( mock_container.id, '%s_%s' % (mock_container.short_id, mock_container.name)) @@ -254,6 +254,15 @@ class ServiceTest(unittest.TestCase): new_container.start.assert_called_once_with() mock_container.remove.assert_called_once_with() + @mock.patch('compose.service.Container', autospec=True) + def test_recreate_container_with_timeout(self, _): + mock_container = mock.create_autospec(Container) + 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) + + mock_container.stop.assert_called_once_with(timeout=1) + def test_parse_repository_tag(self): self.assertEqual(parse_repository_tag("root"), ("root", "")) self.assertEqual(parse_repository_tag("root:tag"), ("root", "tag"))