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
38 class ComputeClient(ComputeRestClient):
39 """OpenStack Compute API 1.1 client"""
52 response_headers=dict(previous=None, next=None)):
54 :param detail: if true, append full server details to each item
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
60 :returns: list of server ids and names
64 changes_since=changes_since,
72 for k, v in response_headers.items():
73 response_headers[k] = r.headers.get(k, v)
74 return r.json['servers']
76 def get_server_details(
86 response_headers=dict(previous=None, next=None),
88 """Return detailed info for a server
90 :param server_id: integer (int or str)
92 :returns: dict with server details
96 changes_since=changes_since,
105 for k, v in response_headers.items():
106 response_headers[k] = r.headers.get(k, v)
107 return r.json['server']
110 self, name, flavor_id, image_id,
113 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 of the image of the virtual server
126 :param metadata: (dict) vm metadata
128 :param personality: a list of (file path, file contents) tuples,
129 describing files to be injected into virtual server upon creation
131 :param networks: (list of dicts) Networks to connect to, list this:
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'
139 :returns: a dict with the new virtual server details
141 :raises ClientError: wraps request errors
144 'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}}
147 req['server']['metadata'] = metadata
150 req['server']['personality'] = personality
152 if networks is not None:
153 req['server']['networks'] = networks or []
155 r = self.servers_post(
157 security_group=security_group,
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']
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)
168 :param server_id: integer (str or int)
170 :param new_name: (str)
172 :returns: (dict) response headers
174 req = {'server': {'name': new_name}}
175 r = self.servers_put(server_id, json_data=req)
178 def delete_server(self, server_id):
179 """Submit a deletion request for a server specified by id
181 :param server_id: integer (str or int)
183 :returns: (dict) response headers
185 r = self.servers_delete(server_id)
188 def change_admin_password(self, server_id, new_password):
190 :param server_id: (int)
192 :param new_password: (str)
194 req = {"changePassword": {"adminPass": new_password}}
195 r = self.servers_action_post(server_id, json_data=req)
198 def rebuild_server(self, server_id, response_headers=dict(location=None)):
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']
207 def reboot_server(self, server_id, hard=False):
209 :param server_id: integer (str or int)
211 :param hard: perform a hard reboot if true, soft reboot otherwise
213 req = {'reboot': {'type': 'HARD' if hard else 'SOFT'}}
214 r = self.servers_action_post(server_id, json_data=req)
217 def resize_server(self, server_id, flavor_id):
219 :param server_id: (str)
221 :param flavor_id: (int)
223 :returns: (dict) request headers
225 req = {'resize': {'flavorRef': flavor_id}}
226 r = self.servers_action_post(server_id, json_data=req)
229 def confirm_resize_server(self, server_id):
231 r = self.servers_action_post(
232 server_id, json_data=dict(confirmResize=None))
235 def revert_resize_server(self, server_id):
237 r = self.servers_action_post(
238 server_id, json_data=dict(revertResize=None))
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']
247 def start_server(self, server_id):
249 req = {'os-start': None}
250 r = self.servers_action_post(server_id, json_data=req, success=202)
253 def shutdown_server(self, server_id):
255 req = {'os-stop': None}
256 r = self.servers_action_post(server_id, json_data=req, success=202)
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']
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']
272 def update_server_metadata(
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']
281 def delete_server_metadata(self, server_id, key):
282 r = self.servers_metadata_delete(server_id, key)
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']
292 def get_flavor_details(self, flavor_id):
293 r = self.flavors_get(flavor_id)
294 return r.json['flavor']
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']
303 def get_image_details(self, image_id, **kwargs):
307 :raises ClientError: 404 if image not available
309 r = self.images_get(image_id, **kwargs)
311 return r.json['image']
313 raise ClientError('Image not available', 404, details=[
314 'Image %d not found or not accessible'])
316 def delete_image(self, image_id):
318 :param image_id: (str)
320 r = self.images_delete(image_id)
323 def get_image_metadata(self, image_id, key='', response_headers=dict(
324 previous=None, next=None)):
326 :param image_id: (str)
328 :param key: (str) the metadatum key
330 :returns (dict) metadata if key not set, specific metadatum otherwise
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']
337 # def create_image_metadata(self, image_id, key, val):
339 # :param image_id: integer (str or int)
341 # :param key: (str) metadatum key
343 # :param val: (str) metadatum value
345 # :returns: (dict) updated metadata
347 # req = {'meta': {key: val}}
348 # r = self.images_metadata_put(image_id, key, json_data=req)
349 # return r.json['meta']
351 def update_image_metadata(
353 response_headers=dict(previous=None, next=None), **metadata):
355 :param image_id: (str)
357 :param metadata: dict
359 :returns: updated metadata
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']
367 def delete_image_metadata(self, image_id, key):
369 :param image_id: (str)
371 :param key: (str) metadatum key
373 :returns: (dict) response headers
375 r = self.images_metadata_delete(image_id, key)
378 def get_floating_ip_pools(self, tenant_id):
380 :param tenant_id: (str)
382 :returns: (dict) {floating_ip_pools:[{name: ...}, ...]}
384 r = self.floating_ip_pools_get(tenant_id)
387 def get_floating_ips(self, tenant_id):
389 :param tenant_id: (str)
391 :returns: (dict) {floating_ips:[
392 {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
395 r = self.floating_ips_get(tenant_id)
398 def alloc_floating_ip(self, tenant_id, pool=None):
400 :param tenant_id: (str)
402 :param pool: (str) pool of ips to allocate from
404 :returns: (dict) {fixed_ip: . id: . instance_id: . ip: . pool: .}
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']
410 def get_floating_ip(self, tenant_id, fip_id=None):
412 :param tenant_id: (str)
414 :param fip_id: (str) floating ip id (if None, all ips are returned)
417 {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
420 r = self.floating_ips_get(tenant_id, fip_id)
421 return r.json['floating_ips']
423 def delete_floating_ip(self, tenant_id, fip_id=None):
425 :param tenant_id: (str)
427 :param fip_id: (str) floating ip id (if None, all ips are deleted)
429 :returns: (dict) request headers
431 r = self.floating_ips_delete(tenant_id, fip_id)