Update documentation for server resize
[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(self, detail=False):
43         """
44         :param detail: if true, append full server details to each item
45
46         :returns: list of server ids and names
47         """
48         detail = 'detail' if detail else ''
49         r = self.servers_get(command=detail)
50         return r.json['servers']
51
52     def get_server_details(self, server_id, **kwargs):
53         """Return detailed info for a server
54
55         :param server_id: integer (int or str)
56
57         :returns: dict with server details
58         """
59         r = self.servers_get(server_id, **kwargs)
60         return r.json['server']
61
62     def create_server(self, name, flavor_id, image_id, personality=None):
63         """Submit request to create a new server
64
65         :param name: (str)
66
67         :param flavor_id: integer id denoting a preset hardware configuration
68
69         :param image_id: (str) id denoting the OS image to run on the VM
70
71         :param personality: a list of (file path, file contents) tuples,
72             describing files to be injected into VM upon creation.
73
74         :returns: a dict with the new VMs details
75
76         :raises ClientError: wraps request errors
77         """
78         req = {'server': {'name': name,
79                           'flavorRef': flavor_id,
80                           'imageRef': image_id}}
81
82         image = self.get_image_details(image_id)
83         metadata = {}
84         for key in ('os', 'users'):
85             try:
86                 metadata[key] = image['metadata'][key]
87             except KeyError:
88                 pass
89         if metadata:
90             req['server']['metadata'] = metadata
91
92         if personality:
93             req['server']['personality'] = personality
94
95         try:
96             r = self.servers_post(json_data=req)
97         except ClientError as err:
98             try:
99                 if isinstance(err.details, list):
100                     tmp_err = err.details
101                 else:
102                     errd = '%s' % err.details
103                     tmp_err = errd.split(',')
104                 tmp_err = tmp_err[0].split(':')
105                 tmp_err = tmp_err[2].split('"')
106                 err.message = tmp_err[1]
107             finally:
108                 raise err
109         return r.json['server']
110
111     def update_server_name(self, server_id, new_name):
112         """Update the name of the server as reported by the API (does not
113             modify the hostname used inside the VM)
114
115         :param server_id: integer (str or int)
116
117         :param new_name: (str)
118
119         :returns: (dict) response headers
120         """
121         req = {'server': {'name': new_name}}
122         r = self.servers_put(server_id, json_data=req)
123         return r.headers
124
125     def delete_server(self, server_id):
126         """Submit a deletion request for a server specified by id
127
128         :param server_id: integer (str or int)
129
130         :returns: (dict) response headers
131         """
132         r = self.servers_delete(server_id)
133         return r.headers
134
135     def reboot_server(self, server_id, hard=False):
136         """
137         :param server_id: integer (str or int)
138
139         :param hard: perform a hard reboot if true, soft reboot otherwise
140         """
141         boot_type = 'HARD' if hard else 'SOFT'
142         req = {'reboot': {'type': boot_type}}
143         r = self.servers_post(server_id, 'action', json_data=req)
144         return r.headers
145
146     def resize_server(self, server_id, flavor_id):
147         """
148         :param server_id: (str)
149
150         :param flavor_id: (int)
151
152         :returns: (dict) request headers
153         """
154         req = {'resize': {'flavorRef': flavor_id}}
155         r = self.servers_post(server_id, 'action', json_data=req)
156         return r.headers
157
158     def get_server_metadata(self, server_id, key=''):
159         """
160         :param server_id: integer (str or int)
161
162         :param key: (str) the metadatum key (all metadata if not given)
163
164         :returns: a key:val dict of requests metadata
165         """
166         command = path4url('metadata', key)
167         r = self.servers_get(server_id, command)
168         return r.json['meta' if key else 'metadata']
169
170     def create_server_metadata(self, server_id, key, val):
171         """
172         :param server_id: integer (str or int)
173
174         :param key: (str)
175
176         :param val: (str)
177
178         :returns: dict of updated key:val metadata
179         """
180         req = {'meta': {key: val}}
181         r = self.servers_put(
182             server_id, 'metadata/' + key, json_data=req, success=201)
183         return r.json['meta']
184
185     def update_server_metadata(self, server_id, **metadata):
186         """
187         :param server_id: integer (str or int)
188
189         :param metadata: dict of key:val metadata
190
191         :returns: dict of updated key:val metadata
192         """
193         req = {'metadata': metadata}
194         r = self.servers_post(
195             server_id, 'metadata', json_data=req, success=201)
196         return r.json['metadata']
197
198     def delete_server_metadata(self, server_id, key):
199         """
200         :param server_id: integer (str or int)
201
202         :param key: (str) the meta key
203
204         :returns: (dict) response headers
205         """
206         r = self.servers_delete(server_id, 'metadata/' + key)
207         return r.headers
208
209     def list_flavors(self, detail=False):
210         """
211         :param detail: (bool) detailed flavor info if set, short if not
212
213         :returns: (list) flavor info
214         """
215         r = self.flavors_get(command='detail' if detail else '')
216         return r.json['flavors']
217
218     def get_flavor_details(self, flavor_id):
219         """
220         :param flavor_id: integer (str or int)
221
222         :returns: dict
223         """
224         r = self.flavors_get(flavor_id)
225         return r.json['flavor']
226
227     def list_images(self, detail=False):
228         """
229         :param detail: (bool) detailed info if set, short if not
230
231         :returns: dict id,name + full info if detail
232         """
233         detail = 'detail' if detail else ''
234         r = self.images_get(command=detail)
235         return r.json['images']
236
237     def get_image_details(self, image_id, **kwargs):
238         """
239         :param image_id: integer (str or int)
240
241         :returns: dict
242
243         :raises ClientError: 404 if image not available
244         """
245         r = self.images_get(image_id, **kwargs)
246         try:
247             return r.json['image']
248         except KeyError:
249             raise ClientError('Image not available', 404, details=[
250                 'Image %d not found or not accessible'])
251
252     def delete_image(self, image_id):
253         """
254         :param image_id: (str)
255         """
256         r = self.images_delete(image_id)
257         return r.headers
258
259     def get_image_metadata(self, image_id, key=''):
260         """
261         :param image_id: (str)
262
263         :param key: (str) the metadatum key
264
265         :returns (dict) metadata if key not set, specific metadatum otherwise
266         """
267         command = path4url('metadata', key)
268         r = self.images_get(image_id, command)
269         return r.json['meta' if key else 'metadata']
270
271     def create_image_metadata(self, image_id, key, val):
272         """
273         :param image_id: integer (str or int)
274
275         :param key: (str) metadatum key
276
277         :param val: (str) metadatum value
278
279         :returns: (dict) updated metadata
280         """
281         req = {'meta': {key: val}}
282         r = self.images_put(image_id, 'metadata/' + key, json_data=req)
283         return r.json['meta']
284
285     def update_image_metadata(self, image_id, **metadata):
286         """
287         :param image_id: (str)
288
289         :param metadata: dict
290
291         :returns: updated metadata
292         """
293         req = {'metadata': metadata}
294         r = self.images_post(image_id, 'metadata', json_data=req)
295         return r.json['metadata']
296
297     def delete_image_metadata(self, image_id, key):
298         """
299         :param image_id: (str)
300
301         :param key: (str) metadatum key
302
303         :returns: (dict) response headers
304         """
305         command = path4url('metadata', key)
306         r = self.images_delete(image_id, command)
307         return r.headers
308
309     def get_floating_ip_pools(self, tenant_id):
310         """
311         :param tenant_id: (str)
312
313         :returns: (dict) {floating_ip_pools:[{name: ...}, ...]}
314         """
315         r = self.floating_ip_pools_get(tenant_id)
316         return r.json
317
318     def get_floating_ips(self, tenant_id):
319         """
320         :param tenant_id: (str)
321
322         :returns: (dict) {floating_ips:[
323             {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
324             ... ]}
325         """
326         r = self.floating_ips_get(tenant_id)
327         return r.json
328
329     def alloc_floating_ip(self, tenant_id, pool=None):
330         """
331         :param tenant_id: (str)
332
333         :param pool: (str) pool of ips to allocate from
334
335         :returns: (dict) {fixed_ip: . id: . instance_id: . ip: . pool: .}
336         """
337         json_data = dict(pool=pool) if pool else dict()
338         r = self.floating_ips_post(tenant_id, json_data)
339         return r.json['floating_ip']
340
341     def get_floating_ip(self, tenant_id, fip_id=None):
342         """
343         :param tenant_id: (str)
344
345         :param fip_id: (str) floating ip id (if None, all ips are returned)
346
347         :returns: (list) [
348             {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
349             ... ]
350         """
351         r = self.floating_ips_get(tenant_id, fip_id)
352         return r.json['floating_ips']
353
354     def delete_floating_ip(self, tenant_id, fip_id=None):
355         """
356         :param tenant_id: (str)
357
358         :param fip_id: (str) floating ip id (if None, all ips are deleted)
359
360         :returns: (dict) request headers
361         """
362         r = self.floating_ips_delete(tenant_id, fip_id)
363         return r.headers