From b54b932b54ea39054aeaab1273c8570001b90804 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 2 Sep 2015 11:53:37 -0700 Subject: [PATCH 1/6] Exit gracefully when requests encounter a ReadTimeout exception. Signed-off-by: Joffrey F --- compose/cli/docker_client.py | 2 +- compose/cli/main.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/compose/cli/docker_client.py b/compose/cli/docker_client.py index ad67d563..91e4059c 100644 --- a/compose/cli/docker_client.py +++ b/compose/cli/docker_client.py @@ -34,5 +34,5 @@ def docker_client(): ca_cert=ca_cert, ) - timeout = int(os.environ.get('DOCKER_CLIENT_TIMEOUT', 60)) + timeout = int(os.environ.get('COMPOSE_HTTP_TIMEOUT', os.environ.get('DOCKER_CLIENT_TIMEOUT', 60))) return Client(base_url=base_url, tls=tls_config, version=api_version, timeout=timeout) diff --git a/compose/cli/main.py b/compose/cli/main.py index 2ace13c2..116c8300 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -10,6 +10,7 @@ from operator import attrgetter import dockerpty from docker.errors import APIError +from requests.exceptions import ReadTimeout from .. import __version__ from .. import legacy @@ -65,6 +66,12 @@ def main(): except NeedsBuildError as e: log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name) sys.exit(1) + except ReadTimeout as e: + log.error( + "HTTP request took too long to complete. Retry with --verbose to obtain debug information.\n" + "If you encounter this issue regularly because of slow network conditions, consider setting " + "COMPOSE_HTTP_TIMEOUT to a higher value." + ) def setup_logging(): From 80c909299965243800401f061c17afc56a689cdd Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 2 Sep 2015 12:52:48 -0700 Subject: [PATCH 2/6] Document COMPOSE_HTTP_TIMEOUT env config Signed-off-by: Joffrey F --- docs/reference/overview.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/reference/overview.md b/docs/reference/overview.md index 7425aa5e..52598737 100644 --- a/docs/reference/overview.md +++ b/docs/reference/overview.md @@ -44,6 +44,11 @@ the `docker` daemon. Configures the path to the `ca.pem`, `cert.pem`, and `key.pem` files used for TLS verification. Defaults to `~/.docker`. +### COMPOSE\_HTTP\_TIMEOUT + +Configures the time (in seconds) a request to the Docker daemon is allowed to hang before Compose considers +it failed. Defaults to 60 seconds. + From e634fe3fd6a768bcd5818dfb81b4c11cdc460853 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 2 Sep 2015 14:28:57 -0700 Subject: [PATCH 3/6] Deprecation warning when DOCKER_CLIENT_TIMEOUT is used Signed-off-by: Joffrey F --- compose/cli/docker_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compose/cli/docker_client.py b/compose/cli/docker_client.py index 91e4059c..e16549e8 100644 --- a/compose/cli/docker_client.py +++ b/compose/cli/docker_client.py @@ -1,9 +1,12 @@ +import logging import os import ssl from docker import Client from docker import tls +log = logging.getLogger(__name__) + def docker_client(): """ @@ -34,5 +37,8 @@ def docker_client(): ca_cert=ca_cert, ) + if 'DOCKER_CLIENT_TIMEOUT' in os.environ: + log.warn('The DOCKER_CLIENT_TIMEOUT environment variable is deprecated. Please use COMPOSE_HTTP_TIMEOUT instead.') timeout = int(os.environ.get('COMPOSE_HTTP_TIMEOUT', os.environ.get('DOCKER_CLIENT_TIMEOUT', 60))) + return Client(base_url=base_url, tls=tls_config, version=api_version, timeout=timeout) From b110bbe9e3f2c69e8f1dc8e990d16f4b016da955 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 2 Sep 2015 14:31:30 -0700 Subject: [PATCH 4/6] Refined error message when timeout is encountered. Signed-off-by: Joffrey F --- compose/cli/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 116c8300..66187429 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -2,6 +2,7 @@ from __future__ import print_function from __future__ import unicode_literals import logging +import os import re import signal import sys @@ -68,9 +69,9 @@ def main(): sys.exit(1) except ReadTimeout as e: log.error( - "HTTP request took too long to complete. Retry with --verbose to obtain debug information.\n" + "An HTTP request took too long to complete. Retry with --verbose to obtain debug information.\n" "If you encounter this issue regularly because of slow network conditions, consider setting " - "COMPOSE_HTTP_TIMEOUT to a higher value." + "COMPOSE_HTTP_TIMEOUT to a higher value (current value: %s)." % os.environ.get('COMPOSE_HTTP_TIMEOUT', 60) ) From f9c7346380dc9c3da7f465b8ac542673700db837 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 2 Sep 2015 14:52:47 -0700 Subject: [PATCH 5/6] HTTP_TIMEOUT as importable constant for consistency Signed-off-by: Joffrey F --- compose/cli/docker_client.py | 5 +++-- compose/cli/main.py | 4 ++-- compose/const.py | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compose/cli/docker_client.py b/compose/cli/docker_client.py index e16549e8..601b0b9a 100644 --- a/compose/cli/docker_client.py +++ b/compose/cli/docker_client.py @@ -5,6 +5,8 @@ import ssl from docker import Client from docker import tls +from ..const import HTTP_TIMEOUT + log = logging.getLogger(__name__) @@ -39,6 +41,5 @@ def docker_client(): if 'DOCKER_CLIENT_TIMEOUT' in os.environ: log.warn('The DOCKER_CLIENT_TIMEOUT environment variable is deprecated. Please use COMPOSE_HTTP_TIMEOUT instead.') - timeout = int(os.environ.get('COMPOSE_HTTP_TIMEOUT', os.environ.get('DOCKER_CLIENT_TIMEOUT', 60))) - return Client(base_url=base_url, tls=tls_config, version=api_version, timeout=timeout) + return Client(base_url=base_url, tls=tls_config, version=api_version, timeout=HTTP_TIMEOUT) diff --git a/compose/cli/main.py b/compose/cli/main.py index 66187429..13a8cef2 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -2,7 +2,6 @@ from __future__ import print_function from __future__ import unicode_literals import logging -import os import re import signal import sys @@ -17,6 +16,7 @@ from .. import __version__ from .. import legacy from ..config import parse_environment from ..const import DEFAULT_TIMEOUT +from ..const import HTTP_TIMEOUT from ..progress_stream import StreamOutputError from ..project import ConfigurationError from ..project import NoSuchService @@ -71,7 +71,7 @@ def main(): log.error( "An HTTP request took too long to complete. Retry with --verbose to obtain debug information.\n" "If you encounter this issue regularly because of slow network conditions, consider setting " - "COMPOSE_HTTP_TIMEOUT to a higher value (current value: %s)." % os.environ.get('COMPOSE_HTTP_TIMEOUT', 60) + "COMPOSE_HTTP_TIMEOUT to a higher value (current value: %s)." % HTTP_TIMEOUT ) diff --git a/compose/const.py b/compose/const.py index 709c3a10..dbfa56b8 100644 --- a/compose/const.py +++ b/compose/const.py @@ -1,3 +1,4 @@ +import os DEFAULT_TIMEOUT = 10 LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number' @@ -6,3 +7,4 @@ LABEL_PROJECT = 'com.docker.compose.project' LABEL_SERVICE = 'com.docker.compose.service' LABEL_VERSION = 'com.docker.compose.version' LABEL_CONFIG_HASH = 'com.docker.compose.config-hash' +HTTP_TIMEOUT = int(os.environ.get('COMPOSE_HTTP_TIMEOUT', os.environ.get('DOCKER_CLIENT_TIMEOUT', 60))) From 6a95f6d628933299ba0531b87a38c7f9a0c5dcc3 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 2 Sep 2015 18:00:08 -0700 Subject: [PATCH 6/6] custom timeout test rewrite Signed-off-by: Joffrey F --- tests/unit/cli/docker_client_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/cli/docker_client_test.py b/tests/unit/cli/docker_client_test.py index 5ccde73a..d497495b 100644 --- a/tests/unit/cli/docker_client_test.py +++ b/tests/unit/cli/docker_client_test.py @@ -16,7 +16,7 @@ class DockerClientTestCase(unittest.TestCase): docker_client.docker_client() def test_docker_client_with_custom_timeout(self): - with mock.patch.dict(os.environ): - os.environ['DOCKER_CLIENT_TIMEOUT'] = timeout = "300" + timeout = 300 + with mock.patch('compose.cli.docker_client.HTTP_TIMEOUT', 300): client = docker_client.docker_client() - self.assertEqual(client.timeout, int(timeout)) + self.assertEqual(client.timeout, int(timeout))