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 sys import stdout
35 from time import sleep
37 from kamaki.clients.cyclades.rest_api import CycladesRestClient
38 from kamaki.clients import ClientError
41 class CycladesClient(CycladesRestClient):
42 """Synnefo Cyclades Compute API client"""
44 def start_server(self, server_id):
45 """Submit a startup request
47 :param server_id: integer (str or int)
49 :returns: (dict) response headers
52 r = self.servers_post(server_id, 'action', json_data=req, success=202)
55 def shutdown_server(self, server_id):
56 """Submit a shutdown request
58 :param server_id: integer (str or int)
60 :returns: (dict) response headers
62 req = {'shutdown': {}}
63 r = self.servers_post(server_id, 'action', json_data=req, success=202)
66 def get_server_console(self, server_id):
68 :param server_id: integer (str or int)
70 :returns: (dict) info to set a VNC connection to VM
72 req = {'console': {'type': 'vnc'}}
73 r = self.servers_post(server_id, 'action', json_data=req, success=200)
74 return r.json['console']
76 def get_firewall_profile(self, server_id):
78 :param server_id: integer (str or int)
80 :returns: (str) ENABLED | DISABLED | PROTECTED
82 :raises ClientError: 520 No Firewall Profile
84 r = self.get_server_details(server_id)
86 return r['attachments']['values'][0]['firewallProfile']
89 'No Firewall Profile',
90 details='Server %s is missing a firewall profile' % server_id)
92 def set_firewall_profile(self, server_id, profile):
93 """Set the firewall profile for the public interface of a server
95 :param server_id: integer (str or int)
97 :param profile: (str) ENABLED | DISABLED | PROTECTED
99 :returns: (dict) response headers
101 req = {'firewallProfile': {'profile': profile}}
102 r = self.servers_post(server_id, 'action', json_data=req, success=202)
105 def list_servers(self, detail=False, changes_since=None):
107 :param detail: (bool) append full server details to each item if true
109 :param changes_since: (date)
111 :returns: list of server ids and names
113 detail = 'detail' if detail else ''
114 r = self.servers_get(command=detail, changes_since=changes_since)
115 return r.json['servers']['values']
117 def list_server_nics(self, server_id):
119 :param server_id: integer (str or int)
121 :returns: (dict) network interface connections
123 r = self.servers_get(server_id, 'ips')
124 return r.json['addresses']['values']
126 def get_server_stats(self, server_id):
128 :param server_id: integer (str or int)
130 :returns: (dict) auto-generated graphs of statistics (urls)
132 r = self.servers_get(server_id, 'stats')
133 return r.json['stats']
135 def list_networks(self, detail=False):
137 :param detail: (bool)
139 :returns: (list) id,name if not detail else full info per network
141 detail = 'detail' if detail else ''
142 r = self.networks_get(command=detail)
143 return r.json['networks']['values']
145 def list_network_nics(self, network_id):
147 :param network_id: integer (str or int)
151 r = self.networks_get(network_id=network_id)
152 return r.json['network']['attachments']['values']
156 cidr=None, gateway=None, type=None, dhcp=False):
162 :param geteway: (str)
164 :param type: (str) if None, will use MAC_FILTERED as default
165 Valid values: CUSTOM, IP_LESS_ROUTED, MAC_FILTERED, PHYSICAL_VLAN
169 :returns: (dict) network detailed info
171 net = dict(name=name)
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']
182 def get_network_details(self, network_id):
184 :param network_id: integer (str or int)
188 r = self.networks_get(network_id=network_id)
189 return r.json['network']
191 def update_network_name(self, network_id, new_name):
193 :param network_id: integer (str or int)
195 :param new_name: (str)
197 req = {'network': {'name': new_name}}
198 self.networks_put(network_id=network_id, json_data=req)
200 def delete_network(self, network_id):
202 :param network_id: integer (str or int)
204 :raises ClientError: 421 Network in use
207 self.networks_delete(network_id)
208 except ClientError as err:
209 if err.status == 421:
211 'Network may be still connected to at least one server']
214 def connect_server(self, server_id, network_id):
215 """ Connect a server to a network
217 :param server_id: integer (str or int)
219 :param network_id: integer (str or int)
221 req = {'add': {'serverRef': server_id}}
222 self.networks_post(network_id, 'action', json_data=req)
224 def disconnect_server(self, server_id, nic_id):
226 :param server_id: integer (str or int)
230 :returns: (int) the number of nics disconnected
232 vm_nets = self.list_server_nics(server_id)
233 num_of_disconnections = 0
234 for (nic_id, network_id) in [(
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
242 def disconnect_network_nics(self, netid):
244 :param netid: integer (str or int)
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)
253 current_status='BUILD',
257 """Wait for server while its status is current_status
259 :param server_id: integer (str or int)
261 :param current_status: (str) BUILD|ACTIVE|STOPPED|DELETED|REBOOT
263 :param delay: time interval between retries
265 :param wait_cb: if set a progressbar is used to show progress
267 :returns: (str) the new mode if succesfull, (bool) False if timed out
269 r = self.get_server_details(server_id)
270 if r['status'] != current_status:
272 old_wait = total_wait = 0
274 if current_status == 'BUILD':
276 wait_gen = wait_cb(max_wait) if wait_cb else None
278 wait_gen = wait_cb(1 + max_wait // delay)
281 while r['status'] == current_status and total_wait <= max_wait:
282 if current_status == 'BUILD':
283 total_wait = int(r['progress'])
285 for i in range(int(old_wait), int(total_wait)):
287 old_wait = total_wait
299 r = self.get_server_details(server_id)
301 if r['status'] != current_status: