Statistics
| Branch: | Tag: | Revision:

root / kamaki / client.py @ 76f01c50

History | View | Annotate | Download (10.5 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, details=''):
44
        self.message = message
45
        self.details = details
46

    
47

    
48
class Client(object):
49
    def __init__(self, url, token=''):
50
        self.url = url
51
        self.token = token
52
    
53
    def _cmd(self, method, path, body=None, success=200):
54
        p = urlparse(self.url)
55
        path = p.path + path
56
        if p.scheme == 'http':
57
            conn = HTTPConnection(p.netloc)
58
        elif p.scheme == 'https':
59
            conn = HTTPSConnection(p.netloc)
60
        else:
61
            raise ClientError("Unknown URL scheme")
62
        
63
        headers = {'X-Auth-Token': self.token}
64
        if body:
65
            headers['Content-Type'] = 'application/json'
66
            headers['Content-Length'] = len(body)
67
        
68
        logging.debug('%s', '>' * 40)
69
        logging.debug('%s %s', method, path)
70

    
71
        for key, val in headers.items():
72
            logging.debug('%s: %s', key, val)
73
        logging.debug('')
74
        if body:
75
            logging.debug(body)
76
            logging.debug('')
77
        
78
        conn.request(method, path, body, headers)
79

    
80
        resp = conn.getresponse()
81
        logging.debug('%s', '<' * 40)
82
        logging.info('%d %s', resp.status, resp.reason)
83
        for key, val in resp.getheaders():
84
            logging.info('%s: %s', key.capitalize(), val)
85
        logging.info('')
86
        
87
        buf = resp.read()
88
        try:
89
            reply = json.loads(buf) if buf else {}
90
        except ValueError:
91
            raise ClientError('Invalid response from the server', buf)
92
        
93
        if resp.status != success:
94
            if len(reply) == 1:
95
                key = reply.keys()[0]
96
                val = reply[key]
97
                message = '%s: %s' % (key, val.get('message', ''))
98
                details = val.get('details', '')
99
                raise ClientError(message, details)
100
            else:
101
                raise ClientError('Invalid response from the server')
102

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

    
224
    def get_flavor_details(self, flavor_id):
225
        path = '/flavors/%d' % flavor_id
226
        reply = self._get(path)
227
        return reply['flavor']
228
    
229
    
230
    # Images
231
    
232
    def list_images(self, detail=False):
233
        path = '/images/detail' if detail else '/images'
234
        reply = self._get(path)
235
        return reply['images']['values']
236

    
237
    def get_image_details(self, image_id):
238
        path = '/images/%d' % image_id
239
        reply = self._get(path)
240
        return reply['image']
241

    
242
    def create_image(self, server_id, name):
243
        req = {'name': name, 'serverRef': server_id}
244
        body = json.dumps({'image': req})
245
        reply = self._post('/images', body)
246
        return reply['image']
247

    
248
    def delete_image(self, image_id):
249
        path = '/images/%d' % image_id
250
        self._delete(path)
251

    
252
    def get_image_metadata(self, image_id, key=None):
253
        path = '/images/%d/meta' % image_id
254
        if key:
255
            path += '/%s' % key
256
        reply = self._get(path)
257
        return reply['meta'] if key else reply['metadata']['values']
258
    
259
    def create_image_metadata(self, image_id, key, val):
260
        path = '/images/%d/meta/%s' % (image_id, key)
261
        body = json.dumps({'meta': {key: val}})
262
        reply = self._put(path, body, 201)
263
        reply['meta']
264

    
265
    def update_image_metadata(self, image_id, key, val):
266
        path = '/images/%d/meta' % image_id
267
        body = json.dumps({'metadata': {key: val}})
268
        reply = self._post(path, body, 201)
269
        return reply['metadata']
270

    
271
    def delete_image_metadata(self, image_id, key):
272
        path = '/images/%d/meta/%s' % (image_id, key)
273
        reply = self._delete(path)
274
    
275
    
276
    # Networks
277
    
278
    def list_networks(self, detail=False):
279
        path = '/networks/detail' if detail else '/networks'
280
        reply = self._get(path)
281
        return reply['networks']['values']
282
    
283
    def create_network(self, name):
284
        body = json.dumps({'network': {'name': name}})
285
        reply = self._post('/networks', body)
286
        return reply['network']
287
    
288
    def get_network_details(self, network_id):
289
        path = '/networks/%s' % network_id
290
        reply = self._get(path)
291
        return reply['network']
292
    
293
    def update_network_name(self, network_id, new_name):
294
        path = '/networks/%s' % network_id
295
        body = json.dumps({'network': {'name': new_name}})
296
        self._put(path, body)
297
    
298
    def delete_network(self, network_id):
299
        path = '/networks/%s' % network_id
300
        self._delete(path)
301

    
302
    def connect_server(self, server_id, network_id):
303
        path = '/networks/%s/action' % network_id
304
        body = json.dumps({'add': {'serverRef': server_id}})
305
        self._post(path, body)
306
    
307
    def disconnect_server(self, server_id, network_id):
308
        path = '/networks/%s/action' % network_id
309
        body = json.dumps({'remove': {'serverRef': server_id}})
310
        self._post(path, body)