Move converge() to a test module, and use a short timeout for tests.

Signed-off-by: Daniel Nephin <dnephin@gmail.com>
This commit is contained in:
Daniel Nephin 2015-06-14 17:11:29 -04:00
parent c24d5380e6
commit 06db577105
7 changed files with 69 additions and 52 deletions

View File

@ -10,7 +10,9 @@ import sys
from docker.errors import APIError from docker.errors import APIError
import dockerpty import dockerpty
from .. import __version__, legacy from .. import __version__
from .. import legacy
from ..const import DEFAULT_TIMEOUT
from ..project import NoSuchService, ConfigurationError from ..project import NoSuchService, ConfigurationError
from ..service import BuildError, NeedsBuildError from ..service import BuildError, NeedsBuildError
from ..config import parse_environment from ..config import parse_environment
@ -394,9 +396,8 @@ class TopLevelCommand(Command):
-t, --timeout TIMEOUT Specify a shutdown timeout in seconds. -t, --timeout TIMEOUT Specify a shutdown timeout in seconds.
(default: 10) (default: 10)
""" """
timeout = options.get('--timeout') timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT)
params = {} if timeout is None else {'timeout': int(timeout)} project.stop(service_names=options['SERVICE'], timeout=timeout)
project.stop(service_names=options['SERVICE'], **params)
def restart(self, project, options): def restart(self, project, options):
""" """
@ -408,9 +409,8 @@ class TopLevelCommand(Command):
-t, --timeout TIMEOUT Specify a shutdown timeout in seconds. -t, --timeout TIMEOUT Specify a shutdown timeout in seconds.
(default: 10) (default: 10)
""" """
timeout = options.get('--timeout') timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT)
params = {} if timeout is None else {'timeout': int(timeout)} project.restart(service_names=options['SERVICE'], timeout=timeout)
project.restart(service_names=options['SERVICE'], **params)
def up(self, project, options): def up(self, project, options):
""" """
@ -452,7 +452,7 @@ class TopLevelCommand(Command):
allow_recreate = not options['--no-recreate'] allow_recreate = not options['--no-recreate']
smart_recreate = options['--x-smart-recreate'] smart_recreate = options['--x-smart-recreate']
service_names = options['SERVICE'] service_names = options['SERVICE']
timeout = int(options['--timeout']) if options['--timeout'] is not None else None timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT)
project.up( project.up(
service_names=service_names, service_names=service_names,
@ -479,8 +479,7 @@ class TopLevelCommand(Command):
signal.signal(signal.SIGINT, handler) signal.signal(signal.SIGINT, handler)
print("Gracefully stopping... (press Ctrl+C again to force)") print("Gracefully stopping... (press Ctrl+C again to force)")
params = {} if timeout is None else {'timeout': timeout} project.stop(service_names=service_names, timeout=timeout)
project.stop(service_names=service_names, **params)
def migrate_to_labels(self, project, _options): def migrate_to_labels(self, project, _options):
""" """

View File

@ -1,4 +1,5 @@
DEFAULT_TIMEOUT = 10
LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number' LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number'
LABEL_ONE_OFF = 'com.docker.compose.oneoff' LABEL_ONE_OFF = 'com.docker.compose.oneoff'
LABEL_PROJECT = 'com.docker.compose.project' LABEL_PROJECT = 'com.docker.compose.project'

View File

@ -6,7 +6,7 @@ from functools import reduce
from docker.errors import APIError from docker.errors import APIError
from .config import get_service_name_from_net, ConfigurationError 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 .service import Service
from .container import Container from .container import Container
from .legacy import check_for_legacy_containers from .legacy import check_for_legacy_containers
@ -212,7 +212,7 @@ class Project(object):
smart_recreate=False, smart_recreate=False,
insecure_registry=False, insecure_registry=False,
do_build=True, do_build=True,
timeout=None): timeout=DEFAULT_TIMEOUT):
services = self.get_services(service_names, include_deps=start_deps) services = self.get_services(service_names, include_deps=start_deps)

View File

