summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--docker/api/swarm.py6
-rw-r--r--docker/client.py15
-rw-r--r--docker/constants.py1
-rw-r--r--docker/errors.py5
-rw-r--r--docker/transport/npipeconn.py10
-rw-r--r--docker/transport/unixconn.py20
-rw-r--r--tests/integration/container_test.py4
-rw-r--r--tests/integration/errors_test.py17
-rw-r--r--tests/integration/service_test.py10
-rw-r--r--tests/integration/swarm_test.py11
11 files changed, 70 insertions, 37 deletions
diff --git a/Makefile b/Makefile
index 1fb21d4..b997722 100644
--- a/Makefile
+++ b/Makefile
@@ -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):