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.
34 from kamaki.clients.compute import ComputeClient, ClientError
35 from kamaki.clients.utils import path4url
37 from time import sleep
40 class CycladesClient(ComputeClient):
41 """GRNet Cyclades API client"""
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 ''
48 path = path4url('networks', network_id, command)
49 success = kwargs.pop('success', (200, 203))
50 return self.get(path, success=success, **kwargs)
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 ''
57 path = path4url('networks', network_id, command)
58 success = kwargs.pop('success', 204)
59 return self.delete(path, success=success, **kwargs)
61 def networks_post(self,
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
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))
77 path = path4url('networks', network_id, command)
78 success = kwargs.pop('success', 202)
79 return self.post(path, data=data, success=success, **kwargs)
81 def networks_put(self,
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
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))
97 path = path4url('networks', network_id, command)
98 success = kwargs.pop('success', 204)
99 return self.put(path, data=data, success=success, **kwargs)
101 def start_server(self, server_id):
102 """Submit a startup request for a server specified by id"""
104 r = self.servers_post(server_id, 'action', json_data=req, success=202)
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)
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']
119 def get_firewall_profile(self, server_id):
120 r = self.get_server_details(server_id)
122 return r['attachments']['values'][0]['firewallProfile']
124 raise ClientError('No Firewall Profile', 520,
125 details='Server %s is missing a firewall profile' % server_id)
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).
132 req = {'firewallProfile': {'profile': profile}}
133 r = self.servers_post(server_id, 'action', json_data=req, success=202)
136 def list_server_nics(self, server_id):
137 r = self.servers_get(server_id, 'ips')
138 return r.json['addresses']['values']
140 def get_server_stats(self, server_id):
141 r = self.servers_get(server_id, 'stats')
142 return r.json['stats']
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']
150 def list_network_nics(self, network_id):
151 r = self.networks_get(network_id=network_id)
152 return r.json['network']['attachments']['values']
154 def create_network(self,
160 """@params cidr, geteway, type and dhcp should be strings
162 net = dict(name=name)
166 net['gateway'] = gateway
171 req = dict(network=net)
172 r = self.networks_post(json_data=req, success=202)
173 return r.json['network']
175 def get_network_details(self, network_id):
176 r = self.networks_get(network_id=network_id)
177 return r.json['network']
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)
184 def delete_network(self, network_id):
186 r = self.networks_delete(network_id)
187 except ClientError as err:
188 if err.status == 421:
190 'Network may be still connected to at least one server'
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)
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']]
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)
211 def disconnect_network_nics(self, netid):
212 r = self.list_network_nics(netid)
214 req = dict(remove=dict(attachment=nic))
215 r = self.networks_post(netid, 'action', json_data=req)
217 def wait_server(self, server_id,
218 current_status='BUILD',
222 """Wait for server to reach a status different from current_status
223 @return the new mode if succesfull, False if timed out
226 @delay time interval between retries
227 @wait_cb if set a progressbar is used to show progress
229 r = self.get_server_details(server_id)
230 if r['status'] != current_status:
233 old_wait = total_wait
235 if current_status == 'BUILD':
236 max_wait = delay * 100
239 wait_gen = wait_cb(1 + max_wait // delay)
241 while r['status'] == current_status and total_wait <= max_wait:
242 if current_status == 'BUILD':
243 total_wait = r['progress']
245 for i in range(old_wait, total_wait):
247 old_wait = total_wait
253 r = self.get_server_details(server_id)
255 if wait_cb and r['status'] != current_status: