Statistics
| Branch: | Tag: | Revision:

root / kamaki / client.py @ 5d1d131b

History | View | Annotate | Download (10.2 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 httplib import HTTPConnection, HTTPSConnection
38
from urlparse import urlparse
39

    
40

    
41
class ClientError(Exception):
42
    def __init__(self, message, details=''):
43
        self.message = message
44
        self.details = details
45

    
46

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

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

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

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

    
214
    def get_flavor_details(self, flavor_id):
215
        path = '/flavors/%d' % flavor_id
216
        reply = self._get(path)
217
        return reply['flavor']
218
    
219
    
220
    # Images
221
    
222
    def list_images(self, detail=False):
223
        path = '/images/detail' if detail else '/images'
224
        reply = self._get(path)
225
        return reply['images']['values']
226

    
227
    def get_image_details(self, image_id):
228
        path = '/images/%d' % image_id
229
        reply = self._get(path)
230
        return reply['image']
231

    
232
    def create_image(self, server_id, name):
233
        req = {'name': name, 'serverRef': server_id}
234
        body = json.dumps({'image': req})
235
        reply = self._post('/images', body)
236
        return reply['image']
237

    
238
    def delete_image(self, image_id):
239
        path = '/images/%d' % image_id
240
        self._delete(path)
241

    
242
    def get_image_metadata(self, image_id, key=None):
243
        path = '/images/%d/meta' % image_id
244
        if key:
245
            path += '/%s' % key
246
        reply = self._get(path)
247
        return reply['meta'] if key else reply['metadata']['values']
248
    
249
    def create_image_metadata(self, image_id, key, val):
250
        path = '/images/%d/meta/%s' % (image_id, key)
251
        body = json.dumps({'meta': {key: val}})
252
        reply = self._put(path, body, 201)
253
        reply['meta']
254

    
255
    def update_image_metadata(self, image_id, key, val):
256
        path = '/images/%d/meta' % image_id
257
        body = json.dumps({'metadata': {key: val}})
258
        reply = self._post(path, body, 201)
259
        return reply['metadata']
260

    
261
    def delete_image_metadata(self, image_id, key):
262
        path = '/images/%d/meta/%s' % (image_id, key)
263
        reply = self._delete(path)
264
    
265
    
266
    # Networks
267
    
268
    def list_networks(self, detail=False):
269
        path = '/networks/detail' if detail else '/networks'
270
        reply = self._get(path)
271
        return reply['networks']['values']
272
    
273
    def create_network(self, name):
274
        body = json.dumps({'network': {'name': name}})
275
        reply = self._post('/networks', body)
276
        return reply['network']
277
    
278
    def get_network_details(self, network_id):
279
        path = '/networks/%s' % network_id
280
        reply = self._get(path)
281
        return reply['network']
282
    
283
    def update_network_name(self, network_id, new_name):
284
        path = '/networks/%s' % network_id
285
        body = json.dumps({'network': {'name': new_name}})
286
        self._put(path, body)
287
    
288
    def delete_network(self, network_id):
289
        path = '/networks/%s' % network_id
290
        self._delete(path)
291

    
292
    def connect_server(self, server_id, network_id):
293
        path = '/networks/%s/action' % network_id
294
        body = json.dumps({'add': {'serverRef': server_id}})
295
        self._post(path, body)
296
    
297
    def disconnect_server(self, server_id, network_id):
298
        path = '/networks/%s/action' % network_id
299
        body = json.dumps({'remove': {'serverRef': server_id}})
300
        self._post(path, body)