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