1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
37 from base64 import b64encode
38 from httplib import HTTPConnection, HTTPSConnection
39 from urlparse import urlparse
42 log = logging.getLogger('kamaki.client')
45 class ClientError(Exception):
46 def __init__(self, message, status=0, details=''):
47 self.message = message
49 self.details = details
52 return int(self.status)
57 r += "\nHTTP Status: %d" % self.status
59 r += "\nDetails: \n%s" % self.details
64 def __init__(self, url, token=''):
68 def _cmd(self, method, path, body=None, success=200):
69 p = urlparse(self.url)
71 if p.scheme == 'http':
72 conn = HTTPConnection(p.netloc)
73 elif p.scheme == 'https':
74 conn = HTTPSConnection(p.netloc)
76 raise ClientError('Unknown URL scheme')
78 headers = {'X-Auth-Token': self.token}
80 headers['Content-Type'] = 'application/json'
81 headers['Content-Length'] = len(body)
83 log.debug('%s', '>' * 40)
84 log.debug('%s %s', method, path)
86 for key, val in headers.items():
87 log.debug('%s: %s', key, val)
93 conn.request(method, path, body, headers)
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)
104 reply = json.loads(buf) if buf else {}
106 raise ClientError('Did not receive valid JSON reply',
109 if resp.status != success:
111 key = reply.keys()[0]
113 message = '%s: %s' % (key, val.get('message', ''))
114 details = val.get('details', '')
115 raise ClientError(message, resp.status, details)
117 raise ClientError('Invalid response from the server')
121 def _get(self, path, success=200):
122 return self._cmd('GET', path, None, success)
124 def _post(self, path, body, success=202):
125 return self._cmd('POST', path, body, success)
127 def _put(self, path, body, success=204):
128 return self._cmd('PUT', path, body, success)
130 def _delete(self, path, success=204):
131 return self._cmd('DELETE', path, None, success)
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']
141 def get_server_details(self, server_id):
142 path = '/servers/%d' % server_id
143 reply = self._get(path)
144 return reply['server']
146 def create_server(self, name, flavor, image, personality=None):
147 """personality is a list of (path, data) tuples"""
149 req = {'name': name, 'flavorRef': flavor, 'imageRef': image}
152 for path, data in personality:
153 contents = b64encode(data)
154 p.append({'path': path, 'contents': contents})
155 req['personality'] = p
157 body = json.dumps({'server': req})
158 reply = self._post('/servers', body)
159 return reply['server']
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)
166 def delete_server(self, server_id):
167 path = '/servers/%d' % server_id
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)
176 def start_server(self, server_id):
177 path = '/servers/%d/action' % server_id
178 body = json.dumps({'start': {}})
179 self._post(path, body)
181 def shutdown_server(self, server_id):
182 path = '/servers/%d/action' % server_id
183 body = json.dumps({'shutdown': {}})
184 self._post(path, body)
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']
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)
197 def list_server_addresses(self, server_id, network=None):
198 path = '/servers/%d/ips' % server_id
200 path += '/%s' % network
201 reply = self._get(path)
202 return [reply['network']] if network else reply['addresses']['values']
204 def get_server_metadata(self, server_id, key=None):
205 path = '/servers/%d/meta' % server_id
208 reply = self._get(path)
209 return reply['meta'] if key else reply['metadata']['values']
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)
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']
223 def delete_server_metadata(self, server_id, key):
224 path = '/servers/%d/meta/%s' % (server_id, key)
225 reply = self._delete(path)
227 def get_server_stats(self, server_id):
228 path = '/servers/%d/stats' % server_id
229 reply = self._get(path)
230 return reply['stats']
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']
240 def get_flavor_details(self, flavor_id):
241 path = '/flavors/%d' % flavor_id
242 reply = self._get(path)
243 return reply['flavor']
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']
253 def get_image_details(self, image_id):
254 path = '/images/%d' % image_id
255 reply = self._get(path)
256 return reply['image']
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']
264 def delete_image(self, image_id):
265 path = '/images/%d' % image_id
268 def get_image_metadata(self, image_id, key=None):
269 path = '/images/%d/meta' % image_id
272 reply = self._get(path)
273 return reply['meta'] if key else reply['metadata']['values']
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)
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']
287 def delete_image_metadata(self, image_id, key):
288 path = '/images/%d/meta/%s' % (image_id, key)
289 reply = self._delete(path)
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']
299 def create_network(self, name):
300 body = json.dumps({'network': {'name': name}})
301 reply = self._post('/networks', body)
302 return reply['network']
304 def get_network_details(self, network_id):
305 path = '/networks/%s' % network_id
306 reply = self._get(path)
307 return reply['network']
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)
314 def delete_network(self, network_id):
315 path = '/networks/%s' % network_id
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)
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)