diff options
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | docker/api/swarm.py | 6 | ||||
-rw-r--r-- | docker/client.py | 15 | ||||
-rw-r--r-- | docker/constants.py | 1 | ||||
-rw-r--r-- | docker/errors.py | 5 | ||||
-rw-r--r-- | docker/transport/npipeconn.py | 10 | ||||
-rw-r--r-- | docker/transport/unixconn.py | 20 | ||||
-rw-r--r-- | tests/integration/container_test.py | 4 | ||||
-rw-r--r-- | tests/integration/errors_test.py | 17 | ||||
-rw-r--r-- | tests/integration/service_test.py | 10 | ||||
-rw-r--r-- | tests/integration/swarm_test.py | 11 |
11 files changed, 70 insertions, 37 deletions
@@ -35,11 +35,11 @@ unit-test-py3: build-py3 .PHONY: integration-test integration-test: build - docker run -v /var/run/docker.sock:/var/run/docker.sock docker-py py.test tests/integration + docker run -v /var/run/docker.sock:/var/run/docker.sock docker-py py.test tests/integration/${file} .PHONY: integration-test-py3 integration-test-py3: build-py3 - docker run -v /var/run/docker.sock:/var/run/docker.sock docker-py3 py.test tests/integration + docker run -v /var/run/docker.sock:/var/run/docker.sock docker-py3 py.test tests/integration/${file} .PHONY: integration-dind integration-dind: build build-py3 @@ -75,3 +75,7 @@ flake8: build .PHONY: docs docs: build-docs docker run -v `pwd`/docs:/home/docker-py/docs/ -p 8000:8000 docker-py-docs mkdocs serve -a 0.0.0.0:8000 + +.PHONY: shell +shell: build + docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker-py python diff --git a/docker/api/swarm.py b/docker/api/swarm.py index d099364..7481c67 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -1,5 +1,6 @@ -from .. import utils import logging +from six.moves import http_client +from .. import utils log = logging.getLogger(__name__) @@ -53,6 +54,9 @@ class SwarmApiMixin(object): def leave_swarm(self, force=False): url = self._url('/swarm/leave') response = self._post(url, params={'force': force}) + # Ignore "this node is not part of a swarm" error + if force and response.status_code == http_client.NOT_ACCEPTABLE: + return True self._raise_for_status(response) return True diff --git a/docker/client.py b/docker/client.py index 6e8b278..47ad09e 100644 --- a/docker/client.py +++ b/docker/client.py @@ -40,7 +40,8 @@ class Client( api.VolumeApiMixin): def __init__(self, base_url=None, version=None, timeout=constants.DEFAULT_TIMEOUT_SECONDS, tls=False, - user_agent=constants.DEFAULT_USER_AGENT): + user_agent=constants.DEFAULT_USER_AGENT, + num_pools=constants.DEFAULT_NUM_POOLS): super(Client, self).__init__() if tls and not base_url: @@ -58,7 +59,9 @@ class Client( base_url, constants.IS_WINDOWS_PLATFORM, tls=bool(tls) ) if base_url.startswith('http+unix://'): - self._custom_adapter = UnixAdapter(base_url, timeout) + self._custom_adapter = UnixAdapter( + base_url, timeout, num_pools=num_pools + ) self.mount('http+docker://', self._custom_adapter) self._unmount('http://', 'https://') self.base_url = 'http+docker://localunixsocket' @@ -68,7 +71,9 @@ class Client( 'The npipe:// protocol is only supported on Windows' ) try: - self._custom_adapter = NpipeAdapter(base_url, timeout) + self._custom_adapter = NpipeAdapter( + base_url, timeout, num_pools=num_pools + ) except NameError: raise errors.DockerException( 'Install pypiwin32 package to enable npipe:// support' @@ -80,7 +85,9 @@ class Client( if isinstance(tls, TLSConfig): tls.configure_client(self) elif tls: - self._custom_adapter = ssladapter.SSLAdapter() + self._custom_adapter = ssladapter.SSLAdapter( + num_pools=num_pools + ) self.mount('https://', self._custom_adapter) self.base_url = base_url diff --git a/docker/constants.py b/docker/constants.py index cf5a39a..0c9a020 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -15,3 +15,4 @@ INSECURE_REGISTRY_DEPRECATION_WARNING = \ IS_WINDOWS_PLATFORM = (sys.platform == 'win32') DEFAULT_USER_AGENT = "docker-py/{0}".format(version) +DEFAULT_NUM_POOLS = 25 diff --git a/docker/errors.py b/docker/errors.py index 97be802..df18d57 100644 --- a/docker/errors.py +++ b/docker/errors.py @@ -11,7 +11,10 @@ class APIError(requests.exceptions.HTTPError): self.explanation = explanation if self.explanation is None and response.content: - self.explanation = response.content.strip() + try: + self.explanation = response.json()['message'] + except ValueError: + self.explanation = response.content.strip() def __str__(self): message = super(APIError, self).__str__() diff --git a/docker/transport/npipeconn.py b/docker/transport/npipeconn.py index 736ddf6..917fa8b 100644 --- a/docker/transport/npipeconn.py +++ b/docker/transport/npipeconn.py @@ -1,6 +1,7 @@ import six import requests.adapters +from .. import constants from .npipesocket import NpipeSocket if six.PY3: @@ -33,9 +34,9 @@ class NpipeHTTPConnection(httplib.HTTPConnection, object): class NpipeHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool): - def __init__(self, npipe_path, timeout=60): + def __init__(self, npipe_path, timeout=60, maxsize=10): super(NpipeHTTPConnectionPool, self).__init__( - 'localhost', timeout=timeout + 'localhost', timeout=timeout, maxsize=maxsize ) self.npipe_path = npipe_path self.timeout = timeout @@ -47,11 +48,12 @@ class NpipeHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool): class NpipeAdapter(requests.adapters.HTTPAdapter): - def __init__(self, base_url, timeout=60): + def __init__(self, base_url, timeout=60, + num_pools=constants.DEFAULT_NUM_POOLS): self.npipe_path = base_url.replace('npipe://', '') self.timeout = timeout self.pools = RecentlyUsedContainer( - 10, dispose_func=lambda p: p.close() + num_pools, dispose_func=lambda p: p.close() ) super(NpipeAdapter, self).__init__() diff --git a/docker/transport/unixconn.py b/docker/transport/unixconn.py index e09b6bf..b7905a0 100644 --- a/docker/transport/unixconn.py +++ b/docker/transport/unixconn.py @@ -2,6 +2,8 @@ import six import requests.adapters import socket +from .. import constants + if six.PY3: import http.client as httplib else: @@ -12,6 +14,7 @@ try: except ImportError: import urllib3 + RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer @@ -32,28 +35,31 @@ class UnixHTTPConnection(httplib.HTTPConnection, object): class UnixHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool): - def __init__(self, base_url, socket_path, timeout=60): + def __init__(self, base_url, socket_path, timeout=60, maxsize=10): super(UnixHTTPConnectionPool, self).__init__( - 'localhost', timeout=timeout + 'localhost', timeout=timeout, maxsize=maxsize ) self.base_url = base_url self.socket_path = socket_path self.timeout = timeout def _new_conn(self): - return UnixHTTPConnection(self.base_url, self.socket_path, - self.timeout) + return UnixHTTPConnection( + self.base_url, self.socket_path, self.timeout + ) class UnixAdapter(requests.adapters.HTTPAdapter): - def __init__(self, socket_url, timeout=60): + def __init__(self, socket_url, timeout=60, + num_pools=constants.DEFAULT_NUM_POOLS): socket_path = socket_url.replace('http+unix://', '') if not socket_path.startswith('/'): socket_path = '/' + socket_path self.socket_path = socket_path self.timeout = timeout - self.pools = RecentlyUsedContainer(10, - dispose_func=lambda p: p.close()) + self.pools = RecentlyUsedContainer( + num_pools, dispose_func=lambda p: p.close() + ) super(UnixAdapter, self).__init__() def get_connection(self, url, proxies=None): diff --git a/tests/integration/container_test.py b/tests/integration/container_test.py index 27d3046..a7267ef 100644 --- a/tests/integration/container_test.py +++ b/tests/integration/container_test.py @@ -118,7 +118,7 @@ class CreateContainerTest(helpers.BaseTestCase): self.client.wait(id) with self.assertRaises(docker.errors.APIError) as exc: self.client.remove_container(id) - err = exc.exception.response.text + err = exc.exception.explanation self.assertIn( 'You cannot remove a running container', err ) @@ -289,7 +289,7 @@ class CreateContainerTest(helpers.BaseTestCase): ) self.client.start(container) - assert six.b(expected_msg) in excinfo.value.explanation + assert excinfo.value.explanation == expected_msg def test_valid_no_log_driver_specified(self): log_config = docker.utils.LogConfig( diff --git a/tests/integration/errors_test.py b/tests/integration/errors_test.py new file mode 100644 index 0000000..42fbae4 --- /dev/null +++ b/tests/integration/errors_test.py @@ -0,0 +1,17 @@ +from docker.errors import APIError +from .. import helpers + + +class ErrorsTest(helpers.BaseTestCase): + def test_api_error_parses_json(self): + container = self.client.create_container( + helpers.BUSYBOX, + ['sleep', '10'] + ) + self.client.start(container['Id']) + with self.assertRaises(APIError) as cm: + self.client.remove_container(container['Id']) + explanation = cm.exception.explanation + assert 'You cannot remove a running container' in explanation + assert '{"message":' not in explanation + self.client.remove_container(container['Id'], force=True) diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index 2b99316..3543894 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -12,10 +12,7 @@ BUSYBOX = helpers.BUSYBOX class ServiceTest(helpers.BaseTestCase): def setUp(self): super(ServiceTest, self).setUp() - try: - self.client.leave_swarm(force=True) - except docker.errors.APIError: - pass + self.client.leave_swarm(force=True) self.client.init_swarm('eth0') def tearDown(self): @@ -25,10 +22,7 @@ class ServiceTest(helpers.BaseTestCase): self.client.remove_service(service['ID']) except docker.errors.APIError: pass - try: - self.client.leave_swarm(force=True) - except docker.errors.APIError: - pass + self.client.leave_swarm(force=True) def get_service_name(self): return 'dockerpytest_{0:x}'.format(random.getrandbits(64)) diff --git a/tests/integration/swarm_test.py b/tests/integration/swarm_test.py index 128628e..8c62f2e 100644 --- a/tests/integration/swarm_test.py +++ b/tests/integration/swarm_test.py @@ -11,17 +11,11 @@ BUSYBOX = helpers.BUSYBOX class SwarmTest(helpers.BaseTestCase): def setUp(self): super(SwarmTest, self).setUp() - try: - self.client.leave_swarm(force=True) - except docker.errors.APIError: - pass + self.client.leave_swarm(force=True) def tearDown(self): super(SwarmTest, self).tearDown() - try: - self.client.leave_swarm(force=True) - except docker.errors.APIError: - pass + self.client.leave_swarm(force=True) @requires_api_version('1.24') def test_init_swarm_simple(self): @@ -65,6 +59,7 @@ class SwarmTest(helpers.BaseTestCase): with pytest.raises(docker.errors.APIError) as exc_info: self.client.inspect_swarm() exc_info.value.response.status_code == 406 + assert self.client.leave_swarm(force=True) @requires_api_version('1.24') def test_update_swarm(self): |