mirror of
https://github.com/docker/compose.git
synced 2025-05-03 14:10:18 +02:00
Fix race condition with up and setting signal handlers.
Also print stdout on wait_for_container(). Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
e925b8272b
commit
bbaae11a0f
@ -2,6 +2,7 @@ from __future__ import absolute_import
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
@ -53,7 +54,7 @@ def main():
|
|||||||
command = TopLevelCommand()
|
command = TopLevelCommand()
|
||||||
command.sys_dispatch()
|
command.sys_dispatch()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
log.error("\nAborting.")
|
log.error("Aborting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except (UserError, NoSuchService, ConfigurationError) as e:
|
except (UserError, NoSuchService, ConfigurationError) as e:
|
||||||
log.error(e.msg)
|
log.error(e.msg)
|
||||||
@ -629,18 +630,20 @@ class TopLevelCommand(DocoptCommand):
|
|||||||
if detached and cascade_stop:
|
if detached and cascade_stop:
|
||||||
raise UserError("--abort-on-container-exit and -d cannot be combined.")
|
raise UserError("--abort-on-container-exit and -d cannot be combined.")
|
||||||
|
|
||||||
to_attach = project.up(
|
with up_shutdown_context(project, service_names, timeout, detached):
|
||||||
service_names=service_names,
|
to_attach = project.up(
|
||||||
start_deps=start_deps,
|
service_names=service_names,
|
||||||
strategy=convergence_strategy_from_opts(options),
|
start_deps=start_deps,
|
||||||
do_build=not options['--no-build'],
|
strategy=convergence_strategy_from_opts(options),
|
||||||
timeout=timeout,
|
do_build=not options['--no-build'],
|
||||||
detached=detached
|
timeout=timeout,
|
||||||
)
|
detached=detached)
|
||||||
|
|
||||||
if not detached:
|
if detached:
|
||||||
|
return
|
||||||
log_printer = build_log_printer(to_attach, service_names, monochrome, cascade_stop)
|
log_printer = build_log_printer(to_attach, service_names, monochrome, cascade_stop)
|
||||||
attach_to_logs(project, log_printer, service_names, timeout)
|
print("Attaching to", list_containers(log_printer.containers))
|
||||||
|
log_printer.run()
|
||||||
|
|
||||||
def version(self, project, options):
|
def version(self, project, options):
|
||||||
"""
|
"""
|
||||||
@ -740,13 +743,16 @@ def build_log_printer(containers, service_names, monochrome, cascade_stop):
|
|||||||
return LogPrinter(containers, monochrome=monochrome, cascade_stop=cascade_stop)
|
return LogPrinter(containers, monochrome=monochrome, cascade_stop=cascade_stop)
|
||||||
|
|
||||||
|
|
||||||
def attach_to_logs(project, log_printer, service_names, timeout):
|
@contextlib.contextmanager
|
||||||
print("Attaching to", list_containers(log_printer.containers))
|
def up_shutdown_context(project, service_names, timeout, detached):
|
||||||
signals.set_signal_handler_to_shutdown()
|
if detached:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
|
||||||
|
signals.set_signal_handler_to_shutdown()
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
log_printer.run()
|
yield
|
||||||
except signals.ShutdownException:
|
except signals.ShutdownException:
|
||||||
print("Gracefully stopping... (press Ctrl+C again to force)")
|
print("Gracefully stopping... (press Ctrl+C again to force)")
|
||||||
project.stop(service_names=service_names, timeout=timeout)
|
project.stop(service_names=service_names, timeout=timeout)
|
||||||
|
@ -43,7 +43,8 @@ def start_process(base_dir, options):
|
|||||||
def wait_on_process(proc, returncode=0):
|
def wait_on_process(proc, returncode=0):
|
||||||
stdout, stderr = proc.communicate()
|
stdout, stderr = proc.communicate()
|
||||||
if proc.returncode != returncode:
|
if proc.returncode != returncode:
|
||||||
print(stderr.decode('utf-8'))
|
print("Stderr: {}".format(stderr))
|
||||||
|
print("Stdout: {}".format(stdout))
|
||||||
assert proc.returncode == returncode
|
assert proc.returncode == returncode
|
||||||
return ProcessResult(stdout.decode('utf-8'), stderr.decode('utf-8'))
|
return ProcessResult(stdout.decode('utf-8'), stderr.decode('utf-8'))
|
||||||
|
|
||||||
@ -81,7 +82,6 @@ class ContainerStateCondition(object):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.running = running
|
self.running = running
|
||||||
|
|
||||||
# State.Running == true
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
try:
|
try:
|
||||||
container = self.client.inspect_container(self.name)
|
container = self.client.inspect_container(self.name)
|
||||||
@ -707,6 +707,17 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
os.kill(proc.pid, signal.SIGTERM)
|
os.kill(proc.pid, signal.SIGTERM)
|
||||||
wait_on_condition(ContainerCountCondition(self.project, 0))
|
wait_on_condition(ContainerCountCondition(self.project, 0))
|
||||||
|
|
||||||
|
@v2_only()
|
||||||
|
def test_up_handles_force_shutdown(self):
|
||||||
|
self.base_dir = 'tests/fixtures/sleeps-composefile'
|
||||||
|
proc = start_process(self.base_dir, ['up', '-t', '200'])
|
||||||
|
wait_on_condition(ContainerCountCondition(self.project, 2))
|
||||||
|
|
||||||
|
os.kill(proc.pid, signal.SIGTERM)
|
||||||
|
time.sleep(0.1)
|
||||||
|
os.kill(proc.pid, signal.SIGTERM)
|
||||||
|
wait_on_condition(ContainerCountCondition(self.project, 0))
|
||||||
|
|
||||||
def test_run_service_without_links(self):
|
def test_run_service_without_links(self):
|
||||||
self.base_dir = 'tests/fixtures/links-composefile'
|
self.base_dir = 'tests/fixtures/links-composefile'
|
||||||
self.dispatch(['run', 'console', '/bin/true'])
|
self.dispatch(['run', 'console', '/bin/true'])
|
||||||
|
10
tests/fixtures/sleeps-composefile/docker-compose.yml
vendored
Normal file
10
tests/fixtures/sleeps-composefile/docker-compose.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
services:
|
||||||
|
simple:
|
||||||
|
image: busybox:latest
|
||||||
|
command: sleep 200
|
||||||
|
another:
|
||||||
|
image: busybox:latest
|
||||||
|
command: sleep 200
|
@ -6,12 +6,9 @@ import logging
|
|||||||
from compose import container
|
from compose import container
|
||||||
from compose.cli.errors import UserError
|
from compose.cli.errors import UserError
|
||||||
from compose.cli.formatter import ConsoleWarningFormatter
|
from compose.cli.formatter import ConsoleWarningFormatter
|
||||||
from compose.cli.log_printer import LogPrinter
|
|
||||||
from compose.cli.main import attach_to_logs
|
|
||||||
from compose.cli.main import build_log_printer
|
from compose.cli.main import build_log_printer
|
||||||
from compose.cli.main import convergence_strategy_from_opts
|
from compose.cli.main import convergence_strategy_from_opts
|
||||||
from compose.cli.main import setup_console_handler
|
from compose.cli.main import setup_console_handler
|
||||||
from compose.project import Project
|
|
||||||
from compose.service import ConvergenceStrategy
|
from compose.service import ConvergenceStrategy
|
||||||
from tests import mock
|
from tests import mock
|
||||||
from tests import unittest
|
from tests import unittest
|
||||||
@ -49,21 +46,6 @@ class CLIMainTestCase(unittest.TestCase):
|
|||||||
log_printer = build_log_printer(containers, service_names, True, False)
|
log_printer = build_log_printer(containers, service_names, True, False)
|
||||||
self.assertEqual(log_printer.containers, containers)
|
self.assertEqual(log_printer.containers, containers)
|
||||||
|
|
||||||
def test_attach_to_logs(self):
|
|
||||||
project = mock.create_autospec(Project)
|
|
||||||
log_printer = mock.create_autospec(LogPrinter, containers=[])
|
|
||||||
service_names = ['web', 'db']
|
|
||||||
timeout = 12
|
|
||||||
|
|
||||||
with mock.patch('compose.cli.main.signals.signal', autospec=True) as mock_signal:
|
|
||||||
attach_to_logs(project, log_printer, service_names, timeout)
|
|
||||||
|
|
||||||
assert mock_signal.signal.mock_calls == [
|
|
||||||
mock.call(mock_signal.SIGINT, mock.ANY),
|
|
||||||
mock.call(mock_signal.SIGTERM, mock.ANY),
|
|
||||||
]
|
|
||||||
log_printer.run.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
class SetupConsoleHandlerTestCase(unittest.TestCase):
|
class SetupConsoleHandlerTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user