1 # Copyright 2011-2013 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 time import sleep
36 from kamaki.clients.cyclades_rest_api import CycladesClientApi
37 from kamaki.clients import ClientError
38 from sys import stdout
41 class CycladesClient(CycladesClientApi):
42 """GRNet Cyclades API client"""
44 def start_server(self, server_id):
45 """Submit a startup request
47 :param server_id: integer (str or int)
50 r = self.servers_post(server_id, 'action', json_data=req, success=202)
53 def shutdown_server(self, server_id):
54 """Submit a shutdown request
56 :param server_id: integer (str or int)
58 req = {'shutdown': {}}
59 r = self.servers_post(server_id, 'action', json_data=req, success=202)
62 def get_server_console(self, server_id):
64 :param server_id: integer (str or int)
66 :returns: (dict) info to set a VNC connection to VM
68 req = {'console': {'type': 'vnc'}}
69 r = self.servers_post(server_id, 'action', json_data=req, success=200)
70 return r.json['console']
72 def get_firewall_profile(self, server_id):
74 :param server_id: integer (str or int)
76 :returns: (str) ENABLED | DISABLED | PROTECTED
78 :raises ClientError: 520 No Firewall Profile
80 r = self.get_server_details(server_id)
82 return r['attachments']['values'][0]['firewallProfile']
85 'No Firewall Profile',
86 details='Server %s is missing a firewall profile' % server_id)
88 def set_firewall_profile(self, server_id, profile):
89 """Set the firewall profile for the public interface of a server
91 :param server_id: integer (str or int)
93 :param profile: (str) ENABLED | DISABLED | PROTECTED
95 req = {'firewallProfile': {'profile': profile}}
96 r = self.servers_post(server_id, 'action', json_data=req, success=202)
99 def list_servers(self, detail=False, changes_since=None):
101 :param detail: (bool) append full server details to each item if true
103 :param changes_since: (date)
105 :returns: list of server ids and names
107 detail = 'detail' if detail else ''
108 r = self.servers_get(command=detail, changes_since=changes_since)
109 return r.json['servers']['values']
111 def list_server_nics(self, server_id):
113 :param server_id: integer (str or int)
115 :returns: (dict) network interface connections
117 r = self.servers_get(server_id, 'ips')
118 return r.json['addresses']['values']
120 def get_server_stats(self, server_id):
122 :param server_id: integer (str or int)
124 :returns: (dict) auto-generated graphs of statistics (urls)
126 r = self.servers_get(server_id, 'stats')
127 return r.json['stats']
129 def list_networks(self, detail=False):
131 :param detail: (bool)
133 :returns: (list) id,name if not detail else full info per network
135 detail = 'detail' if detail else ''
136 r = self.networks_get(command=detail)
137 return r.json['networks']['values']
139 def list_network_nics(self, network_id):
141 :param network_id: integer (str or int)
145 r = self.networks_get(network_id=network_id)
146 return r.json['network']['attachments']['values']
150 cidr=None, gateway=None, type=None, dhcp=False):
156 :param geteway: (str)
162 :returns: (dict) network detailed info
164 net = dict(name=name)
168 net['gateway'] = gateway
171 net['dhcp'] = True if dhcp else False
172 req = dict(network=net)
173 r = self.networks_post(json_data=req, success=202)
174 return r.json['network']
176 def get_network_details(self, network_id):
178 :param network_id: integer (str or int)
182 r = self.networks_get(network_id=network_id)
183 return r.json['network']
185 def update_network_name(self, network_id, new_name):
187 :param network_id: integer (str or int)
189 :param new_name: (str)
191 req = {'network': {'name': new_name}}
192 r = self.networks_put(network_id=network_id, json_data=req)
195 def delete_network(self, network_id):
197 :param network_id: integer (str or int)
199 :raises ClientError: 421 Network in use
202 r = self.networks_delete(network_id)
203 except ClientError as err:
204 if err.status == 421:
206 'Network may be still connected to at least one server']
210 def connect_server(self, server_id, network_id):
211 """ Connect a server to a network
213 :param server_id: integer (str or int)
215 :param network_id: integer (str or int)
217 req = {'add': {'serverRef': server_id}}
218 r = self.networks_post(network_id, 'action', json_data=req)
221 def disconnect_server(self, server_id, nic_id):
223 :param server_id: integer (str or int)
227 vm_nets = self.list_server_nics(server_id)
228 num_of_disconnections = 0
229 for (nic_id, network_id) in [(
231 net['network_id']) for net in vm_nets if nic_id == net['id']]:
232 req = {'remove': {'attachment': '%s' % nic_id}}
233 r = self.networks_post(network_id, 'action', json_data=req)
235 num_of_disconnections += 1
236 return num_of_disconnections
238 def disconnect_network_nics(self, netid):
240 :param netid: integer (str or int)
242 for nic in self.list_network_nics(netid):
243 req = dict(remove=dict(attachment=nic))
244 r = self.networks_post(netid, 'action', json_data=req)
250 current_status='BUILD',
254 """Wait for server while its status is current_status
256 :param server_id: integer (str or int)
258 :param current_status: (str) BUILD|ACTIVE|STOPPED|DELETED|REBOOT
260 :param delay: time interval between retries
262 :param wait_cb: if set a progressbar is used to show progress
264 :returns: (str) the new mode if succesfull, (bool) False if timed out
266 r = self.get_server_details(server_id)
267 if r['status'] != current_status:
269 old_wait = total_wait = 0
271 if current_status == 'BUILD':
273 wait_gen = wait_cb(max_wait) if wait_cb else None
275 wait_gen = wait_cb(1 + max_wait // delay)
278 while r['status'] == current_status and total_wait <= max_wait:
279 if current_status == 'BUILD':
280 total_wait = int(r['progress'])
282 for i in range(int(old_wait), int(total_wait)):
284 old_wait = total_wait
296 r = self.get_server_details(server_id)
298 if r['status'] != current_status: