Allow for configurable timeout on all client requests

Signed-off-by: Maxime Petazzoni <max@signalfuse.com>
This commit is contained in:
Maxime Petazzoni 2013-11-08 13:50:19 -08:00
parent 1345da7972
commit a451119e4a
5 changed files with 145 additions and 70 deletions

View File

@ -8,7 +8,8 @@ An API client for docker written in Python
API
===
`docker.Client(base_url='unix://var/run/docker.sock', version="1.4")`
`docker.Client(base_url='unix://var/run/docker.sock', version="1.4",
timeout=60)`
Client class. `base_url` refers to the protocol+hostname+port where the docker
server is hosted. Version is the version of the API the client will use.

View File

@ -27,6 +27,7 @@ import docker.utils as utils
if not six.PY3:
import websocket
DEFAULT_TIMEOUT_SECONDS = 60
class APIError(requests.exceptions.HTTPError):
def __init__(self, message, response, explanation=None):
@ -61,13 +62,17 @@ class APIError(requests.exceptions.HTTPError):
class Client(requests.Session):
def __init__(self, base_url="unix://var/run/docker.sock", version="1.4"):
def __init__(self, base_url="unix://var/run/docker.sock", version="1.4",
timeout=DEFAULT_TIMEOUT_SECONDS):
super(Client, self).__init__()
if base_url.startswith('unix:///'):
base_url = base_url.replace('unix:/', 'unix:')
self.mount('unix://', unixconn.UnixAdapter(base_url))
self.base_url = base_url
self._version = version
self._timeout = timeout
self.mount('unix://', unixconn.UnixAdapter(base_url, timeout))
try:
self._cfg = auth.load_config()
except Exception:
@ -143,7 +148,8 @@ class Client(requests.Session):
if 'headers' not in kwargs:
kwargs['headers'] = {}
kwargs['headers']['Content-Type'] = 'application/json'
return self.post(url, json.dumps(data2), **kwargs)
return self.post(url, json.dumps(data2),
timeout=self._timeout, **kwargs)
def attach_socket(self, container, params=None, ws=False):
if ws:
@ -153,7 +159,7 @@ class Client(requests.Session):
container = container.get('Id')
u = self._url("/containers/{0}/attach".format(container))
res = self.post(u, None, params=self._attach_params(params),
stream=True)
stream=True, timeout=self._timeout)
self._raise_for_status(res)
# hijack the underlying socket from requests, icky
# but for some reason requests.iter_contents and ilk
@ -215,7 +221,8 @@ class Client(requests.Session):
if context is not None:
headers = {'Content-Type': 'application/tar'}
res = self._result(self.post(u, context, params=params,
headers=headers, stream=True))
headers=headers, stream=True,
timeout=self._timeout))
if context is not None:
context.close()
srch = r'Successfully built ([0-9a-f]+)'
@ -246,7 +253,8 @@ class Client(requests.Session):
'before': before
}
u = self._url("/containers/ps")
res = self._result(self.get(u, params=params), True)
res = self._result(self.get(u, params=params, timeout=self._timeout),
True)
if quiet:
return [{'Id': x['Id']} for x in res]
return res
@ -284,30 +292,33 @@ class Client(requests.Session):
if isinstance(container, dict):
container = container.get('Id')
return self._result(self.get(self._url("/containers/{0}/changes".
format(container))), True)
format(container)), timeout=self._timeout), True)
def export(self, container):
if isinstance(container, dict):
container = container.get('Id')
res = self.get(self._url("/containers/{0}/export".format(container)),
stream=True)
stream=True, timeout=self._timeout)
self._raise_for_status(res)
return res.raw
def history(self, image):
res = self.get(self._url("/images/{0}/history".format(image)))
res = self.get(self._url("/images/{0}/history".format(image)),
timeout=self._timeout)
self._raise_for_status(res)
return self._result(res)
def images(self, name=None, quiet=False, all=False, viz=False):
if viz:
return self._result(self.get(self._url("images/viz")))
return self._result(self.get(self._url("images/viz"),
timeout=self._timeout))
params = {
'filter': name,
'only_ids': 1 if quiet else 0,
'all': 1 if all else 0,
}
res = self._result(self.get(self._url("/images/json"), params=params),
res = self._result(self.get(self._url("/images/json"), params=params,
timeout=self._timeout),
True)
if quiet:
return [x['Id'] for x in res]
@ -331,12 +342,16 @@ class Client(requests.Session):
data = None
if isinstance(src, six.string_types):
params['fromSrc'] = src
return self._result(self.post(u, data, params=params))
return self._result(self.post(u, data, params=params,
timeout=self._timeout))
return self._result(self.post(u, src, params=params))
return self._result(self.post(u, src, params=params,
timeout=self._timeout))
def info(self):
return self._result(self.get(self._url("/info")), True)
return self._result(self.get(self._url("/info"),
timeout=self._timeout),
True)
def insert(self, image, url, path):
api_url = self._url("/images/" + image + "/insert")
@ -344,25 +359,26 @@ class Client(requests.Session):
'url': url,
'path': path
}
return self._result(self.post(api_url, None, params=params))
return self._result(self.post(api_url, None, params=params,
timeout=self._timeout))
def inspect_container(self, container):
if isinstance(container, dict):
container = container.get('Id')
return self._result(self.get(
self._url("/containers/{0}/json".format(container))
), True)
return self._result(self.get(self._url("/containers/{0}/json".format(container)),
timeout=self._timeout),
True)
def inspect_image(self, image_id):
return self._result(self.get(
self._url("/images/{0}/json".format(image_id))
), True)
return self._result(self.get(self._url("/images/{0}/json".format(image_id)),
timeout=self._timeout),
True)
def kill(self, container):
if isinstance(container, dict):
container = container.get('Id')
url = self._url("/containers/{0}/kill".format(container))
res = self.post(url, None)
res = self.post(url, None, timeout=self._timeout)
self._raise_for_status(res)
def login(self, username, password=None, email=None, registry=None):
@ -393,12 +409,14 @@ class Client(requests.Session):
'stderr': 1
}
u = self._url("/containers/{0}/attach".format(container))
return self._result(self.post(u, None, params=params))
return self._result(self.post(u, None, params=params,
timeout=self._timeout))
def port(self, container, private_port):
if isinstance(container, dict):
container = container.get('Id')
res = self.get(self._url("/containers/{0}/json".format(container)))
res = self.get(self._url("/containers/{0}/json".format(container)),
timeout=self._timeout)
self._raise_for_status(res)
json_ = res.json()
s_port = str(private_port)
@ -430,7 +448,8 @@ class Client(requests.Session):
if authcfg:
headers['X-Registry-Auth'] = auth.encode_header(authcfg)
u = self._url("/images/create")
return self._result(self.post(u, params=params, headers=headers))
return self._result(self.post(u, params=params, headers=headers,
timeout=self._timeout))
def push(self, repository):
registry, repo_name = auth.resolve_repository_name(repository)
@ -451,11 +470,12 @@ class Client(requests.Session):
if isinstance(container, dict):
container = container.get('Id')
params = {'v': v}
res = self.delete(self._url("/containers/" + container), params=params)
res = self.delete(self._url("/containers/" + container), params=params,
timeout=self._timeout)
self._raise_for_status(res)
def remove_image(self, image):
res = self.delete(self._url("/images/" + image))
res = self.delete(self._url("/images/" + image), timeout=self._timeout)
self._raise_for_status(res)
def restart(self, container, timeout=10):
@ -463,12 +483,14 @@ class Client(requests.Session):
container = container.get('Id')
params = {'t': timeout}
url = self._url("/containers/{0}/restart".format(container))
res = self.post(url, None, params=params)
res = self.post(url, None, params=params, timeout=self._timeout)
self._raise_for_status(res)
def search(self, term):
return self._result(self.get(self._url("/images/search"),
params={'term': term}), True)
params={'term': term},
timeout=self._timeout),
True)
def start(self, container, binds=None, port_bindings=None, lxc_conf=None):
if isinstance(container, dict):
@ -501,7 +523,7 @@ class Client(requests.Session):
container = container.get('Id')
params = {'t': timeout}
url = self._url("/containers/{0}/stop".format(container))
res = self.post(url, None, params=params)
res = self.post(url, None, params=params, timeout=self._timeout)
self._raise_for_status(res)
def tag(self, image, repository, tag=None, force=False):
@ -511,16 +533,18 @@ class Client(requests.Session):
'force': 1 if force else 0
}
url = self._url("/images/{0}/tag".format(image))
res = self.post(url, None, params=params)
res = self.post(url, None, params=params, timeout=self._timeout)
self._raise_for_status(res)
return res.status_code == 201
def top(self, container):
u = self._url("/containers/{0}/top".format(container))
return self._result(self.get(u), True)
return self._result(self.get(u, timeout=self._timeout), True)
def version(self):
return self._result(self.get(self._url("/version")), True)
return self._result(self.get(self._url("/version"),
timeout=self._timeout),
True)
def wait(self, container):
if isinstance(container, dict):

