Statistics
| Branch: | Tag: | Revision:

root / kamaki / client.py @ 653b0597

History | View | Annotate | Download (10.9 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import json
35
import logging
36

    
37
from base64 import b64encode
38
from httplib import HTTPConnection, HTTPSConnection
39
from urlparse import urlparse
40

    
41

    
42
log = logging.getLogger('kamaki.client')
43

    
44

    
45
class ClientError(Exception):
46
    def __init__(self, message, status=0, details=''):
47
        self.message = message
48
        self.status = status
49
        self.details = details
50

    
51
    def __int__(self):
52
        return int(self.status)
53

    
54
    def __str__(self):
55
        r = self.message
56
        if self.status:
57
            r += "\nHTTP Status: %d" % self.status
58
        if self.details:
59
            r += "\nDetails: \n%s" % self.details
60
        return r
61

    
62

    
63
class Client(object):
64
    def __init__(self, url, token=''):
65
        self.url = url
66
        self.token = token
67
    
68
    def _cmd(self, method, path, body=None, success=200):
69
        p = urlparse(self.url)
70
        path = p.path + path
71
        if p.scheme == 'http':
72
            conn = HTTPConnection(p.netloc)
73
        elif p.scheme == 'https':
74
            conn = HTTPSConnection(p.netloc)
75
        else:
76
            raise ClientError('Unknown URL scheme')
77
        
78
        headers = {'X-Auth-Token': self.token}
79
        if body:
80
            headers['Content-Type'] = 'application/json'
81
            headers['Content-Length'] = len(body)
82
        
83
        log.debug('%s', '>' * 40)
84
        log.debug('%s %s', method, path)
85

    
86
        for key, val in headers.items():
87
            log.debug('%s: %s', key, val)
88
        log.debug('')
89
        if body:
90
            log.debug(body)
91
            log.debug('')
92
        
93
        conn.request(method, path, body, headers)
94

    
95
        resp = conn.getresponse()
96
        log.debug('%s', '<' * 40)
97
        log.info('%d %s', resp.status, resp.reason)
98
        for key, val in resp.getheaders():
99
            log.info('%s: %s', key.capitalize(), val)
100
        log.info('')
101
        
102
        buf = resp.read()
103
        try:
104
            reply = json.loads(buf) if buf else {}
105
        except ValueError:
106
            raise ClientError('Did not receive valid JSON reply',
107
                              resp.status, buf)
108
        
109
        if resp.status != success:
110
            if len(reply) == 1:
111
                key = reply.keys()[0]
112
                val = reply[key]
113
                message = '%s: %s' % (key, val.get('message', ''))
114
                details = val.get('details', '')
115
                raise ClientError(message, resp.status, details)
116
            else:
117
                raise ClientError('Invalid response from the server')
118

    
119
        return reply
120
    
121
    def _get(self, path, success=200):
122
        return self._cmd('GET', path, None, success)
123
    
124
    def _post(self, path, body, success=202):
125
        return self._cmd('POST', path, body, success)
126
    
127
    def _put(self, path, body, success=204):
128
        return self._cmd('PUT', path, body, success)
129
    
130
    def _delete(self, path, success=204):
131
        return self._cmd('DELETE', path, None, success)
132
    
133
    
134
    # Servers
135
    
136
    def list_servers(self, detail=False):
137
        path = '/servers/detail' if detail else '/servers'
138
        reply = self._get(path)
139
        return reply['servers']['values']
140
    
141
    def get_server_details(self, server_id):
142
        path = '/servers/%d' % server_id
143
        reply = self._get(path)
144
        return reply['server']
145
    
146
    def create_server(self, name, flavor, image, personality=None):
147
        """personality is a list of (path, data) tuples"""
148
        
149
        req = {'name': name, 'flavorRef': flavor, 'imageRef': image}
150
        if personality:
151
            p = []
152
            for path, data in personality:
153
                contents = b64encode(data)
154
                p.append({'path': path, 'contents': contents})
155
            req['personality'] = p
156
        
157
        body = json.dumps({'server': req})
158
        reply = self._post('/servers', body)
159
        return reply['server']
160
    
161
    def update_server_name(self, server_id, new_name):
162
        path = '/servers/%d' % server_id
163
        body = json.dumps({'server': {'name': new_name}})
164
        self._put(path, body)
165
    
166
    def delete_server(self, server_id):
167
        path = '/servers/%d' % server_id
168
        self._delete(path)
169
    
170
    def reboot_server(self, server_id, hard=False):
171
        path = '/servers/%d/action' % server_id
172
        type = 'HARD' if hard else 'SOFT'
173
        body = json.dumps({'reboot': {'type': type}})
174
        self._post(path, body)
175
    
176
    def start_server(self, server_id):
177
        path = '/servers/%d/action' % server_id
178
        body = json.dumps({'start': {}})
179
        self._post(path, body)
180
    
181
    def shutdown_server(self, server_id):
182
        path = '/servers/%d/action' % server_id
183
        body = json.dumps({'shutdown': {}})
184
        self._post(path, body)
185
    
186
    def get_server_console(self, server_id):
187
        path = '/servers/%d/action' % server_id
188
        body = json.dumps({'console': {'type': 'vnc'}})
189
        reply = self._cmd('POST', path, body, 200)
190
        return reply['console']
191
    
192
    def set_firewall_profile(self, server_id, profile):
193
        path = '/servers/%d/action' % server_id
194
        body = json.dumps({'firewallProfile': {'profile': profile}})
195
        self._cmd('POST', path, body, 202)
196
    
197
    def list_server_addresses(self, server_id, network=None):
198
        path = '/servers/%d/ips' % server_id
199
        if network:
200
            path += '/%s' % network
201
        reply = self._get(path)
202
        return [reply['network']] if network else reply['addresses']['values']
203
    
204
    def get_server_metadata(self, server_id, key=None):
205
        path = '/servers/%d/meta' % server_id
206
        if key:
207
            path += '/%s' % key
208
        reply = self._get(path)
209
        return reply['meta'] if key else reply['metadata']['values']
210
    
211
    def create_server_metadata(self, server_id, key, val):
212
        path = '/servers/%d/meta/%s' % (server_id, key)
213
        body = json.dumps({'meta': {key: val}})
214
        reply = self._put(path, body, 201)
215
        return reply['meta']
216
    
217
    def update_server_metadata(self, server_id, key, val):
218
        path = '/servers/%d/meta' % server_id
219
        body = json.dumps({'metadata': {key: val}})
220
        reply = self._post(path, body, 201)
221
        return reply['metadata']
222
    
223
    def delete_server_metadata(self, server_id, key):
224
        path = '/servers/%d/meta/%s' % (server_id, key)
225
        reply = self._delete(path)
226
    
227
    def get_server_stats(self, server_id):
228
        path = '/servers/%d/stats' % server_id
229
        reply = self._get(path)
230
        return reply['stats']
231
    
232
    
233
    # Flavors
234
    
235
    def list_flavors(self, detail=False):
236
        path = '/flavors/detail' if detail else '/flavors'
237
        reply = self._get(path)
238
        return reply['flavors']['values']
239

    
240
    def get_flavor_details(self, flavor_id):
241
        path = '/flavors/%d' % flavor_id
242
        reply = self._get(path)
243
        return reply['flavor']
244
    
245
    
246
    # Images
247
    
248
    def list_images(self, detail=False):
249
        path = '/images/detail' if detail else '/images'
250
        reply = self._get(path)
251
        return reply['images']['values']
252

    
253
    def get_image_details(self, image_id):
254
        path = '/images/%d' % image_id
255
        reply = self._get(path)
256
        return reply['image']
257

    
258
    def create_image(self, server_id, name):
259
        req = {'name': name, 'serverRef': server_id}
260
        body = json.dumps({'image': req})
261
        reply = self._post('/images', body)
262
        return reply['image']
263

    
264
    def delete_image(self, image_id):
265
        path = '/images/%d' % image_id
266
        self._delete(path)
267

    
268
    def get_image_metadata(self, image_id, key=None):
269
        path = '/images/%d/meta' % image_id
270
        if key:
271
            path += '/%s' % key
272
        reply = self._get(path)
273
        return reply['meta'] if key else reply['metadata']['values']
274
    
275
    def create_image_metadata(self, image_id, key, val):
276
        path = '/images/%d/meta/%s' % (image_id, key)
277
        body = json.dumps({'meta': {key: val}})
278
        reply = self._put(path, body, 201)
279
        reply['meta']
280

    
281
    def update_image_metadata(self, image_id, key, val):
282
        path = '/images/%d/meta' % image_id
283
        body = json.dumps({'metadata': {key: val}})
284
        reply = self._post(path, body, 201)
285
        return reply['metadata']
286

    
287
    def delete_image_metadata(self, image_id, key):
288
        path = '/images/%d/meta/%s' % (image_id, key)
289
        reply = self._delete(path)
290
    
291
    
292
    # Networks
293
    
294
    def list_networks(self, detail=False):
295
        path = '/networks/detail' if detail else '/networks'
296
        reply = self._get(path)
297
        return reply['networks']['values']
298
    
299
    def create_network(self, name):
300
        body = json.dumps({'network': {'name': name}})
301
        reply = self._post('/networks', body)
302
        return reply['network']
303
    
304
    def get_network_details(self, network_id):
305
        path = '/networks/%s' % network_id
306
        reply = self._get(path)
307
        return reply['network']
308
    
309
    def update_network_name(self, network_id, new_name):
310
        path = '/networks/%s' % network_id
311
        body = json.dumps({'network': {'name': new_name}})
312
        self._put(path, body)
313
    
314
    def delete_network(self, network_id):
315
        path = '/networks/%s' % network_id
316
        self._delete(path)
317

    
318
    def connect_server(self, server_id, network_id):
319
        path = '/networks/%s/action' % network_id
320
        body = json.dumps({'add': {'serverRef': server_id}})
321
        self._post(path, body)
322
    
323
    def disconnect_server(self, server_id, network_id):
324
        path = '/networks/%s/action' % network_id
325
        body = json.dumps({'remove': {'serverRef': server_id}})
326
        self._post(path, body)