~raxnetworking/nova/bare_bones_melange

« back to all changes in this revision

Viewing changes to nova/network/quantum/client.py

  • Committer: Rajaram Mallya
  • Date: 2011-09-12 10:03:21 UTC
  • mfrom: (1265.2.287 nova)
  • Revision ID: rajarammallya@gmail.com-20110912100321-8zw8575a206dc026
Merged from nova trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2011 Citrix Systems
 
4
# All Rights Reserved.
 
5
#
 
6
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
7
#    not use this file except in compliance with the License. You may obtain
 
8
#    a copy of the License at
 
9
#
 
10
#         http://www.apache.org/licenses/LICENSE-2.0
 
11
#
 
12
#    Unless required by applicable law or agreed to in writing, software
 
13
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
14
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
15
#    License for the specific language governing permissions and limitations
 
16
#    under the License.
 
17
#    @author: Tyler Smith, Cisco Systems
 
18
 
 
19
import httplib
 
20
import json
 
21
import socket
 
22
import urllib
 
23
 
 
24
 
 
25
# FIXME(danwent): All content in this file should be removed once the
 
26
# packaging work for the quantum client libraries is complete.
 
27
# At that point, we will be able to just install the libraries as a
 
28
# dependency and import from quantum.client.* and quantum.common.*
 
29
# Until then, we have simplified versions of these classes in this file.
 
30
 
 
31
class JSONSerializer(object):
 
32
    """This is a simple json-only serializer to use until we can just grab
 
33
    the standard serializer from the quantum library.
 
34
    """
 
35
    def serialize(self, data, content_type):
 
36
        try:
 
37
            return json.dumps(data)
 
38
        except TypeError:
 
39
            pass
 
40
        return json.dumps(to_primitive(data))
 
41
 
 
42
    def deserialize(self, data, content_type):
 
43
        return json.loads(data)
 
44
 
 
45
 
 
46
# The full client lib will expose more
 
47
# granular exceptions, for now, just try to distinguish
 
48
# between the cases we care about.
 
49
class QuantumNotFoundException(Exception):
 
50
    """Indicates that Quantum Server returned 404"""
 
51
    pass
 
52
 
 
53
 
 
54
class QuantumServerException(Exception):
 
55
    """Indicates any non-404 error from Quantum Server"""
 
56
    pass
 
57
 
 
58
 
 
59
class QuantumIOException(Exception):
 
60
    """Indicates network IO trouble reaching Quantum Server"""
 
61
    pass
 
62
 
 
63
 
 
64
class api_call(object):
 
65
    """A Decorator to add support for format and tenant overriding"""
 
66
    def __init__(self, func):
 
67
        self.func = func
 
68
 
 
69
    def __get__(self, instance, owner):
 
70
        def with_params(*args, **kwargs):
 
71
            """Temporarily set format and tenant for this request"""
 
72
            (format, tenant) = (instance.format, instance.tenant)
 
73
 
 
74
            if 'format' in kwargs:
 
75
                instance.format = kwargs['format']
 
76
            if 'tenant' in kwargs:
 
77
                instance.tenant = kwargs['tenant']
 
78
 
 
79
            ret = None
 
80
            try:
 
81
                ret = self.func(instance, *args)
 
82
            finally:
 
83
                (instance.format, instance.tenant) = (format, tenant)
 
84
            return ret
 
85
        return with_params
 
86
 
 
87
 
 
88
class Client(object):
 
89
    """A base client class - derived from Glance.BaseClient"""
 
90
 
 
91
    action_prefix = '/v1.0/tenants/{tenant_id}'
 
92
 
 
93
    """Action query strings"""
 
94
    networks_path = "/networks"
 
95
    network_path = "/networks/%s"
 
96
    ports_path = "/networks/%s/ports"
 
97
    port_path = "/networks/%s/ports/%s"
 
98
    attachment_path = "/networks/%s/ports/%s/attachment"
 
99
 
 
100
    def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
 
101
                 format="xml", testing_stub=None, key_file=None,
 
102
                 cert_file=None, logger=None):
 
