Make Docker client timeout configurable

To support long running Docker operations we need to increase our
timeout, but a good value for the timeout will change per deployment.
So, add it as a config option so that it can be tweaked as necessary.

Change-Id: Ie53fa5086e73111bf9f42dbf596172af8d936cf0
Closes-Bug: #1450559
This commit is contained in:
Andrew Melton 2015-04-30 14:46:11 -07:00
parent 37b3149b5e
commit fa191a233a
5 changed files with 180 additions and 10 deletions

View File

@ -133,7 +133,7 @@
#rpc_zmq_bind_address = *
# MatchMaker driver. (string value)
#rpc_zmq_matchmaker = oslo_messaging._drivers.matchmaker.MatchMakerLocalhost
#rpc_zmq_matchmaker = local
# ZeroMQ receiver listening port. (integer value)
#rpc_zmq_port = 9501
@ -383,6 +383,10 @@
# api version in your environment. (string value)
#docker_remote_api_version = 1.17
# Default timeout in seconds for docker client operations. (integer
# value)
#default_timeout = 10
# If set, ignore any SSL validation issues (boolean value)
#api_insecure = false
@ -881,9 +885,10 @@
#rabbit_ha_queues = false
# Number of seconds after which the Rabbit broker is considered down
# if heartbeat's keep-alive fails (0 disable the heartbeat). (integer
# value)
#heartbeat_timeout_threshold = 60
# if heartbeat's keep-alive fails (0 disables the heartbeat, >0
# enables it. Enabling heartbeats requires kombu>=3.0.7 and
# amqp>=1.4.0). EXPERIMENTAL (integer value)
#heartbeat_timeout_threshold = 0
# How often times during the heartbeat_timeout_threshold we check the
# heartbeat. (integer value)

View File

@ -19,13 +19,16 @@ from oslo_config import cfg
from magnum.openstack.common import log as logging
DEFAULT_DOCKER_REMOTE_API_VERSION = '1.17'
DEFAULT_DOCKER_TIMEOUT = 10
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class DockerHTTPClient(client.Client):
def __init__(self, url='unix://var/run/docker.sock',
ver=DEFAULT_DOCKER_REMOTE_API_VERSION):
ver=DEFAULT_DOCKER_REMOTE_API_VERSION,
timeout=DEFAULT_DOCKER_TIMEOUT):
if (CONF.docker.cert_file or
CONF.docker.key_file):
client_cert = (CONF.docker.cert_file, CONF.docker.key_file)
@ -43,7 +46,7 @@ class DockerHTTPClient(client.Client):
super(DockerHTTPClient, self).__init__(
base_url=url,
version=ver,
timeout=10,
timeout=timeout,
tls=ssl_config
)

View File

@ -30,6 +30,10 @@ docker_opts = [
default=docker_client.DEFAULT_DOCKER_REMOTE_API_VERSION,
help='Docker remote api version. Override it according to '
'specific docker api version in your environment.'),
cfg.IntOpt('default_timeout',
default=docker_client.DEFAULT_DOCKER_TIMEOUT,
help='Default timeout in seconds for docker client '
'operations.'),
cfg.BoolOpt('api_insecure',
default=False,
help='If set, ignore any SSL validation issues'),
@ -70,7 +74,8 @@ class Handler(object):
def _docker_for_bay(bay):
tcp_url = 'tcp://%s:2376' % bay.api_address
return docker_client.DockerHTTPClient(tcp_url,
CONF.docker.docker_remote_api_version)
CONF.docker.docker_remote_api_version,
CONF.docker.default_timeout)
@classmethod
def _docker_for_container(cls, context, container):

View File

