34e2f3439c50252e4699cc72942eaceb7e32b6c3
[kamaki] / kamaki / clients / compute / __init__.py
1 # Copyright 2011-2013 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 from kamaki.clients import ClientError
35 from kamaki.clients.compute.rest_api import ComputeRestClient
36 from kamaki.clients.utils import path4url
37
38
39 class ComputeClient(ComputeRestClient):
40     """OpenStack Compute API 1.1 client"""
41
42     def list_servers(
43             self,
44             detail=False,
45             changes_since=None,
46             image=None,
47             flavor=None,
48             name=None,
49             marker=None,
50             limit=None,
51             status=None,
52             host=None,
53             response_headers=dict(previous=None, next=None)):
54         """
55         :param detail: if true, append full server details to each item
56
57         :param response_headers: (dict) use it to get previous/next responses
58             Keep the existing dict format to actually get the server responses
59             Use it with very long lists or with marker
60
61         :returns: list of server ids and names
62         """
63         r = self.servers_get(
64             detail=bool(detail),
65             changes_since=changes_since,
66             image=image,
67             flavor=flavor,
68             name=name,
69             marker=marker,
70             limit=limit,
71             status=status,
72             host=host)
73         for k, v in response_headers.items():
74             response_headers[k] = r.headers.get(k, v)
75         return r.json['servers']
76
77     def get_server_details(
78             self, server_id,
79             changes_since=None,
80             image=None,
81             flavor=None,
82             name=None,
83             marker=None,
84             limit=None,
85             status=None,
86             host=None,
87             response_headers=dict(previous=None, next=None),
88             **kwargs):
89         """Return detailed info for a server
90
91         :param server_id: integer (int or str)
92
93         :returns: dict with server details
94         """
95         r = self.servers_get(
96             server_id,
97             changes_since=changes_since,
98             image=image,
99             flavor=flavor,
100             name=name,
101             marker=marker,
102             limit=limit,
103             status=status,
104             host=host,
105             **kwargs)
106         for k, v in response_headers.items():
107             response_headers[k] = r.headers.get(k, v)
108         return r.json['server']
109
110     def create_server(
111             self, name, flavor_id, image_id,
112             security_group=None,
113             user_data=None,
114             availability_zone=None,
115             metadata=None,
116             personality=None,
117             response_headers=dict(location=None)):
118         """Submit request to create a new server
119
120         :param name: (str)
121
122         :param flavor_id: integer id denoting a preset hardware configuration
123
124         :param image_id: (str) id denoting the OS image to run on the VM
125
126         :param metadata: (dict) vm metadata
127
128         :param personality: a list of (file path, file contents) tuples,
129             describing files to be injected into VM upon creation.
130
131         :returns: a dict with the new VMs details
132
133         :raises ClientError: wraps request errors
134         """
135         req = {'server': {
136             'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}}
137
138         if metadata:
139             req['server']['metadata'] = metadata
140
141         if personality:
142             req['server']['personality'] = personality
143
144         r = self.servers_post(
145             json_data=req,
146             security_group=security_group,
147             user_data=user_data,
148             availability_zone=availability_zone)
149         for k, v in response_headers.items():
150             response_headers[k] = r.headers.get(k, v)
151         return r.json['server']
152
153     def update_server_name(self, server_id, new_name):
154         """Update the name of the server as reported by the API (does not
155             modify the hostname used inside the VM)
156
157         :param server_id: integer (str or int)
158
159         :param new_name: (str)
160
161         :returns: (dict) response headers
162         """
163         req = {'server': {'name': new_name}}
164         r = self.servers_put(server_id, json_data=req)
165         return r.headers
166
167     def delete_server(self, server_id):
168         """Submit a deletion request for a server specified by id
169
170         :param server_id: integer (str or int)
171
172         :returns: (dict) response headers
173         """
174         r = self.servers_delete(server_id)
175         return r.headers
176
177     def change_admin_password(self, server_id, new_password):
178         """
179         :param server_id: (int)
180
181         :param new_password: (str)
182         """
183         req = {"changePassword": {"adminPass": new_password}}
184         r = self.servers_action_post(server_id, json_data=req)
185         return r.headers
186
187     def rebuild_server(self, server_id, response_headers=dict(location=None)):
188         """OS"""
189         server = self.get_server_details(server_id)
190         r = self.servers_action_post(
191             server_id, json_data=dict(rebuild=server['server']))
192         for k, v in response_headers.items():
193             response_headers[k] = r.headers.get(k, v)
194         return r.json['server']
195
196     def reboot_server(self, server_id, hard=False):
197         """
198         :param server_id: integer (str or int)
199
200         :param hard: perform a hard reboot if true, soft reboot otherwise
201         """
202         req = {'reboot': {'type': 'HARD' if hard else 'SOFT'}}
203         r = self.servers_action_post(server_id, json_data=req)
204         return r.headers
205
206     def resize_server(self, server_id, flavor_id):
207         """
208         :param server_id: (str)
209
210         :param flavor_id: (int)
211
212         :returns: (dict) request headers
213         """
214         req = {'resize': {'flavorRef': flavor_id}}
215         r = self.servers_action_post(server_id, json_data=req)
216         return r.headers
217
218     def confirm_resize_server(self, server_id):
219         """OS"""
220         r = self.servers_action_post(
221             server_id, json_data=dict(confirmResize=None))
222         return r.headers
223
224     def revert_resize_server(self, server_id):
225         """OS"""
226         r = self.servers_action_post(
227             server_id, json_data=dict(revertResize=None))
228         return r.headers
229
230     def create_server_image(self, server_id, image_name, **metadata):
231         """OpenStack method for taking snapshots"""
232         req = dict(createImage=dict(name=image_name, metadata=metadata))
233         r = self.servers_action_post(server_id, json_data=req)
234         return r.headers['location']
235
236     def start_server(self, server_id):
237         """OS Extentions"""
238         req = {'os-start': None}
239         r = self.servers_action_post(server_id, json_data=req, success=202)
240         return r.headers
241
242     def shutdown_server(self, server_id):
243         """OS Extentions"""
244         req = {'os-stop': None}
245         r = self.servers_action_post(server_id, json_data=req, success=202)
246         return r.headers
247
248     def get_server_metadata(self, server_id, key='', response_headers=dict(
249             previous=None, next=None)):
250         """
251         :param server_id: integer (str or int)
252
253         :param key: (str) the metadatum key (all metadata if not given)
254
255         :returns: a key:val dict of requests metadata
256         """
257         r = self.servers_metadata_get(server_id, key)
258         for k, v in response_headers.items():
259             response_headers[k] = r.headers.get(k, v)
260         return r.json['meta' if key else 'metadata']
261
262     def create_server_metadata(self, server_id, key, val):
263         """
264         :param server_id: integer (str or int)
265
266         :param key: (str)
267
268         :param val: (str)
269
270         :returns: dict of updated key:val metadata
271         """
272         req = {'meta': {key: val}}
273         r = self.servers_metadata_put(
274             server_id, key, json_data=req, success=201)
275         return r.json['meta']
276
277     def update_server_metadata(
278             self, server_id,
279             response_headers=dict(previous=None, next=None), **metadata):
280         """
281         :param server_id: integer (str or int)
282
283         :param metadata: dict of key:val metadata
284
285         :returns: dict of updated key:val metadata
286         """
287         req = {'metadata': metadata}
288         r = self.servers_metadata_post(server_id, json_data=req, success=201)
289         for k, v in response_headers.items():
290             response_headers[k] = r.headers.get(k, v)
291         return r.json['metadata']
292
293     def delete_server_metadata(self, server_id, key):
294         """
295         :param server_id: integer (str or int)
296
297         :param key: (str) the meta key
298
299         :returns: (dict) response headers
300         """
301         r = self.servers_metadata_delete(server_id, key)
302         return r.headers
303
304     def list_flavors(self, detail=False, response_headers=dict(
305             previous=None, next=None)):
306         """
307         :param detail: (bool) detailed flavor info if set, short if not
308
309         :returns: (list) flavor info
310         """
311         r = self.flavors_get(detail=bool(detail))
312         for k, v in response_headers.items():
313             response_headers[k] = r.headers.get(k, v)
314         return r.json['flavors']
315
316     def get_flavor_details(self, flavor_id):
317         """
318         :param flavor_id: integer (str or int)
319
320         :returns: dict
321         """
322         r = self.flavors_get(flavor_id)
323         return r.json['flavor']
324
325     def list_images(self, detail=False, response_headers=dict(
326             next=None, previous=None)):
327         """
328         :param detail: (bool) detailed info if set, short if not
329
330         :returns: dict id,name + full info if detail
331         """
332         r = self.images_get(detail=bool(detail))
333         for k, v in response_headers.items():
334             response_headers[k] = r.headers.get(k, v)
335         return r.json['images']
336
337     def get_image_details(self, image_id, **kwargs):
338         """
339         :param image_id: integer (str or int)
340
341         :returns: dict
342
343         :raises ClientError: 404 if image not available
344         """
345         r = self.images_get(image_id, **kwargs)
346         try:
347             return r.json['image']
348         except KeyError:
349             raise ClientError('Image not available', 404, details=[
350                 'Image %d not found or not accessible'])
351
352     def delete_image(self, image_id):
353         """
354         :param image_id: (str)
355         """
356         r = self.images_delete(image_id)
357         return r.headers
358
359     def get_image_metadata(self, image_id, key='', response_headers=dict(
360             previous=None, next=None)):
361         """
362         :param image_id: (str)
363
364         :param key: (str) the metadatum key
365
366         :returns (dict) metadata if key not set, specific metadatum otherwise
367         """
368         r = self.images_metadata_get(image_id, key)
369         for k, v in response_headers.items():
370             response_headers[k] = r.headers.get(k, v)
371         return r.json['meta' if key else 'metadata']
372
373     # def create_image_metadata(self, image_id, key, val):
374     #     """
375     #     :param image_id: integer (str or int)
376
377     #     :param key: (str) metadatum key
378
379     #     :param val: (str) metadatum value
380
381     #     :returns: (dict) updated metadata
382     #     """
383     #     req = {'meta': {key: val}}
384     #     r = self.images_metadata_put(image_id, key, json_data=req)
385     #     return r.json['meta']
386
387     def update_image_metadata(
388             self, image_id,
389             response_headers=dict(previous=None, next=None), **metadata):
390         """
391         :param image_id: (str)
392
393         :param metadata: dict
394
395         :returns: updated metadata
396         """
397         req = {'metadata': metadata}
398         r = self.images_metadata_post(image_id, json_data=req)
399         for k, v in response_headers.items():
400             response_headers[k] = r.headers.get(k, v)
401         return r.json['metadata']
402
403     def delete_image_metadata(self, image_id, key):
404         """
405         :param image_id: (str)
406
407         :param key: (str) metadatum key
408
409         :returns: (dict) response headers
410         """
411         r = self.images_metadata_delete(image_id, key)
412         return r.headers
413
414     def get_floating_ip_pools(self, tenant_id):
415         """
416         :param tenant_id: (str)
417
418         :returns: (dict) {floating_ip_pools:[{name: ...}, ...]}
419         """
420         r = self.floating_ip_pools_get(tenant_id)
421         return r.json
422
423     def get_floating_ips(self, tenant_id):
424         """
425         :param tenant_id: (str)
426
427         :returns: (dict) {floating_ips:[
428             {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
429             ... ]}
430         """
431         r = self.floating_ips_get(tenant_id)
432         return r.json
433
434     def alloc_floating_ip(self, tenant_id, pool=None):
435         """
436         :param tenant_id: (str)
437
438         :param pool: (str) pool of ips to allocate from
439
440         :returns: (dict) {fixed_ip: . id: . instance_id: . ip: . pool: .}
441         """
442         json_data = dict(pool=pool) if pool else dict()
443         r = self.floating_ips_post(tenant_id, json_data)
444         return r.json['floating_ip']
445
446     def get_floating_ip(self, tenant_id, fip_id=None):
447         """
448         :param tenant_id: (str)
449
450         :param fip_id: (str) floating ip id (if None, all ips are returned)
451
452         :returns: (list) [
453             {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
454             ... ]
455         """
456         r = self.floating_ips_get(tenant_id, fip_id)
457         return r.json['floating_ips']
458
459     def delete_floating_ip(self, tenant_id, fip_id=None):
460         """
461         :param tenant_id: (str)
462
463         :param fip_id: (str) floating ip id (if None, all ips are deleted)
464
465         :returns: (dict) request headers
466         """
467         r = self.floating_ips_delete(tenant_id, fip_id)
468         return r.headers