From: Giorgos Verigakis Date: Thu, 23 Feb 2012 12:51:42 +0000 (+0200) Subject: Update image client to the new infrastructure X-Git-Tag: v0.6~309 X-Git-Url: https://code.grnet.gr/git/kamaki/commitdiff_plain/cd75ff39fa471f32d9cf3cb8f45d7837389f4cac Update image client to the new infrastructure Delete obsolete HTTPClient. --- diff --git a/kamaki/clients/http.py b/kamaki/clients/http.py deleted file mode 100644 index fa675f3..0000000 --- a/kamaki/clients/http.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright 2011 GRNET S.A. All rights reserved. -# -# Redistribution and use in source and binary forms, with or -# without modification, are permitted provided that the following -# conditions are met: -# -# 1. Redistributions of source code must retain the above -# copyright notice, this list of conditions and the following -# disclaimer. -# -# 2. Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials -# provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS -# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# The views and conclusions contained in the software and -# documentation are those of the authors and should not be -# interpreted as representing official policies, either expressed -# or implied, of GRNET S.A. - -import json -import logging - -from httplib import HTTPConnection, HTTPSConnection -from urlparse import urlparse - -from . import ClientError - - -log = logging.getLogger('kamaki.clients') - - -class HTTPClient(object): - def __init__(self, config): - self.config = config - - @property - def url(self): - url = self.config.get('url') - if not url: - raise ClientError('No URL was given') - return url - - @property - def token(self): - token = self.config.get('token') - if not token: - raise ClientError('No token was given') - return token - - def raw_http_cmd(self, method, path, body=None, headers=None, success=200, - json_reply=False, skip_read=False): - p = urlparse(self.url) - path = p.path + path - if p.scheme == 'http': - conn = HTTPConnection(p.netloc) - elif p.scheme == 'https': - conn = HTTPSConnection(p.netloc) - else: - raise ClientError('Unknown URL scheme') - - headers = headers or {} - headers['X-Auth-Token'] = self.token - if body: - headers.setdefault('Content-Type', 'application/json') - headers['Content-Length'] = len(body) - - log.debug('>' * 50) - log.debug('%s %s', method, path) - for key, val in headers.items(): - log.debug('%s: %s', key, val) - if body: - log.debug('') - log.debug(body) - - conn.request(method, path, body, headers) - - resp = conn.getresponse() - reply = '' if skip_read else resp.read() - - log.debug('<' * 50) - log.info('%d %s', resp.status, resp.reason) - for key, val in resp.getheaders(): - log.info('%s: %s', key.capitalize(), val) - log.info('') - log.debug(reply) - log.debug('-' * 50) - - if json_reply: - try: - reply = json.loads(reply) if reply else {} - except ValueError: - raise ClientError('Did not receive valid JSON reply', - resp.status, reply) - - if success and resp.status != success: - if len(reply) == 1: - if json_reply: - key = reply.keys()[0] - val = reply[key] - message = '%s: %s' % (key, val.get('message', '')) - details = val.get('details', '') - else: - message = reply - details = '' - - raise ClientError(message, resp.status, details) - else: - raise ClientError('Invalid response from the server') - - return resp, reply - - def http_cmd(self, method, path, body=None, headers=None, success=200): - resp, reply = self.raw_http_cmd(method, path, body, headers, success, - json_reply=True) - return reply - - def http_get(self, path, success=200): - return self.http_cmd('GET', path, success=success) - - def http_post(self, path, body=None, headers=None, success=202): - return self.http_cmd('POST', path, body, headers, success) - - def http_put(self, path, body=None, headers=None, success=204): - return self.http_cmd('PUT', path, body, headers, success) - - def http_delete(self, path, success=204): - return self.http_cmd('DELETE', path, success=success) diff --git a/kamaki/clients/image.py b/kamaki/clients/image.py index 6495ce2..0156ef7 100644 --- a/kamaki/clients/image.py +++ b/kamaki/clients/image.py @@ -31,30 +31,19 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. -""" - OpenStack Image Service API 1.0 client -""" -from urllib import quote +from . import Client, ClientError -from . import ClientError -from .http import HTTPClient - -class ImageClient(HTTPClient): - @property - def url(self): - url = self.config.get('image_url') or self.config.get('url') - if not url: - raise ClientError('No URL was given') - return url +class ImageClient(Client): + """OpenStack Image Service API 1.0 and GRNET Plankton client""" - @property - def token(self): - token = self.config.get('image_token') or self.config.get('token') - if not token: - raise ClientError('No token was given') - return token + def raise_for_status(self, r): + if r.status_code == 404: + raise ClientError("Image not found", r.status_code) + + # Fallback to the default + super(ImageClient, self).raise_for_status(r) def list_public(self, detail=False, filters={}, order=''): path = '/images/detail' if detail else '/images/' @@ -70,57 +59,67 @@ class ImageClient(HTTPClient): if order: params['sort_key'] = order - if params: - path += '?' + '&'.join('%s=%s' % item for item in params.items()) - return self.http_get(path) + r = self.get(path, params=params, success=200) + return r.json def get_meta(self, image_id): - path = '/images/%s' % image_id - resp, buf = self.raw_http_cmd('HEAD', path) + path = '/images/%s' % (image_id,) + r = self.head(path, success=200) + reply = {} - prefix = 'x-image-meta-' - for key, val in resp.getheaders(): + properties = {} + meta_prefix = 'x-image-meta-' + property_prefix = 'x-image-meta-property-' + + for key, val in r.headers.items(): key = key.lower() - if not key.startswith(prefix): - continue - key = key[len(prefix):] - reply[key] = val + if key.startswith(property_prefix): + key = key[len(property_prefix):] + properties[key] = val + elif key.startswith(meta_prefix): + key = key[len(meta_prefix):] + reply[key] = val + + if properties: + reply['properties'] = properties return reply def register(self, name, location, params={}, properties={}): path = '/images/' headers = {} - headers['x-image-meta-name'] = quote(name) + headers['x-image-meta-name'] = name headers['x-image-meta-location'] = location + for key, val in params.items(): if key in ('id', 'store', 'disk_format', 'container_format', 'size', 'checksum', 'is_public', 'owner'): key = 'x-image-meta-' + key.replace('_', '-') headers[key] = val + for key, val in properties.items(): - headers['x-image-meta-property-' + quote(key)] = quote(val) - return self.http_post(path, headers=headers, success=200) + headers['x-image-meta-property-' + key] = val + + self.post(path, headers=headers, success=200) def list_members(self, image_id): - path = '/images/%s/members' % image_id - reply = self.http_get(path) - return reply['members'] + path = '/images/%s/members' % (image_id,) + r = self.get(path, success=200) + return r.json['members'] def list_shared(self, member): - path = '/shared-images/%s' % member - reply = self.http_get(path) - return reply['shared_images'] + path = '/shared-images/%s' % (member,) + r = self.get(path, success=200) + return r.json['shared_images'] def add_member(self, image_id, member): path = '/images/%s/members/%s' % (image_id, member) - self.http_put(path) + r = self.put(path, success=204) def remove_member(self, image_id, member): path = '/images/%s/members/%s' % (image_id, member) - self.http_delete(path) + self.delete(path, success=204) def set_members(self, image_id, members): path = '/images/%s/members' % image_id req = {'memberships': [{'member_id': member} for member in members]} - body = json.dumps(req) - self.http_put(path, body) + self.put(path, json=req, success=204)