@ -0,0 +1,154 @@
# Copyright 2015 Rackspace, inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from docker import client as docker_py_client
import mock
from magnum.conductor.handlers.common import docker_client
from magnum.tests import base
class DockerClientTestCase(base.BaseTestCase):
def test_docker_client_init(self):
client = docker_client.DockerHTTPClient()
self.assertEqual(docker_client.DEFAULT_DOCKER_REMOTE_API_VERSION,
client.api_version)
self.assertEqual(docker_client.DEFAULT_DOCKER_TIMEOUT,
client.timeout)
def test_docker_client_init_timeout(self):
expected_timeout = 300
client = docker_client.DockerHTTPClient(timeout=expected_timeout)
self.assertEqual(docker_client.DEFAULT_DOCKER_REMOTE_API_VERSION,
client.api_version)
self.assertEqual(expected_timeout, client.timeout)
def test_docker_client_init_url(self):
expected_url = 'http://127.0.0.1:2375'
client = docker_client.DockerHTTPClient(url=expected_url)
self.assertEqual(expected_url,
client.base_url)
self.assertEqual(docker_client.DEFAULT_DOCKER_REMOTE_API_VERSION,
client.api_version)
self.assertEqual(docker_client.DEFAULT_DOCKER_TIMEOUT,
client.timeout)
def test_docker_client_init_version(self):
expected_version = '1.16'
client = docker_client.DockerHTTPClient(ver=expected_version)
self.assertEqual(expected_version,
client.api_version)
self.assertEqual(docker_client.DEFAULT_DOCKER_TIMEOUT,
client.timeout)
@mock.patch.object(docker_py_client.Client, 'inspect_container')
@mock.patch.object(docker_py_client.Client, 'containers')
def test_list_instances(self, mock_containers, mock_inspect):
client = docker_client.DockerHTTPClient()
containers = [dict(Id=x) for x in range(0, 3)]
inspect_results = [dict(Config=dict(Hostname=x)) for x in range(0, 3)]
mock_containers.return_value = containers
mock_inspect.side_effect = inspect_results
instances = client.list_instances()
self.assertEqual([0, 1, 2], instances)
mock_containers.assert_called_once_with(all=True)
mock_inspect.assert_has_calls([mock.call(x) for x in range(0, 3)])
@mock.patch.object(docker_py_client.Client, 'inspect_container')
@mock.patch.object(docker_py_client.Client, 'containers')
def test_list_instances_inspect(self, mock_containers, mock_inspect):
client = docker_client.DockerHTTPClient()
containers = [dict(Id=x) for x in range(0, 3)]
inspect_results = [dict(Config=dict(Hostname=x)) for x in range(0, 3)]
mock_containers.return_value = containers
mock_inspect.side_effect = inspect_results
instances = client.list_instances(inspect=True)
self.assertEqual(inspect_results, instances)
mock_containers.assert_called_once_with(all=True)
mock_inspect.assert_has_calls([mock.call(x) for x in range(0, 3)])
@mock.patch.object(docker_py_client.Client, '_raise_for_status')
@mock.patch.object(docker_py_client.Client, '_post')
@mock.patch.object(docker_py_client.Client, '_url')
def test_pause(self, mock_url, mock_post, mock_raise_for_status):
client = docker_client.DockerHTTPClient()
client.pause('someid')
mock_url.assert_called_once_with('/containers/someid/pause')
mock_post.assert_callend_once_with(mock_url.return_value)
mock_raise_for_status.assert_called_once_with(
mock_post.return_value)
@mock.patch.object(docker_py_client.Client, '_raise_for_status')
@mock.patch.object(docker_py_client.Client, '_post')
@mock.patch.object(docker_py_client.Client, '_url')
def test_pause_container_dict(self, mock_url, mock_post,
mock_raise_for_status):
client = docker_client.DockerHTTPClient()
client.pause(dict(Id='someid'))
mock_url.assert_called_once_with('/containers/someid/pause')
mock_post.assert_callend_once_with(mock_url.return_value)
mock_raise_for_status.assert_called_once_with(
mock_post.return_value)
@mock.patch.object(docker_py_client.Client, '_raise_for_status')
@mock.patch.object(docker_py_client.Client, '_post')
@mock.patch.object(docker_py_client.Client, '_url')
def test_unpause(self, mock_url, mock_post, mock_raise_for_status):
client = docker_client.DockerHTTPClient()
client.unpause('someid')
mock_url.assert_called_once_with('/containers/someid/unpause')
mock_post.assert_callend_once_with(mock_url.return_value)
mock_raise_for_status.assert_called_once_with(
mock_post.return_value)
@mock.patch.object(docker_py_client.Client, '_raise_for_status')
@mock.patch.object(docker_py_client.Client, '_post')
@mock.patch.object(docker_py_client.Client, '_url')
def test_unpause_container_dict(self, mock_url, mock_post,
mock_raise_for_status):
client = docker_client.DockerHTTPClient()
client.unpause(dict(Id='someid'))
mock_url.assert_called_once_with('/containers/someid/unpause')
mock_post.assert_callend_once_with(mock_url.return_value)
mock_raise_for_status.assert_called_once_with(
mock_post.return_value)
@mock.patch.object(docker_py_client.Client, 'attach')
def test_get_container_logs(self, mock_attach):
client = docker_client.DockerHTTPClient()
client.get_container_logs('someid')
mock_attach.assert_called_once_with('someid',
1, 1, 0, 1)

View File

@ -39,7 +39,8 @@ class TestDockerConductor(base.BaseTestCase):
self.assertEqual(mock_docker, actual_docker)
args = ('tcp://1.1.1.1:2376', CONF.docker.docker_remote_api_version)
args = ('tcp://1.1.1.1:2376', CONF.docker.docker_remote_api_version,
CONF.docker.default_timeout)
mock_docker_client.DockerHTTPClient.assert_called_once_with(*args)
@mock.patch.object(docker_conductor, 'docker_client')
@ -62,7 +63,8 @@ class TestDockerConductor(base.BaseTestCase):
self.assertEqual(mock_docker, actual_docker)
args = ('tcp://1.1.1.1:2376', CONF.docker.docker_remote_api_version)
args = ('tcp://1.1.1.1:2376', CONF.docker.docker_remote_api_version,
CONF.docker.default_timeout)
mock_bay_get_by_uuid.assert_called_once_with(mock.sentinel.context,
mock_container.bay_uuid)
mock_docker_client.DockerHTTPClient.assert_called_once_with(*args)
@ -91,7 +93,8 @@ class TestDockerConductor(base.BaseTestCase):
self.assertEqual(mock_docker, actual_docker)
args = ('tcp://1.1.1.1:2376', CONF.docker.docker_remote_api_version)
args = ('tcp://1.1.1.1:2376', CONF.docker.docker_remote_api_version,
CONF.docker.default_timeout)
mock_container_get_by_uuid.assert_called_once_with(
mock.sentinel.context,
mock_container.uuid)