View File

@ -25,15 +25,16 @@ try:
except ImportError:
import urllib3.connectionpool as connectionpool
class UnixHTTPConnection(httplib.HTTPConnection, object):
def __init__(self, base_url, unix_socket):
httplib.HTTPConnection.__init__(self, 'localhost')
def __init__(self, base_url, unix_socket, timeout=60):
httplib.HTTPConnection.__init__(self, 'localhost', timeout=timeout)
self.base_url = base_url
self.unix_socket = unix_socket
self.timeout = timeout
def connect(self):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
sock.connect(self.base_url.replace("unix:/", ""))
self.sock = sock
@ -47,19 +48,22 @@ class UnixHTTPConnection(httplib.HTTPConnection, object):
class UnixHTTPConnectionPool(connectionpool.HTTPConnectionPool):
def __init__(self, base_url, socket_path):
self.socket_path = socket_path
def __init__(self, base_url, socket_path, timeout=60):
connectionpool.HTTPConnectionPool.__init__(self, 'localhost',
timeout=timeout)
self.base_url = base_url
super(UnixHTTPConnectionPool, self).__init__(self, 'localhost')
self.socket_path = socket_path
self.timeout = timeout
def _new_conn(self):
return UnixHTTPConnection(self.base_url, self.socket_path)
return UnixHTTPConnection(self.base_url, self.socket_path, self.timeout)
class UnixAdapter(requests.adapters.HTTPAdapter):
def __init__(self, base_url):
def __init__(self, base_url, timeout=60):
self.base_url = base_url
self.timeout = timeout
super(UnixAdapter, self).__init__()
def get_connection(self, socket_path, proxies=None):
return UnixHTTPConnectionPool(self.base_url, socket_path)
return UnixHTTPConnectionPool(self.base_url, socket_path, self.timeout)

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import time
import base64
import io
import os
@ -73,9 +74,9 @@ class TestSearch(BaseTestCase):
def runTest(self):
res = self.client.search('busybox')
self.assertTrue(len(res) >= 1)
base_img = [x for x in res if x['Name'] == 'busybox']
base_img = [x for x in res if x['name'] == 'busybox']
self.assertEqual(len(base_img), 1)
self.assertIn('Description', base_img[0])
self.assertIn('description', base_img[0])
###################
## LISTING TESTS ##
@ -165,7 +166,10 @@ class TestCreateContainerPrivileged(BaseTestCase):
res = self.client.create_container('busybox', 'true', privileged=True)
inspect = self.client.inspect_container(res['Id'])
self.assertIn('Config', inspect)
self.assertEqual(inspect['Config']['Privileged'], True)
# Since Nov 2013, the Privileged flag is no longer part of the
# container's config exposed via the API (safety concerns?).
#
# self.assertEqual(inspect['Config']['Privileged'], True)
class TestCreateContainerWithName(BaseTestCase):
@ -602,5 +606,23 @@ class TestLoadConfig(BaseTestCase):
self.assertEqual(cfg['Email'], 'sakuya@scarlet.net')
self.assertEqual(cfg.get('Auth'), None)
class TestConnectionTimeout(unittest.TestCase):
def setUp(self):
self.timeout = 0.5
self.client = docker.client.Client(base_url='http://192.168.10.2:4243',
timeout=self.timeout)
def runTest(self):
start = time.time()
res = None
# This call isn't supposed to complete, and it should fail fast.
try: res = self.client.inspect_container('id')
except: pass
end = time.time()
self.assertTrue(res is None)
self.assertTrue(end - start < 2 * self.timeout)
if __name__ == '__main__':
unittest.main()

