Allow non-ascii paramters on path2url
[kamaki] / kamaki / clients / compute.py
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 from kamaki.clients import ClientError
35 from kamaki.clients.compute_rest_api import ComputeClientApi
36 from kamaki.clients.utils import path4url
37 import json
38
39
40 class ComputeClient(ComputeClientApi):
41     """OpenStack Compute API 1.1 client"""
42
43     def list_servers(self, detail=False):
44         """
45         :param detail: if true, append full server details to each item
46
47         :returns: list of server ids and names
48         """
49         detail = 'detail' if detail else ''
50         r = self.servers_get(command=detail)
51         return r.json['servers']['values']
52
53     def get_server_details(self, server_id, **kwargs):
54         """Return detailed info for a server
55
56         :param server_id: integer (int or str)
57
58         :returns: dict with server details
59         """
60         r = self.servers_get(server_id, **kwargs)
61         return r.json['server']
62
63     def create_server(self, name, flavor_id, image_id, personality=None):
64         """Submit request to create a new server
65
66         :param name: (str)
67
68         :param flavor_id: integer id denoting a preset hardware configuration
69
70         :param image_id: (str) id denoting the OS image to run on the VM
71
72         :param personality: a list of (file path, file contents) tuples,
73             describing files to be injected into VM upon creation.
74
75         :returns: a dict with the new VMs details
76
77         :raises ClientError: wraps request errors
78         """
79         req = {'server': {'name': name,
80                           'flavorRef': flavor_id,
81                           'imageRef': image_id}}
82
83         image = self.get_image_details(image_id)
84         img_meta = image['metadata']['values']
85         metadata = {}
86         for key in ('os', 'users'):
87             try:
88                 metadata[key] = img_meta[key]
89             except KeyError:
90                 pass
91         if metadata:
92             req['server']['metadata'] = metadata
93
94         if personality:
95             req['server']['personality'] = personality
96
97         try:
98             r = self.servers_post(json_data=req)
99         except ClientError as err:
100             try:
101                 if isinstance(err.details, list):
102                     tmp_err = err.details
103                 else:
104                     errd = '%s' % err.details
105                     tmp_err = errd.split(',')
106                 tmp_err = tmp_err[0].split(':')
107                 tmp_err = tmp_err[2].split('"')
108                 err.message = tmp_err[1]
109             finally:
110                 raise err
111         return r.json['server']
112
113     def update_server_name(self, server_id, new_name):
114         """Update the name of the server as reported by the API (does not
115             modify the hostname used inside the VM)
116
117         :param server_id: integer (str or int)
118
119         :param new_name: (str)
120         """
121         req = {'server': {'name': new_name}}
122         r = self.servers_put(server_id, json_data=req)
123         r.release()
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         r = self.servers_delete(server_id)
131         r.release()
132
133     def reboot_server(self, server_id, hard=False):
134         """
135         :param server_id: integer (str or int)
136
137         :param hard: perform a hard reboot if true, soft reboot otherwise
138         """
139         type = 'HARD' if hard else 'SOFT'
140         req = {'reboot': {'type': type}}
141         r = self.servers_post(server_id, 'action', json_data=req)
142         r.release()
143
144     def get_server_metadata(self, server_id, key=''):
145         """
146         :param server_id: integer (str or int)
147
148         :param key: (str) the metadatum key (all metadata if not given)
149
150         :returns: a key:val dict of requests metadata
151         """
152         command = path4url('meta', key)
153         r = self.servers_get(server_id, command)
154         return r.json['meta'] if key != '' else r.json['metadata']['values']
155
156     def create_server_metadata(self, server_id, key, val):
157         """
158         :param server_id: integer (str or int)
159
160         :param key: (str)
161
162         :param val: (str)
163
164         :returns: dict of updated key:val metadata
165         """
166         req = {'meta': {key: val}}
167         r = self.servers_put(
168             server_id,
169             'meta/' + key,
170             json_data=req,
171             success=201)
172         return r.json['meta']
173
174     def update_server_metadata(self, server_id, **metadata):
175         """
176         :param server_id: integer (str or int)
177
178         :param metadata: dict of key:val metadata
179
180         :returns: dict of updated key:val metadata
181         """
182         req = {'metadata': metadata}
183         r = self.servers_post(server_id, 'meta', json_data=req, success=201)
184         return r.json['metadata']
185
186     def delete_server_metadata(self, server_id, key):
187         """
188         :param server_id: integer (str or int)
189
190         :param key: (str) the meta key
191         """
192         r = self.servers_delete(server_id, 'meta/' + key)
193         r.release()
194
195     def list_flavors(self, detail=False):
196         """
197         :param detail: (bool) detailed flavor info if set, short if not
198
199         :returns: (dict) flavor info
200         """
201         detail = 'detail' if detail else ''
202         r = self.flavors_get(command='detail')
203         return r.json['flavors']['values']
204
205     def get_flavor_details(self, flavor_id):
206         """
207         :param flavor_id: integer (str or int)
208
209         :returns: dict
210         """
211         r = self.flavors_get(flavor_id)
212         return r.json['flavor']
213
214     def list_images(self, detail=False):
215         """
216         :param detail: (bool) detailed info if set, short if not
217
218         :returns: dict id,name + full info if detail
219         """
220         detail = 'detail' if detail else ''
221         r = self.images_get(command=detail)
222         return r.json['images']['values']
223
224     def get_image_details(self, image_id, **kwargs):
225         """
226         :param image_id: integer (str or int)
227
228         :returns: dict
229
230         :raises ClientError: 404 if image not available
231         """
232         r = self.images_get(image_id, **kwargs)
233         try:
234             return r.json['image']
235         except KeyError:
236             raise ClientError('Image not available', 404, details=[
237                 'Image %d not found or not accessible'])
238
239     def delete_image(self, image_id):
240         """
241         :param image_id: (str)
242         """
243         r = self.images_delete(image_id)
244         r.release()
245
246     def get_image_metadata(self, image_id, key=''):
247         """
248         :param image_id: (str)
249
250         :param key: (str) the metadatum key
251
252         :returns (dict) metadata if key not set, specific metadatum otherwise
253         """
254         command = path4url('meta', key)
255         r = self.images_get(image_id, command)
256         return r.json['meta'] if key else r.json['metadata']['values']
257
258     def create_image_metadata(self, image_id, key, val):
259         """
260         :param image_id: integer (str or int)
261
262         :param key: (str) metadatum key
263
264         :param val: (str) metadatum value
265
266         :returns: (dict) updated metadata
267         """
268         req = {'meta': {key: val}}
269         r = self.images_put(image_id, 'meta/' + key, json_data=req)
270         return r.json['meta']
271
272     def update_image_metadata(self, image_id, **metadata):
273         """
274         :param image_id: (str)
275
276         :param metadata: dict
277
278         :returns: updated metadata
279         """
280         req = {'metadata': metadata}
281         r = self.images_post(image_id, 'meta', json_data=req)
282         return r.json['metadata']
283
284     def delete_image_metadata(self, image_id, key):
285         """
286         :param image_id: (str)
287
288         :param key: (str) metadatum key
289         """
290         command = path4url('meta', key)
291         r = self.images_delete(image_id, command)
292         r.release()