Docker cannot start a new instance because of an internal error

The root cause for the issue is that the Docker rest API does not
longer deliver the container list for v1.4 api calls.
The Docker API must be upgraded to 1.7.

Docker API 1.7 is a good choice since that is the first docker version
which officially supports rhel/centos.

In addition will docker client only parse json if the response needed
(to_json()) and this method will not catch json parse exception which makes
finding th eroot cause of a problem in the future easier.

Change-Id: I53dfac5f09ee021c6ed4763f7f164206e8ab32ff
Closes-Bug: #1278104
This commit is contained in:
Daniel Kuffner 2014-02-09 16:22:12 +01:00
parent a710452674
commit 423f344c59
2 changed files with 61 additions and 47 deletions

View File

@ -40,7 +40,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_list_containers(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.4/containers/ps?all=1&limit=50',
mock_conn.request('GET', '/v1.7/containers/ps?all=1',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='[]',
headers={'Content-Type': 'application/json'})
@ -180,7 +180,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_start_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/containers/XXX/start',
mock_conn.request('POST', '/v1.7/containers/XXX/start',
body='{}',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200,
@ -197,7 +197,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_start_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/containers/XXX/start',
mock_conn.request('POST', '/v1.7/containers/XXX/start',
body='{}',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
@ -213,7 +213,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_inspect_image(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.4/images/XXX/json',
mock_conn.request('GET', '/v1.7/images/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='{"name": "XXX"}',
headers={'Content-Type': 'application/json'})
@ -230,7 +230,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_inspect_image_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.4/images/XXX/json',
mock_conn.request('GET', '/v1.7/images/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(404)
mock_conn.getresponse().AndReturn(response)
@ -246,7 +246,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_inspect_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.4/containers/XXX/json',
mock_conn.request('GET', '/v1.7/containers/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='{"id": "XXX"}',
headers={'Content-Type': 'application/json'})
@ -263,7 +263,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_inspect_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.4/containers/XXX/json',
mock_conn.request('GET', '/v1.7/containers/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(404)
mock_conn.getresponse().AndReturn(response)
@ -279,7 +279,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_stop_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/containers/XXX/stop?t=5',
mock_conn.request('POST', '/v1.7/containers/XXX/stop?t=5',
headers={'Content-Type': 'application/json'})
response = FakeResponse(204,
headers={'Content-Type': 'application/json'})
@ -295,7 +295,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_kill_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/containers/XXX/kill',
mock_conn.request('POST', '/v1.7/containers/XXX/kill',
headers={'Content-Type': 'application/json'})
response = FakeResponse(204,
headers={'Content-Type': 'application/json'})
@ -311,7 +311,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_stop_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/containers/XXX/stop?t=5',
mock_conn.request('POST', '/v1.7/containers/XXX/stop?t=5',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
@ -326,7 +326,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_kill_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/containers/XXX/kill',
mock_conn.request('POST', '/v1.7/containers/XXX/kill',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
@ -341,7 +341,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_destroy_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('DELETE', '/v1.4/containers/XXX',
mock_conn.request('DELETE', '/v1.7/containers/XXX',
headers={'Content-Type': 'application/json'})
response = FakeResponse(204,
headers={'Content-Type': 'application/json'})
@ -357,7 +357,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_destroy_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('DELETE', '/v1.4/containers/XXX',
mock_conn.request('DELETE', '/v1.7/containers/XXX',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
@ -372,7 +372,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_pull_repository(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/images/create?fromImage=ping',
mock_conn.request('POST', '/v1.7/images/create?fromImage=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200,
headers={'Content-Type': 'application/json'})
@ -388,7 +388,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_pull_repository_tag(self):
mock_conn = self.mox.CreateMockAnything()
url = '/v1.4/images/create?fromImage=ping&tag=pong'
url = '/v1.7/images/create?fromImage=ping&tag=pong'
mock_conn.request('POST', url,
headers={'Content-Type': 'application/json'})
response = FakeResponse(200,
@ -405,7 +405,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_pull_repository_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/images/create?fromImage=ping',
mock_conn.request('POST', '/v1.7/images/create?fromImage=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400,
headers={'Content-Type': 'application/json'})
@ -423,7 +423,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
body = ('{"username":"foo","password":"bar",'
'"auth":"","email":"foo@bar.bar"}')
mock_conn.request('POST', '/v1.4/images/ping/push',
mock_conn.request('POST', '/v1.7/images/ping/push',
headers={'Content-Type': 'application/json'},
body=body)
response = FakeResponse(200,
@ -442,7 +442,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
body = ('{"username":"foo","password":"bar",'
'"auth":"","email":"foo@bar.bar"}')
mock_conn.request('POST', '/v1.4/images/ping/push',
mock_conn.request('POST', '/v1.7/images/ping/push',
headers={'Content-Type': 'application/json'},
body=body)
response = FakeResponse(400,
@ -459,7 +459,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_commit_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/commit?container=XXX&repo=ping',
mock_conn.request('POST', '/v1.7/commit?container=XXX&repo=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(201,
headers={'Content-Type': 'application/json'})
@ -475,7 +475,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_commit_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.4/commit?container=XXX&repo=ping',
mock_conn.request('POST', '/v1.7/commit?container=XXX&repo=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400,
headers={'Content-Type': 'application/json'})
@ -491,7 +491,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_get_container_logs(self):
mock_conn = self.mox.CreateMockAnything()
url = '/v1.4/containers/XXX/attach?logs=1&stream=0&stdout=1&stderr=1'
url = '/v1.7/containers/XXX/attach?logs=1&stream=0&stdout=1&stderr=1'
mock_conn.request('POST', url,
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='ping pong',
@ -509,7 +509,7 @@ class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_get_container_logs_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
url = '/v1.4/containers/XXX/attach?logs=1&stream=0&stdout=1&stderr=1'
url = '/v1.7/containers/XXX/attach?logs=1&stream=0&stdout=1&stderr=1'
mock_conn.request('POST', url,
headers={'Content-Type': 'application/json'})
response = FakeResponse(404)

View File

@ -19,6 +19,7 @@ import socket
from eventlet.green import httplib
import six
from nova.openstack.common.gettextutils import _
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
@ -50,23 +51,36 @@ def filter_data(f):
class Response(object):
def __init__(self, http_response, skip_body=False):
def __init__(self, http_response, url=None):
self.url = url
self._response = http_response
self.code = int(http_response.status)
self.data = http_response.read()
self.json = self._decode_json(self.data)
self._json = None
def read(self, size=None):
return self._response.read(size)
@filter_data
def _decode_json(self, data):
def to_json(self, default=None):
if not self._json:
self._json = self._decode_json(self.data, default)
return self._json
def _validate_content_type(self):
# Docker does not return always the correct Content-Type.
# Lets try to parse the response anyway since json is requested.
if self._response.getheader('Content-Type') != 'application/json':
return
try:
return jsonutils.loads(self.data)
except ValueError:
return
LOG.debug(_("Content-Type of response is not application/json"
" (Docker bug?). Requested URL %s") % self.url)
@filter_data
def _decode_json(self, data, default=None):
if not data:
return default
self._validate_content_type()
# Do not catch ValueError or SyntaxError since that
# just hides the root cause of errors.
return jsonutils.loads(data)
class UnixHTTPConnection(httplib.HTTPConnection):
@ -100,13 +114,13 @@ class DockerHTTPClient(object):
kwargs['headers'] = headers
conn = self.connection
conn.request(*args, **kwargs)
return Response(conn.getresponse())
return Response(conn.getresponse(), url=args[1])
def list_containers(self, _all=True):
resp = self.make_request(
'GET',
'/v1.4/containers/ps?all={0}&limit=50'.format(int(_all)))
return resp.json
'/v1.7/containers/ps?all={0}'.format(int(_all)))
return resp.to_json(default=[])
def create_container(self, args, name):
data = {
@ -135,7 +149,7 @@ class DockerHTTPClient(object):
body=jsonutils.dumps(data))
if resp.code != 201:
return
obj = jsonutils.loads(resp.data)
obj = resp.to_json()
for k, v in obj.iteritems():
if k.lower() == 'id':
return v
@ -143,48 +157,48 @@ class DockerHTTPClient(object):
def start_container(self, container_id):
resp = self.make_request(
'POST',
'/v1.4/containers/{0}/start'.format(container_id),
'/v1.7/containers/{0}/start'.format(container_id),
body='{}')
return (resp.code == 200)
def inspect_image(self, image_name):
resp = self.make_request(
'GET',
'/v1.4/images/{0}/json'.format(image_name))
'/v1.7/images/{0}/json'.format(image_name))
if resp.code != 200:
return
return resp.json
return resp.to_json()
def inspect_container(self, container_id):
resp = self.make_request(
'GET',
'/v1.4/containers/{0}/json'.format(container_id))
'/v1.7/containers/{0}/json'.format(container_id))
if resp.code != 200:
return
return resp.json
return resp.to_json()
def stop_container(self, container_id):
timeout = 5
resp = self.make_request(
'POST',
'/v1.4/containers/{0}/stop?t={1}'.format(container_id, timeout))
'/v1.7/containers/{0}/stop?t={1}'.format(container_id, timeout))
return (resp.code == 204)
def kill_container(self, container_id):
resp = self.make_request(
'POST',
'/v1.4/containers/{0}/kill'.format(container_id))
'/v1.7/containers/{0}/kill'.format(container_id))
return (resp.code == 204)
def destroy_container(self, container_id):
resp = self.make_request(
'DELETE',
'/v1.4/containers/{0}'.format(container_id))
'/v1.7/containers/{0}'.format(container_id))
return (resp.code == 204)
def pull_repository(self, name):
parts = name.rsplit(':', 1)
url = '/v1.4/images/create?fromImage={0}'.format(parts[0])
url = '/v1.7/images/create?fromImage={0}'.format(parts[0])
if len(parts) > 1:
url += '&tag={0}'.format(parts[1])
resp = self.make_request('POST', url)
@ -196,7 +210,7 @@ class DockerHTTPClient(object):
return (resp.code == 200)
def push_repository(self, name, headers=None):
url = '/v1.4/images/{0}/push'.format(name)
url = '/v1.7/images/{0}/push'.format(name)
# NOTE(samalba): docker requires the credentials fields even if
# they're not needed here.
body = ('{"username":"foo","password":"bar",'
@ -211,7 +225,7 @@ class DockerHTTPClient(object):
def commit_container(self, container_id, name):
parts = name.rsplit(':', 1)
url = '/v1.4/commit?container={0}&repo={1}'.format(container_id,
url = '/v1.7/commit?container={0}&repo={1}'.format(container_id,
parts[0])
if len(parts) > 1:
url += '&tag={0}'.format(parts[1])
@ -221,7 +235,7 @@ class DockerHTTPClient(object):
def get_container_logs(self, container_id):
resp = self.make_request(
'POST',
('/v1.4/containers/{0}/attach'
('/v1.7/containers/{0}/attach'
'?logs=1&stream=0&stdout=1&stderr=1').format(container_id))
if resp.code != 200:
return