Statistics
| Branch: | Tag: | Revision:

root / kamaki / client.py @ 6d604f07

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
class ClientError(Exception):
43
    def __init__(self, message, status=0, details=''):
44
        self.message = message
45
        self.status = status
46
        self.details = details
47

    
48
    def __int__(self):
49
        return int(self.status)
50

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

    
59

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

    
83
        for key, val in headers.items():
84
            logging.debug('%s: %s', key, val)
85
        logging.debug('')
86
        if body:
87
            logging.debug(body)
88
            logging.debug('')
89
        
90
        conn.request(method, path, body, headers)
91

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

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

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

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

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

    
261
    def delete_image(self, image_id):
262
        path = '/images/%d' % image_id
263
        self._delete(path)
264

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

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

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

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