View File

@ -76,7 +76,8 @@ class DockerClientTest(unittest.TestCase):
self.fail('Command should not raise exception: {0}'.format(e))
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/version'
'unix://var/run/docker.sock/v1.4/version',
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_info(self):
@ -85,7 +86,8 @@ class DockerClientTest(unittest.TestCase):
except Exception as e:
self.fail('Command should not raise exception: {0}'.format(e))
fake_request.assert_called_with('unix://var/run/docker.sock/v1.4/info')
fake_request.assert_called_with('unix://var/run/docker.sock/v1.4/info',
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS)
def test_search(self):
try:
@ -95,7 +97,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/images/search',
params={'term': 'busybox'}
params={'term': 'busybox'},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
###################
@ -109,7 +112,8 @@ class DockerClientTest(unittest.TestCase):
self.fail('Command should not raise exception: {0}'.format(e))
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/images/json',
params={'filter': None, 'only_ids': 0, 'all': 1}
params={'filter': None, 'only_ids': 0, 'all': 1},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_image_ids(self):
@ -120,7 +124,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/images/json',
params={'filter': None, 'only_ids': 1, 'all': 0}
params={'filter': None, 'only_ids': 1, 'all': 0},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_list_containers(self):
@ -137,7 +142,8 @@ class DockerClientTest(unittest.TestCase):
'limit': -1,
'trunc_cmd': 1,
'before': None
}
},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
#####################
@ -232,7 +238,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/start',
'{}',
headers={'Content-Type': 'application/json'}
headers={'Content-Type': 'application/json'},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_start_container_with_lxc_conf(self):
@ -283,7 +290,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/start',
'{"Binds": ["/tmp:/mnt"]}',
headers={'Content-Type': 'application/json'}
headers={'Content-Type': 'application/json'},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_start_container_with_dict_instead_of_id(self):
@ -293,7 +301,8 @@ class DockerClientTest(unittest.TestCase):
self.fail('Command should not raise exception: {0}'.format(e))
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/start',
'{}', headers={'Content-Type': 'application/json'}
'{}', headers={'Content-Type': 'application/json'},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_wait(self):
@ -329,7 +338,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/attach',
None,
params={'logs': 1, 'stderr': 1, 'stdout': 1}
params={'logs': 1, 'stderr': 1, 'stdout': 1},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_logs_with_dict_instead_of_id(self):
@ -341,7 +351,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/attach',
None,
params={'logs': 1, 'stderr': 1, 'stdout': 1}
params={'logs': 1, 'stderr': 1, 'stdout': 1},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_diff(self):
@ -351,7 +362,8 @@ class DockerClientTest(unittest.TestCase):
self.fail('Command should not raise exception: {0}'.format(e))
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/changes')
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/changes',
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS)
def test_diff_with_dict_instead_of_id(self):
try:
@ -360,7 +372,8 @@ class DockerClientTest(unittest.TestCase):
self.fail('Command should not raise exception: {0}'.format(e))
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/changes')
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/changes',
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS)
def test_stop_container(self):
try:
@ -371,7 +384,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/stop',
None,
params={'t': 2}
params={'t': 2},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_stop_container_with_dict_instead_of_id(self):
@ -383,7 +397,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/stop',
None,
params={'t': 2}
params={'t': 2},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_kill_container(self):
@ -394,7 +409,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/kill',
None
None,
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_kill_container_with_dict_instead_of_id(self):
@ -405,7 +421,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/kill',
None
None,
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_restart_container(self):
@ -417,7 +434,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/restart',
None,
params={'t': 2}
params={'t': 2},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_restart_container_with_dict_instead_of_id(self):
@ -429,7 +447,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b/restart',
None,
params={'t': 2}
params={'t': 2},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_remove_container(self):
@ -440,7 +459,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b',
params={'v': False}
params={'v': False},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_remove_container_with_dict_instead_of_id(self):
@ -451,7 +471,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/containers/3cc2351ab11b',
params={'v': False}
params={'v': False},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
##################
@ -467,7 +488,8 @@ class DockerClientTest(unittest.TestCase):
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/images/create',
headers={},
params={'tag': None, 'fromImage': 'joffrey/test001'}
params={'tag': None, 'fromImage': 'joffrey/test001'},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_commit(self):
@ -486,7 +508,8 @@ class DockerClientTest(unittest.TestCase):
'tag': None,
'container': '3cc2351ab11b',
'author': None
}
},
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
def test_remove_image(self):
@ -496,7 +519,8 @@ class DockerClientTest(unittest.TestCase):
self.fail('Command should not raise exception: {0}'.format(e))
fake_request.assert_called_with(
'unix://var/run/docker.sock/v1.4/images/e9aa60c60128'
'unix://var/run/docker.sock/v1.4/images/e9aa60c60128',
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
)
#################