+++ /dev/null
-# 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)
# 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/'
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)