1 # Copyright 2011-2013 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 ComputeRestClient
36 from kamaki.clients.utils import path4url
39 class ComputeClient(ComputeRestClient):
40 """OpenStack Compute API 1.1 client"""
42 def list_servers(self, detail=False):
44 :param detail: if true, append full server details to each item
46 :returns: list of server ids and names
48 detail = 'detail' if detail else ''
49 r = self.servers_get(command=detail)
50 return r.json['servers']
52 def get_server_details(self, server_id, **kwargs):
53 """Return detailed info for a server
55 :param server_id: integer (int or str)
57 :returns: dict with server details
59 r = self.servers_get(server_id, **kwargs)
60 return r.json['server']
62 def create_server(self, name, flavor_id, image_id, personality=None):
63 """Submit request to create a new server
67 :param flavor_id: integer id denoting a preset hardware configuration
69 :param image_id: (str) id denoting the OS image to run on the VM
71 :param personality: a list of (file path, file contents) tuples,
72 describing files to be injected into VM upon creation.
74 :returns: a dict with the new VMs details
76 :raises ClientError: wraps request errors
78 req = {'server': {'name': name,
79 'flavorRef': flavor_id,
80 'imageRef': image_id}}
82 image = self.get_image_details(image_id)
84 for key in ('os', 'users'):
86 metadata[key] = image['metadata'][key]
90 req['server']['metadata'] = metadata
93 req['server']['personality'] = personality
96 r = self.servers_post(json_data=req)
97 except ClientError as err:
99 if isinstance(err.details, list):
100 tmp_err = err.details
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]
109 return r.json['server']
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)
115 :param server_id: integer (str or int)
117 :param new_name: (str)
119 :returns: (dict) response headers
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 :returns: (dict) response headers
132 r = self.servers_delete(server_id)
135 def reboot_server(self, server_id, hard=False):
137 :param server_id: integer (str or int)
139 :param hard: perform a hard reboot if true, soft reboot otherwise
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)
146 def resize_server(self, server_id, flavor_id):
148 :param server_id: (str)
150 :param flavor_id: (int)
152 :returns: (dict) request headers
154 req = {'resize': {'flavorRef': flavor_id}}
155 r = self.servers_post(server_id, 'action', json_data=req)
158 def get_server_metadata(self, server_id, key=''):
160 :param server_id: integer (str or int)
162 :param key: (str) the metadatum key (all metadata if not given)
164 :returns: a key:val dict of requests metadata
166 command = path4url('metadata', key)
167 r = self.servers_get(server_id, command)
168 return r.json['meta' if key else 'metadata']
170 def create_server_metadata(self, server_id, key, val):
172 :param server_id: integer (str or int)
178 :returns: dict of updated key:val metadata
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']
185 def update_server_metadata(self, server_id, **metadata):
187 :param server_id: integer (str or int)
189 :param metadata: dict of key:val metadata
191 :returns: dict of updated key:val metadata
193 req = {'metadata': metadata}
194 r = self.servers_post(
195 server_id, 'metadata', json_data=req, success=201)
196 return r.json['metadata']
198 def delete_server_metadata(self, server_id, key):
200 :param server_id: integer (str or int)
202 :param key: (str) the meta key
204 :returns: (dict) response headers
206 r = self.servers_delete(server_id, 'metadata/' + key)
209 def list_flavors(self, detail=False):
211 :param detail: (bool) detailed flavor info if set, short if not
213 :returns: (list) flavor info
215 r = self.flavors_get(command='detail' if detail else '')
216 return r.json['flavors']
218 def get_flavor_details(self, flavor_id):
220 :param flavor_id: integer (str or int)
224 r = self.flavors_get(flavor_id)
225 return r.json['flavor']
227 def list_images(self, detail=False):
229 :param detail: (bool) detailed info if set, short if not
231 :returns: dict id,name + full info if detail
233 detail = 'detail' if detail else ''
234 r = self.images_get(command=detail)
235 return r.json['images']
237 def get_image_details(self, image_id, **kwargs):
239 :param image_id: integer (str or int)
243 :raises ClientError: 404 if image not available
245 r = self.images_get(image_id, **kwargs)
247 return r.json['image']
249 raise ClientError('Image not available', 404, details=[
250 'Image %d not found or not accessible'])
252 def delete_image(self, image_id):
254 :param image_id: (str)
256 r = self.images_delete(image_id)
259 def get_image_metadata(self, image_id, key=''):
261 :param image_id: (str)
263 :param key: (str) the metadatum key
265 :returns (dict) metadata if key not set, specific metadatum otherwise
267 command = path4url('metadata', key)
268 r = self.images_get(image_id, command)
269 return r.json['meta' if key else 'metadata']
271 def create_image_metadata(self, image_id, key, val):
273 :param image_id: integer (str or int)
275 :param key: (str) metadatum key
277 :param val: (str) metadatum value
279 :returns: (dict) updated metadata
281 req = {'meta': {key: val}}
282 r = self.images_put(image_id, 'metadata/' + key, json_data=req)
283 return r.json['meta']
285 def update_image_metadata(self, image_id, **metadata):
287 :param image_id: (str)
289 :param metadata: dict
291 :returns: updated metadata
293 req = {'metadata': metadata}
294 r = self.images_post(image_id, 'metadata', json_data=req)
295 return r.json['metadata']
297 def delete_image_metadata(self, image_id, key):
299 :param image_id: (str)
301 :param key: (str) metadatum key
303 :returns: (dict) response headers
305 command = path4url('metadata', key)
306 r = self.images_delete(image_id, command)
309 def get_floating_ip_pools(self, tenant_id):
311 :param tenant_id: (str)
313 :returns: (dict) {floating_ip_pools:[{name: ...}, ...]}
315 r = self.floating_ip_pools_get(tenant_id)
318 def get_floating_ips(self, tenant_id):
320 :param tenant_id: (str)
322 :returns: (dict) {floating_ips:[
323 {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
326 r = self.floating_ips_get(tenant_id)
329 def alloc_floating_ip(self, tenant_id, pool=None):
331 :param tenant_id: (str)
333 :param pool: (str) pool of ips to allocate from
335 :returns: (dict) {fixed_ip: . id: . instance_id: . ip: . pool: .}
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']
341 def get_floating_ip(self, tenant_id, fip_id=None):
343 :param tenant_id: (str)
345 :param fip_id: (str) floating ip id (if None, all ips are returned)
348 {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
351 r = self.floating_ips_get(tenant_id, fip_id)
352 return r.json['floating_ips']
354 def delete_floating_ip(self, tenant_id, fip_id=None):
356 :param tenant_id: (str)
358 :param fip_id: (str) floating ip id (if None, all ips are deleted)
360 :returns: (dict) request headers
362 r = self.floating_ips_delete(tenant_id, fip_id)