@ -13,6 +13,7 @@ from docker.utils import create_host_config, LogConfig
from . import __version__ from . import __version__
from .config import DOCKER_CONFIG_KEYS, merge_environment from .config import DOCKER_CONFIG_KEYS, merge_environment
from .const import ( from .const import (
DEFAULT_TIMEOUT,
LABEL_CONTAINER_NUMBER, LABEL_CONTAINER_NUMBER,
LABEL_ONE_OFF, LABEL_ONE_OFF,
LABEL_PROJECT, LABEL_PROJECT,
@ -251,26 +252,6 @@ class Service(object):
else: else:
return self.options['image'] 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, def convergence_plan(self,
allow_recreate=True, allow_recreate=True,
smart_recreate=False): smart_recreate=False):
@ -312,7 +293,7 @@ class Service(object):
plan, plan,
insecure_registry=False, insecure_registry=False,
do_build=True, do_build=True,
timeout=None): timeout=DEFAULT_TIMEOUT):
(action, containers) = plan (action, containers) = plan
if action == 'create': if action == 'create':
@ -352,7 +333,7 @@ class Service(object):
def recreate_container(self, def recreate_container(self,
container, container,
insecure_registry=False, insecure_registry=False,
timeout=None): timeout=DEFAULT_TIMEOUT):
"""Recreate a container. """Recreate a container.
The original container is renamed to a temporary name so that data The original container is renamed to a temporary name so that data
@ -361,8 +342,7 @@ class Service(object):
""" """
log.info("Recreating %s..." % container.name) log.info("Recreating %s..." % container.name)
try: try:
stop_params = {} if timeout is None else {'timeout': timeout} container.stop(timeout=timeout)
container.stop(**stop_params)
except APIError as e: except APIError as e:
if (e.response.status_code == 500 if (e.response.status_code == 500
and e.explanation and e.explanation

View File

@ -2,8 +2,9 @@ from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
import os import os
from os import path from os import path
import mock
from docker.errors import APIError
import mock
import tempfile import tempfile
import shutil import shutil
import six import six
@ -18,11 +19,11 @@ from compose.const import (
) )
from compose.service import ( from compose.service import (
ConfigError, ConfigError,
ConvergencePlan,
Service, Service,
build_extra_hosts, build_extra_hosts,
) )
from compose.container import Container from compose.container import Container
from docker.errors import APIError
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
@ -249,7 +250,7 @@ class ServiceTest(DockerClientTestCase):
self.assertIn(volume_container_2.id, self.assertIn(volume_container_2.id,
host_container.get('HostConfig.VolumesFrom')) host_container.get('HostConfig.VolumesFrom'))
def test_converge(self): def test_execute_convergence_plan_recreate(self):
service = self.create_service( service = self.create_service(
'db', 'db',
environment={'FOO': '1'}, environment={'FOO': '1'},
@ -269,7 +270,8 @@ class ServiceTest(DockerClientTestCase):
num_containers_before = len(self.client.containers(all=True)) num_containers_before = len(self.client.containers(all=True))
service.options['environment']['FOO'] = '2' 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.Entrypoint'), ['top'])
self.assertEqual(new_container.get('Config.Cmd'), ['-d', '1']) self.assertEqual(new_container.get('Config.Cmd'), ['-d', '1'])
@ -286,7 +288,7 @@ class ServiceTest(DockerClientTestCase):
self.client.inspect_container, self.client.inspect_container,
old_container.id) 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( service = self.create_service(
'db', 'db',
environment={'FOO': '1'}, environment={'FOO': '1'},
@ -295,11 +297,21 @@ class ServiceTest(DockerClientTestCase):
command=['-d', '1'] command=['-d', '1']
) )
service.create_container() 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( service = Service(
project='composetest', project='composetest',
name='db', name='db',
@ -311,7 +323,8 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(old_container.get('Volumes').keys(), ['/data']) self.assertEqual(old_container.get('Volumes').keys(), ['/data'])
volume_path = old_container.get('Volumes')['/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').keys(), ['/data'])
self.assertEqual(new_container.get('Volumes')['/data'], volume_path) self.assertEqual(new_container.get('Volumes')['/data'], volume_path)

View File

@ -12,8 +12,8 @@ from .testcases import DockerClientTestCase
class ProjectTestCase(DockerClientTestCase): class ProjectTestCase(DockerClientTestCase):
def run_up(self, cfg, **kwargs): def run_up(self, cfg, **kwargs):
if 'smart_recreate' not in kwargs: kwargs.setdefault('smart_recreate', True)
kwargs['smart_recreate'] = True kwargs.setdefault('timeout', 0.1)
project = self.make_project(cfg) project = self.make_project(cfg)
project.up(**kwargs) project.up(**kwargs)
@ -153,7 +153,31 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.assertEqual(new_containers - old_containers, set()) 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): class ServiceStateTest(DockerClientTestCase):
"""Test cases for Service.convergence_plan."""
def test_trigger_create(self): def test_trigger_create(self):
web = self.create_service('web') web = self.create_service('web')
self.assertEqual(('create', []), web.convergence_plan(smart_recreate=True)) self.assertEqual(('create', []), web.convergence_plan(smart_recreate=True))
@ -250,15 +274,15 @@ class ConfigHashTest(DockerClientTestCase):
def test_config_hash_with_custom_labels(self): def test_config_hash_with_custom_labels(self):
web = self.create_service('web', labels={'foo': '1'}) 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(LABEL_CONFIG_HASH, container.labels)
self.assertIn('foo', container.labels) self.assertIn('foo', container.labels)
def test_config_hash_sticks_around(self): def test_config_hash_sticks_around(self):
web = self.create_service('web', command=["top"]) web = self.create_service('web', command=["top"])
container = web.converge()[0] container = converge(web)[0]
self.assertIn(LABEL_CONFIG_HASH, container.labels) self.assertIn(LABEL_CONFIG_HASH, container.labels)
web = self.create_service('web', command=["top", "-d", "1"]) web = self.create_service('web', command=["top", "-d", "1"])
container = web.converge()[0] container = converge(web)[0]
self.assertIn(LABEL_CONFIG_HASH, container.labels) self.assertIn(LABEL_CONFIG_HASH, container.labels)

View File

@ -246,7 +246,7 @@ class ServiceTest(unittest.TestCase):
service.image = lambda: {'Id': 'abc123'} service.image = lambda: {'Id': 'abc123'}
new_container = service.recreate_container(mock_container) 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( self.mock_client.rename.assert_called_once_with(
mock_container.id, mock_container.id,
'%s_%s' % (mock_container.short_id, mock_container.name)) '%s_%s' % (mock_container.short_id, mock_container.name))