103
        """Creates a new client to some service.
 
104
 
 
105
        :param host: The host where service resides
 
106
        :param port: The port where service resides
 
107
        :param use_ssl: True to use SSL, False to use HTTP
 
108
        :param tenant: The tenant ID to make requests with
 
109
        :param format: The format to query the server with
 
110
        :param testing_stub: A class that stubs basic server methods for tests
 
111
        :param key_file: The SSL key file to use if use_ssl is true
 
112
        :param cert_file: The SSL cert file to use if use_ssl is true
 
113
        """
 
114
        self.host = host
 
115
        self.port = port
 
116
        self.use_ssl = use_ssl
 
117
        self.tenant = tenant
 
118
        self.format = format
 
119
        self.connection = None
 
120
        self.testing_stub = testing_stub
 
121
        self.key_file = key_file
 
122
        self.cert_file = cert_file
 
123
        self.logger = logger
 
124
 
 
125
    def get_connection_type(self):
 
126
        """Returns the proper connection type"""
 
127
        if self.testing_stub:
 
128
            return self.testing_stub
 
129
        elif self.use_ssl:
 
130
            return httplib.HTTPSConnection
 
131
        else:
 
132
            return httplib.HTTPConnection
 
133
 
 
134
    def do_request(self, method, action, body=None,
 
135
                   headers=None, params=None):
 
136
        """Connects to the server and issues a request.
 
137
        Returns the result data, or raises an appropriate exception if
 
138
        HTTP status code is not 2xx
 
139
 
 
140
        :param method: HTTP method ("GET", "POST", "PUT", etc...)
 
141
        :param body: string of data to send, or None (default)
 
142
        :param headers: mapping of key/value pairs to add as headers
 
143
        :param params: dictionary of key/value pairs to add to append
 
144
                             to action
 
145
        """
 
146
 
 
147
        # Ensure we have a tenant id
 
148
        if not self.tenant:
 
149
            raise Exception(_("Tenant ID not set"))
 
150
 
 
151
        # Add format and tenant_id
 
152
        action += ".%s" % self.format
 
153
        action = Client.action_prefix + action
 
154
        action = action.replace('{tenant_id}', self.tenant)
 
155
 
 
156
        if type(params) is dict:
 
157
            action += '?' + urllib.urlencode(params)
 
158
 
 
159
        try:
 
160
            connection_type = self.get_connection_type()
 
161
            headers = headers or {"Content-Type":
 
162
                                      "application/%s" % self.format}
 
163
 
 
164
            # Open connection and send request, handling SSL certs
 
165
            certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
 
166
            certs = dict((x, certs[x]) for x in certs if certs[x] != None)
 
167
 
 
168
            if self.use_ssl and len(certs):
 
169
                c = connection_type(self.host, self.port, **certs)
 
170
            else:
 
171
                c = connection_type(self.host, self.port)
 
172
 
 
173
            if self.logger:
 
174
                self.logger.debug(
 
175
                    _("Quantum Client Request:\n%(method)s %(action)s\n" %
 
176
                                    locals()))
 
177
                if body:
 
178
                    self.logger.debug(body)
 
179
 
 
180
            c.request(method, action, body, headers)
 
181
            res = c.getresponse()
 
182
            status_code = self.get_status_code(res)
 
183
            data = res.read()
 
184
 
 
185
            if self.logger:
 
186
                self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \
 
187
                        % (str(status_code), data))
 
188
 
 
189
            if status_code == httplib.NOT_FOUND:
 
190
                raise QuantumNotFoundException(
 
191
                    _("Quantum entity not found: %s" % data))
 
192
 
 
193
            if status_code in (httplib.OK,
 
194
                               httplib.CREATED,
 
195
                               httplib.ACCEPTED,
 
196
                               httplib.NO_CONTENT):
 
197
                if data is not None and len(data):
 
198
                    return self.deserialize(data, status_code)
 
199
            else:
 
200
                raise QuantumServerException(
 
201
                      _("Server %(status_code)s error: %(data)s"
 
202
                                        % locals()))
 
203
 
 
204
        except (socket.error, IOError), e:
 
