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"""
53 response_headers=dict(previous=None, next=None)):
55 :param detail: if true, append full server details to each item
57 :param response_headers: (dict) use it to get previous/next responses
58 Keep the existing dict format to actually get the server responses
59 Use it with very long lists or with marker
61 :returns: list of server ids and names
65 changes_since=changes_since,
73 for k, v in response_headers.items():
74 response_headers[k] = r.headers.get(k, v)
75 return r.json['servers']
77 def get_server_details(
87 response_headers=dict(previous=None, next=None),
89 """Return detailed info for a server
91 :param server_id: integer (int or str)
93 :returns: dict with server details
97 changes_since=changes_since,
106 for k, v in response_headers.items():
107 response_headers[k] = r.headers.get(k, v)
108 return r.json['server']
111 self, name, flavor_id, image_id,
114 availability_zone=None,
117 response_headers=dict(location=None)):
118 """Submit request to create a new server
122 :param flavor_id: integer id denoting a preset hardware configuration
124 :param image_id: (str) id denoting the OS image to run on the VM
126 :param metadata: (dict) vm metadata
128 :param personality: a list of (file path, file contents) tuples,
129 describing files to be injected into VM upon creation.
131 :returns: a dict with the new VMs details
133 :raises ClientError: wraps request errors
136 'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}}
139 req['server']['metadata'] = metadata
142 req['server']['personality'] = personality
144 r = self.servers_post(
146 security_group=security_group,
148 availability_zone=availability_zone)
149 for k, v in response_headers.items():
150 response_headers[k] = r.headers.get(k, v)
151 return r.json['server']
153 def update_server_name(self, server_id, new_name):
154 """Update the name of the server as reported by the API (does not
155 modify the hostname used inside the VM)
157 :param server_id: integer (str or int)
159 :param new_name: (str)
161 :returns: (dict) response headers
163 req = {'server': {'name': new_name}}
164 r = self.servers_put(server_id, json_data=req)
167 def delete_server(self, server_id):
168 """Submit a deletion request for a server specified by id
170 :param server_id: integer (str or int)
172 :returns: (dict) response headers
174 r = self.servers_delete(server_id)
177 def change_admin_password(self, server_id, new_password):
179 :param server_id: (int)
181 :param new_password: (str)
183 req = {"changePassword": {"adminPass": new_password}}
184 r = self.servers_action_post(server_id, json_data=req)
187 def rebuild_server(self, server_id, response_headers=dict(location=None)):
189 server = self.get_server_details(server_id)
190 r = self.servers_action_post(
191 server_id, json_data=dict(rebuild=server['server']))
192 for k, v in response_headers.items():
193 response_headers[k] = r.headers.get(k, v)
194 return r.json['server']
196 def reboot_server(self, server_id, hard=False):
198 :param server_id: integer (str or int)
200 :param hard: perform a hard reboot if true, soft reboot otherwise
202 req = {'reboot': {'type': 'HARD' if hard else 'SOFT'}}
203 r = self.servers_action_post(server_id, json_data=req)
206 def resize_server(self, server_id, flavor_id):
208 :param server_id: (str)
210 :param flavor_id: (int)
212 :returns: (dict) request headers
214 req = {'resize': {'flavorRef': flavor_id}}
215 r = self.servers_action_post(server_id, json_data=req)
218 def confirm_resize_server(self, server_id):
220 r = self.servers_action_post(
221 server_id, json_data=dict(confirmResize=None))
224 def revert_resize_server(self, server_id):
226 r = self.servers_action_post(
227 server_id, json_data=dict(revertResize=None))
230 def create_server_image(self, server_id, image_name, **metadata):
231 """OpenStack method for taking snapshots"""
232 req = dict(createImage=dict(name=image_name, metadata=metadata))
233 r = self.servers_action_post(server_id, json_data=req)
234 return r.headers['location']
236 def start_server(self, server_id):
238 req = {'os-start': None}
239 r = self.servers_action_post(server_id, json_data=req, success=202)
242 def shutdown_server(self, server_id):
244 req = {'os-stop': None}
245 r = self.servers_action_post(server_id, json_data=req, success=202)
248 def get_server_metadata(self, server_id, key='', response_headers=dict(
249 previous=None, next=None)):
251 :param server_id: integer (str or int)
253 :param key: (str) the metadatum key (all metadata if not given)
255 :returns: a key:val dict of requests metadata
257 r = self.servers_metadata_get(server_id, key)
258 for k, v in response_headers.items():
259 response_headers[k] = r.headers.get(k, v)
260 return r.json['meta' if key else 'metadata']
262 def create_server_metadata(self, server_id, key, val):
264 :param server_id: integer (str or int)
270 :returns: dict of updated key:val metadata
272 req = {'meta': {key: val}}
273 r = self.servers_metadata_put(
274 server_id, key, json_data=req, success=201)
275 return r.json['meta']
277 def update_server_metadata(
279 response_headers=dict(previous=None, next=None), **metadata):
281 :param server_id: integer (str or int)
283 :param metadata: dict of key:val metadata
285 :returns: dict of updated key:val metadata
287 req = {'metadata': metadata}
288 r = self.servers_metadata_post(server_id, json_data=req, success=201)
289 for k, v in response_headers.items():
290 response_headers[k] = r.headers.get(k, v)
291 return r.json['metadata']
293 def delete_server_metadata(self, server_id, key):
295 :param server_id: integer (str or int)
297 :param key: (str) the meta key
299 :returns: (dict) response headers
301 r = self.servers_metadata_delete(server_id, key)
304 def list_flavors(self, detail=False, response_headers=dict(
305 previous=None, next=None)):
307 :param detail: (bool) detailed flavor info if set, short if not
309 :returns: (list) flavor info
311 r = self.flavors_get(detail=bool(detail))
312 for k, v in response_headers.items():
313 response_headers[k] = r.headers.get(k, v)
314 return r.json['flavors']
316 def get_flavor_details(self, flavor_id):
318 :param flavor_id: integer (str or int)
322 r = self.flavors_get(flavor_id)
323 return r.json['flavor']
325 def list_images(self, detail=False, response_headers=dict(
326 next=None, previous=None)):
328 :param detail: (bool) detailed info if set, short if not
330 :returns: dict id,name + full info if detail
332 r = self.images_get(detail=bool(detail))
333 for k, v in response_headers.items():
334 response_headers[k] = r.headers.get(k, v)
335 return r.json['images']
337 def get_image_details(self, image_id, **kwargs):
339 :param image_id: integer (str or int)
343 :raises ClientError: 404 if image not available
345 r = self.images_get(image_id, **kwargs)
347 return r.json['image']
349 raise ClientError('Image not available', 404, details=[
350 'Image %d not found or not accessible'])
352 def delete_image(self, image_id):
354 :param image_id: (str)
356 r = self.images_delete(image_id)
359 def get_image_metadata(self, image_id, key='', response_headers=dict(
360 previous=None, next=None)):
362 :param image_id: (str)
364 :param key: (str) the metadatum key
366 :returns (dict) metadata if key not set, specific metadatum otherwise
368 r = self.images_metadata_get(image_id, key)
369 for k, v in response_headers.items():
370 response_headers[k] = r.headers.get(k, v)
371 return r.json['meta' if key else 'metadata']
373 # def create_image_metadata(self, image_id, key, val):
375 # :param image_id: integer (str or int)
377 # :param key: (str) metadatum key
379 # :param val: (str) metadatum value
381 # :returns: (dict) updated metadata
383 # req = {'meta': {key: val}}
384 # r = self.images_metadata_put(image_id, key, json_data=req)
385 # return r.json['meta']
387 def update_image_metadata(
389 response_headers=dict(previous=None, next=None), **metadata):
391 :param image_id: (str)
393 :param metadata: dict
395 :returns: updated metadata
397 req = {'metadata': metadata}
398 r = self.images_metadata_post(image_id, json_data=req)
399 for k, v in response_headers.items():
400 response_headers[k] = r.headers.get(k, v)
401 return r.json['metadata']
403 def delete_image_metadata(self, image_id, key):
405 :param image_id: (str)
407 :param key: (str) metadatum key
409 :returns: (dict) response headers
411 r = self.images_metadata_delete(image_id, key)
414 def get_floating_ip_pools(self, tenant_id):
416 :param tenant_id: (str)
418 :returns: (dict) {floating_ip_pools:[{name: ...}, ...]}
420 r = self.floating_ip_pools_get(tenant_id)
423 def get_floating_ips(self, tenant_id):
425 :param tenant_id: (str)
427 :returns: (dict) {floating_ips:[
428 {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
431 r = self.floating_ips_get(tenant_id)
434 def alloc_floating_ip(self, tenant_id, pool=None):
436 :param tenant_id: (str)
438 :param pool: (str) pool of ips to allocate from
440 :returns: (dict) {fixed_ip: . id: . instance_id: . ip: . pool: .}
442 json_data = dict(pool=pool) if pool else dict()
443 r = self.floating_ips_post(tenant_id, json_data)
444 return r.json['floating_ip']
446 def get_floating_ip(self, tenant_id, fip_id=None):
448 :param tenant_id: (str)
450 :param fip_id: (str) floating ip id (if None, all ips are returned)
453 {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
456 r = self.floating_ips_get(tenant_id, fip_id)
457 return r.json['floating_ips']
459 def delete_floating_ip(self, tenant_id, fip_id=None):
461 :param tenant_id: (str)
463 :param fip_id: (str) floating ip id (if None, all ips are deleted)
465 :returns: (dict) request headers
467 r = self.floating_ips_delete(tenant_id, fip_id)