~hudson-openstack/glance/milestone-proposed

« back to all changes in this revision

Viewing changes to glance/client.py

  • Committer: Tarmac
  • Author(s): Brian Waldon, Yuriy Taraday, Justin Shepherd, Ewan Mellor, Thierry Carrez
  • Date: 2011-06-28 19:42:20 UTC
  • mfrom: (139.9.1 d2-merge)
  • Revision ID: tarmac-20110628194220-rhxw4nwelxeolztc
Merge diablo-2 development work

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
Client classes for callers of a Glance system
20
20
"""
21
21
 
22
 
import httplib
23
22
import json
24
 
import logging
25
 
import urlparse
26
 
import socket
27
 
import sys
28
 
import urllib
29
23
 
 
24
from glance.api.v1 import images as v1_images
 
25
from glance.common import client as base_client
 
26
from glance.common import exception
30
27
from glance import utils
31
 
from glance.common import exception
32
28
 
33
29
#TODO(jaypipes) Allow a logger param for client classes
34
30
 
35
31
 
36
 
class ClientConnectionError(Exception):
37
 
    """Error resulting from a client connecting to a server"""
38
 
    pass
39
 
 
40
 
 
41
 
class ImageBodyIterator(object):
42
 
 
43
 
    """
44
 
    A class that acts as an iterator over an image file's
45
 
    chunks of data.  This is returned as part of the result
46
 
    tuple from `glance.client.Client.get_image`
47
 
    """
48
 
 
49
 
    CHUNKSIZE = 65536
50
 
 
51
 
    def __init__(self, response):
52
 
        """
53
 
        Constructs the object from an HTTPResponse object
54
 
        """
55
 
        self.response = response
56
 
 
57
 
    def __iter__(self):
58
 
        """
59
 
        Exposes an iterator over the chunks of data in the
60
 
        image file.
61
 
        """
62
 
        while True:
63
 
            chunk = self.response.read(ImageBodyIterator.CHUNKSIZE)
64
 
            if chunk:
65
 
                yield chunk
66
 
            else:
67
 
                break
68
 
 
69
 
 
70
 
class BaseClient(object):
71
 
 
72
 
    """A base client class"""
73
 
 
74
 
    CHUNKSIZE = 65536
75
 
 
76
 
    def __init__(self, host, port, use_ssl):
77
 
        """
78
 
        Creates a new client to some service.
79
 
 
80
 
        :param host: The host where service resides
81
 
        :param port: The port where service resides
82
 
        :param use_ssl: Should we use HTTPS?
83
 
        """
84
 
        self.host = host
85
 
        self.port = port
86
 
        self.use_ssl = use_ssl
87
 
        self.connection = None
88
 
 
89
 
    def get_connection_type(self):
90
 
        """
91
 
        Returns the proper connection type
92
 
        """
93
 
        if self.use_ssl:
94
 
            return httplib.HTTPSConnection
95
 
        else:
96
 
            return httplib.HTTPConnection
97
 
 
98
 
    def do_request(self, method, action, body=None, headers=None,
99
 
                   params=None):
100
 
        """
101
 
        Connects to the server and issues a request.  Handles converting
102
 
        any returned HTTP error status codes to OpenStack/Glance exceptions
103
 
        and closing the server connection. Returns the result data, or
104
 
        raises an appropriate exception.
105
 
 
106
 
        :param method: HTTP method ("GET", "POST", "PUT", etc...)
107
 
        :param action: part of URL after root netloc
108
 
        :param body: string of data to send, or None (default)
109
 
        :param headers: mapping of key/value pairs to add as headers
110
 
        :param params: dictionary of key/value pairs to add to append
111
 
                             to action
112
 
 
113
 
        :note
114
 
 
115
 
        If the body param has a read attribute, and method is either
116
 
        POST or PUT, this method will automatically conduct a chunked-transfer
117
 
        encoding and use the body as a file object, transferring chunks
118
 
        of data using the connection's send() method. This allows large
119
 
        objects to be transferred efficiently without buffering the entire
120
 
        body in memory.