205
            raise QuantumIOException(_("Unable to connect to "
 
206
                              "server. Got error: %s" % e))
 
207
 
 
208
    def get_status_code(self, response):
 
209
        """Returns the integer status code from the response, which
 
210
        can be either a Webob.Response (used in testing) or httplib.Response
 
211
        """
 
212
        if hasattr(response, 'status_int'):
 
213
            return response.status_int
 
214
        else:
 
215
            return response.status
 
216
 
 
217
    def serialize(self, data):
 
218
        if not data:
 
219
            return None
 
220
        elif type(data) is dict:
 
221
            return JSONSerializer().serialize(data, self.content_type())
 
222
        else:
 
223
            raise Exception(_("unable to deserialize object of type = '%s'" %
 
224
                              type(data)))
 
225
 
 
226
    def deserialize(self, data, status_code):
 
227
        if status_code == 202:
 
228
            return data
 
229
        return JSONSerializer().deserialize(data, self.content_type())
 
230
 
 
231
    def content_type(self, format=None):
 
232
        if not format:
 
233
            format = self.format
 
234
        return "application/%s" % (format)
 
235
 
 
236
    @api_call
 
237
    def list_networks(self):
 
238
        """Fetches a list of all networks for a tenant"""
 
239
        return self.do_request("GET", self.networks_path)
 
240
 
 
241
    @api_call
 
242
    def show_network_details(self, network):
 
243
        """Fetches the details of a certain network"""
 
244
        return self.do_request("GET", self.network_path % (network))
 
245
 
 
246
    @api_call
 
247
    def create_network(self, body=None):
 
248
        """Creates a new network"""
 
249
        body = self.serialize(body)
 
250
        return self.do_request("POST", self.networks_path, body=body)
 
251
 
 
252
    @api_call
 
253
    def update_network(self, network, body=None):
 
254
        """Updates a network"""
 
255
        body = self.serialize(body)
 
256
        return self.do_request("PUT", self.network_path % (network), body=body)
 
257
 
 
258
    @api_call
 
259
    def delete_network(self, network):
 
260
        """Deletes the specified network"""
 
261
        return self.do_request("DELETE", self.network_path % (network))
 
262
 
 
263
    @api_call
 
264
    def list_ports(self, network):
 
265
        """Fetches a list of ports on a given network"""
 
266
        return self.do_request("GET", self.ports_path % (network))
 
267
 
 
268
    @api_call
 
269
    def show_port_details(self, network, port):
 
270
        """Fetches the details of a certain port"""
 
271
        return self.do_request("GET", self.port_path % (network, port))
 
272
 
 
273
    @api_call
 
274
    def create_port(self, network, body=None):
 
275
        """Creates a new port on a given network"""
 
276
        body = self.serialize(body)
 
277
        return self.do_request("POST", self.ports_path % (network), body=body)
 
278
 
 
279
    @api_call
 
280
    def delete_port(self, network, port):
 
281
        """Deletes the specified port from a network"""
 
282
        return self.do_request("DELETE", self.port_path % (network, port))
 
283
 
 
284
    @api_call
 
285
    def set_port_state(self, network, port, body=None):
 
286
        """Sets the state of the specified port"""
 
287
        body = self.serialize(body)
 
288
        return self.do_request("PUT",
 
289
            self.port_path % (network, port), body=body)
 
290
 
 
291
    @api_call
 
292
    def show_port_attachment(self, network, port):
 
293
        """Fetches the attachment-id associated with the specified port"""
 
294
        return self.do_request("GET", self.attachment_path % (network, port))
 
295
 
 
296
    @api_call
 
297
    def attach_resource(self, network, port, body=None):
 
298
        """Sets the attachment-id of the specified port"""
 
299
        body = self.serialize(body)
 
300
        return self.do_request("PUT",
 
301
            self.attachment_path % (network, port), body=body)
 
302
 
 
303
    @api_call
 
304
    def detach_resource(self, network, port):
 
305
        """Removes the attachment-id of the specified port"""
 
306
        return self.do_request("DELETE",
 
307
                               self.attachment_path % (network, port))