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.
34 from kamaki.clients import ClientError
35 from kamaki.clients.compute_rest_api import ComputeClientApi
36 from kamaki.clients.utils import path4url
40 class ComputeClient(ComputeClientApi):
41 """OpenStack Compute API 1.1 client"""
43 def list_servers(self, detail=False):
45 :param detail: if true, append full server details to each item
47 :returns: list of server ids and names
49 detail = 'detail' if detail else ''
50 r = self.servers_get(command=detail)
51 return r.json['servers']['values']
53 def get_server_details(self, server_id, **kwargs):
54 """Return detailed info for a server
56 :param server_id: integer (int or str)
58 :returns: dict with server details
60 r = self.servers_get(server_id, **kwargs)
61 return r.json['server']
63 def create_server(self, name, flavor_id, image_id, personality=None):
64 """Submit request to create a new server
68 :param flavor_id: integer id denoting a preset hardware configuration
70 :param image_id: (str) id denoting the OS image to run on the VM
72 :param personality: a list of (file path, file contents) tuples,
73 describing files to be injected into VM upon creation.
75 :returns: a dict with the new VMs details
77 :raises ClientError: wraps request errors
79 req = {'server': {'name': name,
80 'flavorRef': flavor_id,
81 'imageRef': image_id}}
83 image = self.get_image_details(image_id)
84 img_meta = image['metadata']['values']
86 for key in ('os', 'users'):
88 metadata[key] = img_meta[key]
92 req['server']['metadata'] = metadata
95 req['server']['personality'] = personality
98 r = self.servers_post(json_data=req)
99 except ClientError as err:
101 if isinstance(err.details, list):
102 tmp_err = err.details
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]
111 return r.json['server']
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)
117 :param server_id: integer (str or int)
119 :param new_name: (str)
121 req = {'server': {'name': new_name}}
122 r = self.servers_put(server_id, json_data=req)
125 def delete_server(self, server_id):
126 """Submit a deletion request for a server specified by id
128 :param server_id: integer (str or int)
130 r = self.servers_delete(server_id)
133 def reboot_server(self, server_id, hard=False):
135 :param server_id: integer (str or int)
137 :param hard: perform a hard reboot if true, soft reboot otherwise
139 type = 'HARD' if hard else 'SOFT'
140 req = {'reboot': {'type': type}}
141 r = self.servers_post(server_id, 'action', json_data=req)
144 def get_server_metadata(self, server_id, key=''):
146 :param server_id: integer (str or int)
148 :param key: (str) the metadatum key (all metadata if not given)
150 :returns: a key:val dict of requests metadata
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']
156 def create_server_metadata(self, server_id, key, val):
158 :param server_id: integer (str or int)
164 :returns: dict of updated key:val metadata
166 req = {'meta': {key: val}}
167 r = self.servers_put(
172 return r.json['meta']
174 def update_server_metadata(self, server_id, **metadata):
176 :param server_id: integer (str or int)
178 :param metadata: dict of key:val metadata
180 :returns: dict of updated key:val metadata
182 req = {'metadata': metadata}
183 r = self.servers_post(server_id, 'meta', json_data=req, success=201)
184 return r.json['metadata']
186 def delete_server_metadata(self, server_id, key):
188 :param server_id: integer (str or int)
190 :param key: (str) the meta key
192 r = self.servers_delete(server_id, 'meta/' + key)
195 def list_flavors(self, detail=False):
197 :param detail: (bool) detailed flavor info if set, short if not
199 :returns: (dict) flavor info
201 detail = 'detail' if detail else ''
202 r = self.flavors_get(command='detail')
203 return r.json['flavors']['values']
205 def get_flavor_details(self, flavor_id):
207 :param flavor_id: integer (str or int)
211 r = self.flavors_get(flavor_id)
212 return r.json['flavor']
214 def list_images(self, detail=False):
216 :param detail: (bool) detailed info if set, short if not
218 :returns: dict id,name + full info if detail
220 detail = 'detail' if detail else ''
221 r = self.images_get(command=detail)
222 return r.json['images']['values']
224 def get_image_details(self, image_id, **kwargs):
226 :param image_id: integer (str or int)
230 :raises ClientError: 404 if image not available
232 r = self.images_get(image_id, **kwargs)
234 return r.json['image']
236 raise ClientError('Image not available', 404, details=[
237 'Image %d not found or not accessible'])
239 def delete_image(self, image_id):
241 :param image_id: (str)
243 r = self.images_delete(image_id)
246 def get_image_metadata(self, image_id, key=''):
248 :param image_id: (str)
250 :param key: (str) the metadatum key
252 :returns (dict) metadata if key not set, specific metadatum otherwise
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']
258 def create_image_metadata(self, image_id, key, val):
260 :param image_id: integer (str or int)
262 :param key: (str) metadatum key
264 :param val: (str) metadatum value
266 :returns: (dict) updated metadata
268 req = {'meta': {key: val}}
269 r = self.images_put(image_id, 'meta/' + key, json_data=req)
270 return r.json['meta']
272 def update_image_metadata(self, image_id, **metadata):
274 :param image_id: (str)
276 :param metadata: dict
278 :returns: updated metadata
280 req = {'metadata': metadata}
281 r = self.images_post(image_id, 'meta', json_data=req)
282 return r.json['metadata']
284 def delete_image_metadata(self, image_id, key):
286 :param image_id: (str)
288 :param key: (str) metadatum key
290 command = path4url('meta', key)
291 r = self.images_delete(image_id, command)