121
 
        """
122
 
        if type(params) is dict:
123
 
            action += '?' + urllib.urlencode(params)
124
 
 
125
 
        try:
126
 
            connection_type = self.get_connection_type()
127
 
            headers = headers or {}
128
 
            c = connection_type(self.host, self.port)
129
 
 
130
 
            # Do a simple request or a chunked request, depending
131
 
            # on whether the body param is a file-like object and
132
 
            # the method is PUT or POST
133
 
            if hasattr(body, 'read') and method.lower() in ('post', 'put'):
134
 
                # Chunk it, baby...
135
 
                c.putrequest(method, action)
136
 
 
137
 
                for header, value in headers.items():
138
 
                    c.putheader(header, value)
139
 
                c.putheader('Transfer-Encoding', 'chunked')
140
 
                c.endheaders()
141
 
 
142
 
                chunk = body.read(self.CHUNKSIZE)
143
 
                while chunk:
144
 
                    c.send('%x\r\n%s\r\n' % (len(chunk), chunk))
145
 
                    chunk = body.read(self.CHUNKSIZE)
146
 
                c.send('0\r\n\r\n')
147
 
            else:
148
 
                # Simple request...
149
 
                c.request(method, action, body, headers)
150
 
            res = c.getresponse()
151
 
            status_code = self.get_status_code(res)
152
 
            if status_code in (httplib.OK,
153
 
                               httplib.CREATED,
154
 
                               httplib.ACCEPTED,
155
 
                               httplib.NO_CONTENT):
156
 
                return res
157
 
            elif status_code == httplib.UNAUTHORIZED:
158
 
                raise exception.NotAuthorized
159
 
            elif status_code == httplib.FORBIDDEN:
160
 
                raise exception.NotAuthorized
161
 
            elif status_code == httplib.NOT_FOUND:
162
 
                raise exception.NotFound
163
 
            elif status_code == httplib.CONFLICT:
164
 
                raise exception.Duplicate(res.read())
165
 
            elif status_code == httplib.BAD_REQUEST:
166
 
                raise exception.Invalid(res.read())
167
 
            elif status_code == httplib.INTERNAL_SERVER_ERROR:
168
 
                raise Exception("Internal Server error: %s" % res.read())
169
 
            else:
170
 
                raise Exception("Unknown error occurred! %s" % res.read())
171
 
 
172
 
        except (socket.error, IOError), e:
173
 
            raise ClientConnectionError("Unable to connect to "
174
 
                                        "server. Got error: %s" % e)
175
 
 
176
 
    def get_status_code(self, response):
177
 
        """
178
 
        Returns the integer status code from the response, which
179
 
        can be either a Webob.Response (used in testing) or httplib.Response
180
 
        """
181
 
        if hasattr(response, 'status_int'):
182
 
            return response.status_int
183
 
        else:
184
 
            return response.status
185
 
 
186
 
 
187
 
class V1Client(BaseClient):
 
32
class V1Client(base_client.BaseClient):
188
33
 
189
34
    """Main client class for accessing Glance resources"""
190
35
 
209
54
        return super(V1Client, self).do_request(method, action, body,
210
55
                                                headers, params)
211
56
 
212
 
    def get_images(self, filters=None, marker=None, limit=None):
 
57
    def get_images(self, **kwargs):
213
58
        """
214
59
        Returns a list of image id/name mappings from Registry
215
60
 
217
62
                        collection of images should be filtered
218
63
        :param marker: id after which to start the page of images
219
64
        :param limit: maximum number of items to return
 
65
        :param sort_key: results will be ordered by this image attribute
 
66
        :param sort_dir: direction in which to to order results (asc, desc)
220
67
        """
221
 
 
222
 
        params = filters or {}
223
 
        if marker:
224
 
            params['marker'] = marker
225
 
        if limit:
226
 
            params['limit'] = limit
 
68
        params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
227
69
        res = self.do_request("GET", "/images", params=params)
228
70
        data = json.loads(res.read())['images']
229
71
        return data
230
72
 
231
 
    def get_images_detailed(self, filters=None, marker=None, limit=None):
 
73
    def get_images_detailed(self, **kwargs):
232
74
        """
233
75
        Returns a list of detailed image data mappings from Registry
234
76
 
236
78
                        collection of images should be filtered
237
79
        :param marker: id after which to start the page of images
238
80
        :param limit: maximum number of items to return
 
81
        :param sort_key: results will be ordered by this image attribute
 
82
        :param sort_dir: direction in which to to order results (asc, desc)
239
83
        """
240
84
 
241
 
        params = filters or {}
242
 
        if marker:
243
 
            params['marker'] = marker
244
 
        if limit:
245
 
            params['limit'] = limit
 
85
        params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
246
86
        res = self.do_request("GET", "/images/detail", params=params)
247
87
        data = json.loads(res.read())['images']
248
88
        return data
260
100
        res = self.do_request("GET", "/images/%s" % image_id)
261
101
 
262
102
        image = utils.get_image_meta_from_headers(res)
263
 
        return image, ImageBodyIterator(res)
 
103
        return image, base_client.ImageBodyIterator(res)
264
104
 
265
105
    def get_image_meta(self, image_id):
266
106
        """