Add json/optional outputs to server commands
[kamaki] / kamaki / clients / cyclades / __init__.py
1 # Copyright 2011-2013 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 from sys import stdout
35 from time import sleep
36
37 from kamaki.clients.cyclades.rest_api import CycladesRestClient
38 from kamaki.clients import ClientError
39
40
41 class CycladesClient(CycladesRestClient):
42     """Synnefo Cyclades Compute API client"""
43
44     def start_server(self, server_id):
45         """Submit a startup request
46
47         :param server_id: integer (str or int)
48
49         :returns: (dict) response headers
50         """
51         req = {'start': {}}
52         r = self.servers_post(server_id, 'action', json_data=req, success=202)
53         return r.headers
54
55     def shutdown_server(self, server_id):
56         """Submit a shutdown request
57
58         :param server_id: integer (str or int)
59
60         :returns: (dict) response headers
61         """
62         req = {'shutdown': {}}
63         r = self.servers_post(server_id, 'action', json_data=req, success=202)
64         return r.headers
65
66     def get_server_console(self, server_id):
67         """
68         :param server_id: integer (str or int)
69
70         :returns: (dict) info to set a VNC connection to VM
71         """
72         req = {'console': {'type': 'vnc'}}
73         r = self.servers_post(server_id, 'action', json_data=req, success=200)
74         return r.json['console']
75
76     def get_firewall_profile(self, server_id):
77         """
78         :param server_id: integer (str or int)
79
80         :returns: (str) ENABLED | DISABLED | PROTECTED
81
82         :raises ClientError: 520 No Firewall Profile
83         """
84         r = self.get_server_details(server_id)
85         try:
86             return r['attachments']['values'][0]['firewallProfile']
87         except KeyError:
88             raise ClientError(
89                 'No Firewall Profile',
90                 details='Server %s is missing a firewall profile' % server_id)
91
92     def set_firewall_profile(self, server_id, profile):
93         """Set the firewall profile for the public interface of a server
94
95         :param server_id: integer (str or int)
96
97         :param profile: (str) ENABLED | DISABLED | PROTECTED
98
99         :returns: (dict) response headers
100         """
101         req = {'firewallProfile': {'profile': profile}}
102         r = self.servers_post(server_id, 'action', json_data=req, success=202)
103         return r.headers
104
105     def list_servers(self, detail=False, changes_since=None):
106         """
107         :param detail: (bool) append full server details to each item if true
108
109         :param changes_since: (date)
110
111         :returns: list of server ids and names
112         """
113         detail = 'detail' if detail else ''
114         r = self.servers_get(command=detail, changes_since=changes_since)
115         return r.json['servers']['values']
116
117     def list_server_nics(self, server_id):
118         """
119         :param server_id: integer (str or int)
120
121         :returns: (dict) network interface connections
122         """
123         r = self.servers_get(server_id, 'ips')
124         return r.json['addresses']['values']
125
126     def get_server_stats(self, server_id):
127         """
128         :param server_id: integer (str or int)
129
130         :returns: (dict) auto-generated graphs of statistics (urls)
131         """
132         r = self.servers_get(server_id, 'stats')
133         return r.json['stats']
134
135     def list_networks(self, detail=False):
136         """
137         :param detail: (bool)
138
139         :returns: (list) id,name if not detail else full info per network
140         """
141         detail = 'detail' if detail else ''
142         r = self.networks_get(command=detail)
143         return r.json['networks']['values']
144
145     def list_network_nics(self, network_id):
146         """
147         :param network_id: integer (str or int)
148
149         :returns: (list)
150         """
151         r = self.networks_get(network_id=network_id)
152         return r.json['network']['attachments']['values']
153
154     def create_network(
155             self, name,
156             cidr=None, gateway=None, type=None, dhcp=False):
157         """
158         :param name: (str)
159
160         :param cidr: (str)
161
162         :param geteway: (str)
163
164         :param type: (str) if None, will use MAC_FILTERED as default
165             Valid values: CUSTOM, IP_LESS_ROUTED, MAC_FILTERED, PHYSICAL_VLAN
166
167         :param dhcp: (bool)
168
169         :returns: (dict) network detailed info
170         """
171         net = dict(name=name)
172         if cidr:
173             net['cidr'] = cidr
174         if gateway:
175             net['gateway'] = gateway
176         net['type'] = type or 'MAC_FILTERED'
177         net['dhcp'] = True if dhcp else False
178         req = dict(network=net)
179         r = self.networks_post(json_data=req, success=202)
180         return r.json['network']
181
182     def get_network_details(self, network_id):
183         """
184         :param network_id: integer (str or int)
185
186         :returns: (dict)
187         """
188         r = self.networks_get(network_id=network_id)
189         return r.json['network']
190
191     def update_network_name(self, network_id, new_name):
192         """
193         :param network_id: integer (str or int)
194
195         :param new_name: (str)
196         """
197         req = {'network': {'name': new_name}}
198         self.networks_put(network_id=network_id, json_data=req)
199
200     def delete_network(self, network_id):
201         """
202         :param network_id: integer (str or int)
203
204         :raises ClientError: 421 Network in use
205         """
206         try:
207             self.networks_delete(network_id)
208         except ClientError as err:
209             if err.status == 421:
210                 err.details = [
211                     'Network may be still connected to at least one server']
212             raise
213
214     def connect_server(self, server_id, network_id):
215         """ Connect a server to a network
216
217         :param server_id: integer (str or int)
218
219         :param network_id: integer (str or int)
220         """
221         req = {'add': {'serverRef': server_id}}
222         self.networks_post(network_id, 'action', json_data=req)
223
224     def disconnect_server(self, server_id, nic_id):
225         """
226         :param server_id: integer (str or int)
227
228         :param nic_id: (str)
229
230         :returns: (int) the number of nics disconnected
231         """
232         vm_nets = self.list_server_nics(server_id)
233         num_of_disconnections = 0
234         for (nic_id, network_id) in [(
235                 net['id'],
236                 net['network_id']) for net in vm_nets if nic_id == net['id']]:
237             req = {'remove': {'attachment': '%s' % nic_id}}
238             self.networks_post(network_id, 'action', json_data=req)
239             num_of_disconnections += 1
240         return num_of_disconnections
241
242     def disconnect_network_nics(self, netid):
243         """
244         :param netid: integer (str or int)
245         """
246         for nic in self.list_network_nics(netid):
247             req = dict(remove=dict(attachment=nic))
248             self.networks_post(netid, 'action', json_data=req)
249
250     def wait_server(
251             self,
252             server_id,
253             current_status='BUILD',
254             delay=0.5,
255             max_wait=128,
256             wait_cb=None):
257         """Wait for server while its status is current_status
258
259         :param server_id: integer (str or int)
260
261         :param current_status: (str) BUILD|ACTIVE|STOPPED|DELETED|REBOOT
262
263         :param delay: time interval between retries
264
265         :param wait_cb: if set a progressbar is used to show progress
266
267         :returns: (str) the new mode if succesfull, (bool) False if timed out
268         """
269         r = self.get_server_details(server_id)
270         if r['status'] != current_status:
271             return r['status']
272         old_wait = total_wait = 0
273
274         if current_status == 'BUILD':
275             max_wait = 100
276             wait_gen = wait_cb(max_wait) if wait_cb else None
277         elif wait_cb:
278             wait_gen = wait_cb(1 + max_wait // delay)
279             wait_gen.next()
280
281         while r['status'] == current_status and total_wait <= max_wait:
282             if current_status == 'BUILD':
283                 total_wait = int(r['progress'])
284                 if wait_cb:
285                     for i in range(int(old_wait), int(total_wait)):
286                         wait_gen.next()
287                     old_wait = total_wait
288                 else:
289                     stdout.write('.')
290                     stdout.flush()
291             else:
292                 if wait_cb:
293                     wait_gen.next()
294                 else:
295                     stdout.write('.')
296                     stdout.flush()
297                 total_wait += delay
298             sleep(delay)
299             r = self.get_server_details(server_id)
300
301         if r['status'] != current_status:
302             if wait_cb:
303                 try:
304                     while True:
305                         wait_gen.next()
306                 except:
307                     pass
308             return r['status']
309         return False