Refactor HTTPClient to use two request methods

Rather than depend on magic, I would prefer that we explicitly call
two different request methods: json_request and raw_request. The
former will encode/decode request bodies to and from JSON, while
the latter will not.

Change-Id: I6a429a5975993f71df85df55f11c5d51c050c289
This commit is contained in:
Brian Waldon 2012-05-15 10:01:47 -07:00
parent 623a0898f8
commit 3943699427
5 changed files with 82 additions and 117 deletions

View File

@ -17,9 +17,6 @@
Base utilities to build API operation managers and objects on top of.
"""
from glanceclient.common import exceptions
# Python 2.4 compat
try:
all
@ -50,11 +47,7 @@ class Manager(object):
self.api = api
def _list(self, url, response_key, obj_class=None, body=None):
resp = None
if body:
resp, body = self.api.post(url, body=body)
else:
resp, body = self.api.get(url)
resp, body = self.api.json_request('GET', url)
if obj_class is None:
obj_class = self.resource_class
@ -62,29 +55,11 @@ class Manager(object):
data = body[response_key]
return [obj_class(self, res, loaded=True) for res in data if res]
def _get(self, url, response_key):
resp, body = self.api.get(url)
return self.resource_class(self, body[response_key])
def _create(self, url, body, response_key, return_raw=False):
resp, body = self.api.post(url, body=body)
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
def _delete(self, url):
self.api.delete(url)
self.api.raw_request('DELETE', url)
def _update(self, url, body, response_key=None, method="PUT"):
methods = {"PUT": self.api.put,
"POST": self.api.post}
try:
_method = methods[method]
except KeyError:
msg = "Invalid update method: %s" % method
raise exceptions.ClientException(msg)
resp, body = _method(url, body=body)
def _update(self, url, body, response_key=None):
resp, body = self.api.json_request('PUT', url, body=body)
# PUT requests may not return a body
if body:
return self.resource_class(self, body[response_key])

View File

@ -69,30 +69,16 @@ class HTTPClient(httplib2.Http):
Wrapper around httplib2.Http.request to handle tasks such as
setting headers, JSON encoding/decoding, and error handling.
"""
url = self.endpoint + url
# Copy the kwargs so we can reuse the original in case of redirects
_kwargs = copy.copy(kwargs)
_kwargs.setdefault('headers', kwargs.get('headers', {}))
_kwargs['headers']['User-Agent'] = USER_AGENT
if 'raw_body' in _kwargs:
raw_body = _kwargs.pop('raw_body')
if raw_body is not None:
_kwargs['headers']['Content-Type'] = 'application/octet-stream'
_kwargs['body'] = raw_body
elif 'body' in kwargs and kwargs['body'] is not None:
_kwargs['headers']['Content-Type'] = 'application/json'
_kwargs['body'] = json.dumps(kwargs['body'])
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
if self.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
resp, body = super(HTTPClient, self).request(url, method, **_kwargs)
self.http_log((url, method,), _kwargs, resp, body)
if body:
try:
body = json.loads(body)
except ValueError:
logger.debug("Could not decode JSON from body: %s" % body)
else:
logger.debug("No body was returned.")
body = None
resp, body = super(HTTPClient, self).request(url, method, **kwargs)
self.http_log((url, method,), kwargs, resp, body)
if 400 <= resp.status < 600:
logger.exception("Request returned failure status.")
@ -103,26 +89,28 @@ class HTTPClient(httplib2.Http):
return resp, body
def request(self, url, method, **kwargs):
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
if self.auth_token:
kwargs['headers']['X-Auth-Token'] = self.auth_token
kwargs['headers'].setdefault('Content-Type', 'application/json')
if 'body' in kwargs:
kwargs['body'] = json.dumps(kwargs['body'])
resp, body = self._http_request(url, method, **kwargs)
if body:
try:
body = json.loads(body)
except ValueError:
logger.debug("Could not decode JSON from body: %s" % body)
else:
logger.debug("No body was returned.")
body = None
req_url = self.endpoint + url
resp, body = self._http_request(req_url, method, **kwargs)
return resp, body
def head(self, url, **kwargs):
return self.request(url, 'HEAD', **kwargs)
def get(self, url, **kwargs):
return self.request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self.request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self.request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self.request(url, 'DELETE', **kwargs)
def raw_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
return self._http_request(url, method, **kwargs)

View File

@ -34,7 +34,7 @@ class ImageMemberManager(base.Manager):
def get(self, image, member_id):
image_id = base.getid(image)
url = '/v1/images/%s/members/%s' % (image_id, member_id)
resp, body = self.api.get(url)
resp, body = self.api.json_request('GET', url)
member = body['member']
member['image_id'] = image_id
return ImageMember(self, member, loaded=True)
@ -59,7 +59,8 @@ class ImageMemberManager(base.Manager):
def _list_by_image(self, image):
image_id = base.getid(image)
resp, body = self.api.get('/v1/images/%s/members' % image_id)
url = '/v1/images/%s/members' % image_id
resp, body = self.api.json_request('GET', url)
out = []
for member in body['members']:
member['image_id'] = image_id
@ -68,7 +69,8 @@ class ImageMemberManager(base.Manager):
def _list_by_member(self, member):
member_id = base.getid(member)
resp, body = self.api.get('/v1/shared-images/%s' % member_id)
url = '/v1/shared-images/%s' % member_id
resp, body = self.api.json_request('GET', url)
out = []
for member in body['shared_images']:
member['member_id'] = member_id
@ -98,4 +100,4 @@ class ImageMemberManager(base.Manager):
obj['can_share'] = member['can_share']
memberships.append(obj)
url = '/v1/images/%s/members' % base.getid(image)
self.api.put(url, {}, {'memberships': memberships})
self.api.json_request('PUT', url, {}, {'memberships': memberships})

View File

@ -15,6 +15,7 @@
import copy
import errno
import json
import os
import urllib
@ -67,7 +68,7 @@ class ImageManager(base.Manager):
:param image: image object or id to look up
:rtype: :class:`Image`
"""
resp, body = self.api.head('/v1/images/%s' % image_id)
resp, body = self.api.raw_request('HEAD', '/v1/images/%s' % image_id)
meta = self._image_meta_from_headers(resp)
return Image(self, meta)
@ -142,9 +143,9 @@ class ImageManager(base.Manager):
if copy_from is not None:
hdrs['x-glance-api-copy-from'] = copy_from
resp, body = self.api.post('/v1/images', headers=hdrs,
raw_body=image_data)
return Image(self, body['image'])
resp, body = self.api.raw_request(
'POST', '/v1/images', headers=hdrs, body=image_data)
return Image(self, json.loads(body)['image'])
def update(self, image, **kwargs):
"""Update an image
@ -172,7 +173,7 @@ class ImageManager(base.Manager):
if copy_from is not None:
hdrs['x-glance-api-copy-from'] = copy_from
image_id = base.getid(image)
resp, body = self.api.put('/v1/images/%s' % image_id, headers=hdrs,
raw_body=image_data)
return Image(self, body['image'])
url = '/v1/images/%s' % base.getid(image)
resp, body = self.api.raw_request(
'PUT', url, headers=hdrs, body=image_data)
return Image(self, json.loads(body)['image'])

View File

@ -13,23 +13,29 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
fixtures = {
'/v1/images': {
'POST': (
{
'location': '/v1/images/1',
},
{'image': {
'id': '1',
'name': 'image-1',
'container_format': 'ovf',
'disk_format': 'vhd',
'owner': 'asdf',
'size': '1024',
'min_ram': '512',
'min_disk': '10',
'properties': {'a': 'b', 'c': 'd'},
}}),
json.dumps(
{'image': {
'id': '1',
'name': 'image-1',
'container_format': 'ovf',
'disk_format': 'vhd',
'owner': 'asdf',
'size': '1024',
'min_ram': '512',
'min_disk': '10',
'properties': {'a': 'b', 'c': 'd'},
}},
),
),
},
'/v1/images/detail': {
'GET': (
@ -53,17 +59,19 @@ fixtures = {
None),
'PUT': (
{},
{'image': {
'id': '1',
'name': 'image-2',
'container_format': 'ovf',
'disk_format': 'vhd',
'owner': 'asdf',
'size': '1024',
'min_ram': '512',
'min_disk': '10',
'properties': {'a': 'b', 'c': 'd'},
}},
json.dumps(
{'image': {
'id': '1',
'name': 'image-2',
'container_format': 'ovf',
'disk_format': 'vhd',
'owner': 'asdf',
'size': '1024',
'min_ram': '512',
'min_disk': '10',
'properties': {'a': 'b', 'c': 'd'},
}},
),
),
'DELETE': ({}, None),
},
@ -110,17 +118,8 @@ class FakeAPI(object):
url = url.split('?', 1)[0]
return fixtures[url][method]
def get(self, url):
return self._request('GET', url)
def raw_request(self, *args, **kwargs):
return self._request(*args, **kwargs)
def head(self, url):
return self._request('HEAD', url)
def post(self, url, headers=None, body=None, raw_body=None):
return self._request('POST', url, headers, body or raw_body)
def put(self, url, headers=None, body=None, raw_body=None):
return self._request('PUT', url, headers, body or raw_body)
def delete(self, url):
return self._request('DELETE', url)
def json_request(self, *args, **kwargs):
return self._request(*args, **kwargs)