~hudson-openstack/glance/milestone-proposed

« back to all changes in this revision

Viewing changes to glance/common/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:
 
1
import httplib
 
2
import logging
 
3
import socket
 
4
import urllib
 
5
 
 
6
from glance.common import exception
 
7
 
 
8
 
 
9
class ImageBodyIterator(object):
 
10
 
 
11
    """
 
12
    A class that acts as an iterator over an image file's
 
13
    chunks of data.  This is returned as part of the result
 
14
    tuple from `glance.client.Client.get_image`
 
15
    """
 
16
 
 
17
    CHUNKSIZE = 65536
 
18
 
 
19
    def __init__(self, response):
 
20
        """
 
21
        Constructs the object from an HTTPResponse object
 
22
        """
 
23
        self.response = response
 
24
 
 
25
    def __iter__(self):
 
26
        """
 
27
        Exposes an iterator over the chunks of data in the
 
28
        image file.
 
29
        """
 
30
        while True:
 
31
            chunk = self.response.read(ImageBodyIterator.CHUNKSIZE)
 
32
            if chunk:
 
33
                yield chunk
 
34
            else:
 
35
                break
 
36
 
 
37
 
 
38
class BaseClient(object):
 
39
 
 
40
    """A base client class"""
 
41
 
 
42
    CHUNKSIZE = 65536
 
43
 
 
44
    def __init__(self, host, port, use_ssl):
 
45
        """
 
46
        Creates a new client to some service.
 
47
 
 
48
        :param host: The host where service resides
 
49
        :param port: The port where service resides
 
50
        :param use_ssl: Should we use HTTPS?
 
51
        """
 
52
        self.host = host
 
53
        self.port = port
 
54
        self.use_ssl = use_ssl
 
55
        self.connection = None
 
56
 
 
57
    def get_connection_type(self):
 
58
        """
 
59
        Returns the proper connection type
 
60
        """
 
61
        if self.use_ssl:
 
62
            return httplib.HTTPSConnection
 
63
        else:
 
64
            return httplib.HTTPConnection
 
65
 
 
66
    def do_request(self, method, action, body=None, headers=None,
 
67
                   params=None):
 
68
        """
 
69
        Connects to the server and issues a request.  Handles converting
 
70
        any returned HTTP error status codes to OpenStack/Glance exceptions
 
71
        and closing the server connection. Returns the result data, or
 
72
        raises an appropriate exception.
 
73
 
 
74
        :param method: HTTP method ("GET", "POST", "PUT", etc...)
 
75
        :param action: part of URL after root netloc
 
76
        :param body: string of data to send, or None (default)
 
77
        :param headers: mapping of key/value pairs to add as headers
 
78
        :param params: dictionary of key/value pairs to add to append
 
79
                             to action
 
80
 
 
81
        :note
 
82
 
 
83
        If the body param has a read attribute, and method is either
 
84
        POST or PUT, this method will automatically conduct a chunked-transfer
 
85
        encoding and use the body as a file object, transferring chunks
 
86
        of data using the connection's send() method. This allows large
 
87
        objects to be transferred efficiently without buffering the entire
 
88
        body in memory.
 
89
        """
 
90
        if type(params) is dict:
 
91
            action += '?' + urllib.urlencode(params)
 
92
 
 
93
        try:
 
94
            connection_type = self.get_connection_type()
 
95
            headers = headers or {}
 
96
            c = connection_type(self.host, self.port)
 
97
 
 
98
            # Do a simple request or a chunked request, depending
 
99
            # on whether the body param is a file-like object and
 
100
            # the method is PUT or POST
 
101
            if hasattr(body, 'read') and method.lower() in ('post', 'put'):
 
102
                # Chunk it, baby...
 
103
                c.putrequest(method, action)
 
104
 
 
105
                for header, value in headers.items():
 
106
                    c.putheader(header, value)
 
107
                c.putheader('Transfer-Encoding', 'chunked')
 
108
                c.endheaders()
 
109
 
 
110
                chunk = body.read(self.CHUNKSIZE)
 
111
                while chunk:
 
112
                    c.send('%x\r\n%s\r\n' % (len(chunk), chunk))
 
113
                    chunk = body.read(self.CHUNKSIZE)
 
114
                c.send('0\r\n\r\n')
 
115
            else:
 
116
                # Simple request...
 
117
                c.request(method, action, body, headers)
 
118
            res = c.getresponse()
 
119
            status_code = self.get_status_code(res)
 
120
            if status_code in (httplib.OK,
 
121
                               httplib.CREATED,
 
122
                               httplib.ACCEPTED,
 
123
                               httplib.NO_CONTENT):
 
124
                return res
 
125
            elif status_code == httplib.UNAUTHORIZED:
 
126
                raise exception.NotAuthorized
 
127
            elif status_code == httplib.FORBIDDEN:
 
128
                raise exception.NotAuthorized
 
129
            elif status_code == httplib.NOT_FOUND:
 
130
                raise exception.NotFound
 
131
            elif status_code == httplib.CONFLICT:
 
132
                raise exception.Duplicate(res.read())
 
133
            elif status_code == httplib.BAD_REQUEST:
 
134
                raise exception.Invalid(res.read())
 
135
            elif status_code == httplib.INTERNAL_SERVER_ERROR:
 
136
                raise Exception("Internal Server error: %s" % res.read())
 
137
            else:
 
138
                raise Exception("Unknown error occurred! %s" % res.read())
 
139
 
 
140
        except (socket.error, IOError), e:
 
141
            raise exception.ClientConnectionError("Unable to connect to "
 
142
                                                  "server. Got error: %s" % e)
 
143
 
 
144
    def get_status_code(self, response):
 
145
        """
 
146
        Returns the integer status code from the response, which
 
147
        can be either a Webob.Response (used in testing) or httplib.Response
 
148
        """
 
149
        if hasattr(response, 'status_int'):
 
150
            return response.status_int
 
151
        else:
 
152
            return response.status
 
153
 
 
154
    def _extract_params(self, actual_params, allowed_params):
 
155
        """
 
156
        Extract a subset of keys from a dictionary. The filters key
 
157
        will also be extracted, and each of its values will be returned
 
158
        as an individual param.
 
159
 
 
160
        :param actual_params: dict of keys to filter
 
161
        :param allowed_params: list of keys that 'actual_params' will be
 
162
                               reduced to
 
163
        :retval subset of 'params' dict
 
164
        """
 
165
        result = actual_params.get('filters', {})
 
166
        for allowed_param in allowed_params:
 
167
            if allowed_param in actual_params:
 
168
                result[allowed_param] = actual_params[allowed_param]
 
169
        return result