7d2cf8d8aefb0cf5a8d99f34d4c58e9ec07b8bda
[kamaki] / kamaki / clients / cyclades.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 from kamaki.clients.compute import ComputeClient, ClientError
35 from kamaki.clients.utils import path4url
36 import json
37 from time import sleep
38
39
40 class CycladesClient(ComputeClient):
41     """GRNet Cyclades API client"""
42
43     def networks_get(self, network_id='', command='', **kwargs):
44         """GET base_url/networks[/network_id][/command] request
45         @param network_id or ''
46         @param command can be 'detail', or ''
47         """
48         path = path4url('networks', network_id, command)
49         success = kwargs.pop('success', (200, 203))
50         return self.get(path, success=success, **kwargs)
51
52     def networks_delete(self, network_id='', command='', **kwargs):
53         """DEL ETE base_url/networks[/network_id][/command] request
54         @param network_id or ''
55         @param command can be 'detail', or ''
56         """
57         path = path4url('networks', network_id, command)
58         success = kwargs.pop('success', 204)
59         return self.delete(path, success=success, **kwargs)
60
61     def networks_post(self,
62         network_id='',
63         command='',
64         json_data=None,
65         **kwargs):
66         """POST base_url/servers[/server_id]/[command] request
67         @param server_id or ''
68         @param command: can be 'action' or ''
69         @param json_data: a json valid dict that will be send as data
70         """
71         data = json_data
72         if json_data is not None:
73             data = json.dumps(json_data)
74             self.set_header('Content-Type', 'application/json')
75             self.set_header('Content-Length', len(data))
76
77         path = path4url('networks', network_id, command)
78         success = kwargs.pop('success', 202)
79         return self.post(path, data=data, success=success, **kwargs)
80
81     def networks_put(self,
82         network_id='',
83         command='',
84         json_data=None,
85         **kwargs):
86         """PUT base_url/servers[/server_id]/[command] request
87         @param server_id or ''
88         @param command: can be 'action' or ''
89         @param json_data: a json valid dict that will be send as data
90         """
91         data = json_data
92         if json_data is not None:
93             data = json.dumps(json_data)
94             self.set_header('Content-Type', 'application/json')
95             self.set_header('Content-Length', len(data))
96
97         path = path4url('networks', network_id, command)
98         success = kwargs.pop('success', 204)
99         return self.put(path, data=data, success=success, **kwargs)
100
101     def start_server(self, server_id):
102         """Submit a startup request for a server specified by id"""
103         req = {'start': {}}
104         r = self.servers_post(server_id, 'action', json_data=req, success=202)
105         r.release()
106
107     def shutdown_server(self, server_id):
108         """Submit a shutdown request for a server specified by id"""
109         req = {'shutdown': {}}
110         r = self.servers_post(server_id, 'action', json_data=req, success=202)
111         r.release()
112
113     def get_server_console(self, server_id):
114         """Get a VNC connection to the console of a server specified by id"""
115         req = {'console': {'type': 'vnc'}}
116         r = self.servers_post(server_id, 'action', json_data=req, success=200)
117         return r.json['console']
118
119     def get_firewall_profile(self, server_id):
120         r = self.get_server_details(server_id)
121         try:
122             return r['attachments']['values'][0]['firewallProfile']
123         except KeyError:
124             raise ClientError('No Firewall Profile', 520,
125                 details='Server %s is missing a firewall profile' % server_id)
126
127     def set_firewall_profile(self, server_id, profile):
128         """Set the firewall profile for the public interface of a server
129            The server is specified by id, the profile argument
130            is one of (ENABLED, DISABLED, PROTECTED).
131         """
132         req = {'firewallProfile': {'profile': profile}}
133         r = self.servers_post(server_id, 'action', json_data=req, success=202)
134         r.release()
135
136     def list_server_nics(self, server_id):
137         r = self.servers_get(server_id, 'ips')
138         return r.json['addresses']['values']
139
140     def get_server_stats(self, server_id):
141         r = self.servers_get(server_id, 'stats')
142         return r.json['stats']
143
144     def list_networks(self, detail=False):
145         detail = 'detail' if detail else ''
146         r = self.networks_get(command=detail)
147         return r.json['networks']['values']
148
149     #NEW
150     def list_network_nics(self, network_id):
151         r = self.networks_get(network_id=network_id)
152         return r.json['network']['attachments']['values']
153
154     def create_network(self,
155         name,
156         cidr=False,
157         gateway=False,
158         type=False,
159         dhcp=False):
160         """@params cidr, geteway, type and dhcp should be strings
161         """
162         net = dict(name=name)
163         if cidr:
164             net['cidr'] = cidr
165         if gateway:
166             net['gateway'] = gateway
167         if type:
168             net['type'] = type
169         if dhcp:
170             net['dhcp'] = dhcp
171         req = dict(network=net)
172         r = self.networks_post(json_data=req, success=202)
173         return r.json['network']
174
175     def get_network_details(self, network_id):
176         r = self.networks_get(network_id=network_id)
177         return r.json['network']
178
179     def update_network_name(self, network_id, new_name):
180         req = {'network': {'name': new_name}}
181         r = self.networks_put(network_id=network_id, json_data=req)
182         r.release()
183
184     def delete_network(self, network_id):
185         try:
186             r = self.networks_delete(network_id)
187         except ClientError as err:
188             if err.status == 421:
189                 err.details =\
190                 'Network may be still connected to at least one server'
191             raise err
192         r.release()
193
194     def connect_server(self, server_id, network_id):
195         req = {'add': {'serverRef': server_id}}
196         r = self.networks_post(network_id, 'action', json_data=req)
197         r.release()
198
199     def disconnect_server(self, server_id, nic_id):
200         server_nets = self.list_server_nics(server_id)
201         nets = [(net['id'], net['network_id']) for net in server_nets\
202             if nic_id == net['id']]
203         if len(nets) == 0:
204             return
205         for (nic_id, network_id) in nets:
206             req = {'remove': {'attachment': unicode(nic_id)}}
207             r = self.networks_post(network_id, 'action', json_data=req)
208             r.release()
209
210     #NEW
211     def disconnect_network_nics(self, netid):
212         r = self.list_network_nics(netid)
213         for nic in r:
214             req = dict(remove=dict(attachment=nic))
215             r = self.networks_post(netid, 'action', json_data=req)
216
217     def wait_server(self, server_id,
218         current_status='BUILD',
219         delay=0.5,
220         max_wait=128,
221         wait_cb=None):
222         """Wait for server to reach a status different from current_status
223             @return the new mode if succesfull, False if timed out
224             @server_id
225             @current_status
226             @delay time interval between retries
227             @wait_cb if set a progressbar is used to show progress
228         """
229         r = self.get_server_details(server_id)
230         if r['status'] != current_status:
231             return r['status']
232         total_wait = 1
233         old_wait = total_wait
234
235         if current_status == 'BUILD':
236             max_wait = delay * 100
237
238         if wait_cb:
239             wait_gen = wait_cb(1 + max_wait // delay)
240
241         while r['status'] == current_status and total_wait <= max_wait:
242             if current_status == 'BUILD':
243                 total_wait = r['progress']
244                 if wait_cb:
245                     for i in range(old_wait, total_wait):
246                         wait_gen.next()
247                     old_wait = total_wait
248             else:
249                 if wait_cb:
250                     wait_gen.next()
251                 total_wait += delay
252             sleep(delay)
253             r = self.get_server_details(server_id)
254
255         if wait_cb and r['status'] != current_status:
256             try:
257                 while True:
258                     wait_gen.next()
259             except:
260                 pass
261             return r['status']
262         return False