Bugfixes
[kamaki] / kamaki / clients / compute.py
1 # Copyright 2011 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
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.
15 #
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.
28 #
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.
33
34 import json
35
36 from . import ClientError
37 from .http import HTTPClient
38
39
40 class ComputeClient(HTTPClient):
41     """OpenStack Compute API 1.1 client"""
42     
43     @property
44     def url(self):
45         url = self.config.get('compute_url') or self.config.get('url')
46         if not url:
47             raise ClientError('No URL was given')
48         return url
49     
50     @property
51     def token(self):
52         token = self.config.get('compute_token') or self.config.get('token')
53         if not token:
54             raise ClientError('No token was given')
55         return token
56     
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']
62     
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']
68     
69     def create_server(self, name, flavor_id, image_id, personality=None):
70         """Submit request to create a new server
71
72         The flavor_id specifies the hardware configuration to use,
73         the image_id specifies the OS Image to be deployed inside the new
74         server.
75
76         The personality argument is a list of (file path, file contents)
77         tuples, describing files to be injected into the server upon creation.
78
79         The call returns a dictionary describing the newly created server.
80         """
81         req = {'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}
82         if personality:
83             req['personality'] = personality
84         
85         body = json.dumps({'server': req})
86         reply = self.http_post('/servers', body)
87         return reply['server']
88     
89     def update_server_name(self, server_id, new_name):
90         """Update the name of the server as reported by the API.
91
92         This call does not modify the hostname actually used by the server
93         internally.
94         """
95         path = '/servers/%d' % server_id
96         body = json.dumps({'server': {'name': new_name}})
97         self.http_put(path, body)
98     
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)
103     
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)
110         
111     def get_server_metadata(self, server_id, key=None):
112         path = '/servers/%d/meta' % server_id
113         if key:
114             path += '/%s' % key
115         reply = self.http_get(path)
116         return reply['meta'] if key else reply['metadata']['values']
117     
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)
122         return reply['meta']
123     
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']
129     
130     def delete_server_metadata(self, server_id, key):
131         path = '/servers/%d/meta/%s' % (server_id, key)
132         reply = self.http_delete(path)
133         
134         
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']
139
140     def get_flavor_details(self, flavor_id):
141         path = '/flavors/%d' % flavor_id
142         reply = self.http_get(path)
143         return reply['flavor']
144     
145     
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']
150     
151     def get_image_details(self, image_id):
152         path = '/images/%s' % image_id
153         reply = self.http_get(path)
154         return reply['image']
155     
156     def delete_image(self, image_id):
157         path = '/images/%s' % image_id
158         self.http_delete(path)
159
160     def get_image_metadata(self, image_id, key=None):
161         path = '/images/%s/meta' % image_id
162         if key:
163             path += '/%s' % key
164         reply = self.http_get(path)
165         return reply['meta'] if key else reply['metadata']['values']
166     
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)
171         return reply['meta']
172
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']
178
179     def delete_image_metadata(self, image_id, key):
180         path = '/images/%s/meta/%s' % (image_id, key)
181         self.http_delete(path)