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.
36 from . import ClientError
37 from .http import HTTPClient
40 class ComputeClient(HTTPClient):
41 """OpenStack Compute API 1.1 client"""
45 url = self.config.get('compute_url') or self.config.get('url')
47 raise ClientError('No URL was given')
52 token = self.config.get('compute_token') or self.config.get('token')
54 raise ClientError('No token was given')
57 def list_servers(self, detail=False):
58 """List servers, returned detailed output if detailed is True"""
59 path = '/servers/detail' if detail else '/servers'
60 reply = self.http_get(path)
61 return reply['servers']['values']
63 def get_server_details(self, server_id):
64 """Return detailed output on a server specified by its id"""
65 path = '/servers/%d' % server_id
66 reply = self.http_get(path)
67 return reply['server']
69 def create_server(self, name, flavor_id, image_id, personality=None):
70 """Submit request to create a new server
72 The flavor_id specifies the hardware configuration to use,
73 the image_id specifies the OS Image to be deployed inside the new
76 The personality argument is a list of (file path, file contents)
77 tuples, describing files to be injected into the server upon creation.
79 The call returns a dictionary describing the newly created server.
81 req = {'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}
83 req['personality'] = personality
85 body = json.dumps({'server': req})
86 reply = self.http_post('/servers', body)
87 return reply['server']
89 def update_server_name(self, server_id, new_name):
90 """Update the name of the server as reported by the API.
92 This call does not modify the hostname actually used by the server
95 path = '/servers/%d' % server_id
96 body = json.dumps({'server': {'name': new_name}})
97 self.http_put(path, body)
99 def delete_server(self, server_id):
100 """Submit a deletion request for a server specified by id"""
101 path = '/servers/%d' % server_id
102 self.http_delete(path)
104 def reboot_server(self, server_id, hard=False):
105 """Submit a reboot request for a server specified by id"""
106 path = '/servers/%d/action' % server_id
107 type = 'HARD' if hard else 'SOFT'
108 body = json.dumps({'reboot': {'type': type}})
109 self.http_post(path, body)
111 def get_server_metadata(self, server_id, key=None):
112 path = '/servers/%d/meta' % server_id
115 reply = self.http_get(path)
116 return reply['meta'] if key else reply['metadata']['values']
118 def create_server_metadata(self, server_id, key, val):
119 path = '/servers/%d/meta/%s' % (server_id, key)
120 body = json.dumps({'meta': {key: val}})
121 reply = self.http_put(path, body, success=201)
124 def update_server_metadata(self, server_id, **metadata):
125 path = '/servers/%d/meta' % server_id
126 body = json.dumps({'metadata': metadata})
127 reply = self.http_post(path, body, success=201)
128 return reply['metadata']
130 def delete_server_metadata(self, server_id, key):
131 path = '/servers/%d/meta/%s' % (server_id, key)
132 reply = self.http_delete(path)
135 def list_flavors(self, detail=False):
136 path = '/flavors/detail' if detail else '/flavors'
137 reply = self.http_get(path)
138 return reply['flavors']['values']
140 def get_flavor_details(self, flavor_id):
141 path = '/flavors/%d' % flavor_id
142 reply = self.http_get(path)
143 return reply['flavor']
146 def list_images(self, detail=False):
147 path = '/images/detail' if detail else '/images'
148 reply = self.http_get(path)
149 return reply['images']['values']
151 def get_image_details(self, image_id):
152 path = '/images/%s' % image_id
153 reply = self.http_get(path)
154 return reply['image']
156 def delete_image(self, image_id):
157 path = '/images/%s' % image_id
158 self.http_delete(path)
160 def get_image_metadata(self, image_id, key=None):
161 path = '/images/%s/meta' % image_id
164 reply = self.http_get(path)
165 return reply['meta'] if key else reply['metadata']['values']
167 def create_image_metadata(self, image_id, key, val):
168 path = '/images/%s/meta/%s' % (image_id, key)
169 body = json.dumps({'meta': {key: val}})
170 reply = self.http_put(path, body, success=201)
173 def update_image_metadata(self, image_id, **metadata):
174 path = '/images/%s/meta' % image_id
175 body = json.dumps({'metadata': metadata})
176 reply = self.http_post(path, body, success=201)
177 return reply['metadata']
179 def delete_image_metadata(self, image_id, key):
180 path = '/images/%s/meta/%s' % (image_id, key)
181 self.http_delete(path)