Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / cyclades / __init__.py @ c6afee48

History | View | Annotate | Download (13.7 kB)

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 kamaki.clients.cyclades.rest_api import CycladesRestClient
35
from kamaki.clients.network import NetworkClient
36
from kamaki.clients.utils import path4url
37
from kamaki.clients import ClientError, Waiter
38

    
39

    
40
class CycladesClient(CycladesRestClient, Waiter):
41
    """Synnefo Cyclades Compute API client"""
42

    
43
    def create_server(
44
            self, name, flavor_id, image_id,
45
            metadata=None, personality=None, networks=None):
46
        """Submit request to create a new server
47

48
        :param name: (str)
49

50
        :param flavor_id: integer id denoting a preset hardware configuration
51

52
        :param image_id: (str) id denoting the OS image to run on virt. server
53

54
        :param metadata: (dict) vm metadata updated by os/users image metadata
55

56
        :param personality: a list of (file path, file contents) tuples,
57
            describing files to be injected into virtual server upon creation
58

59
        :param networks: (list of dicts) Networks to connect to, list this:
60
            "networks": [
61
                {"network": <network_uuid>},
62
                {"network": <network_uuid>, "fixed_ip": address},
63
                {"port": <port_id>}, ...]
64

65
        :returns: a dict with the new virtual server details
66

67
        :raises ClientError: wraps request errors
68
        """
69
        image = self.get_image_details(image_id)
70
        metadata = metadata or dict()
71
        for key in ('os', 'users'):
72
            try:
73
                metadata[key] = image['metadata'][key]
74
            except KeyError:
75
                pass
76

    
77
        return super(CycladesClient, self).create_server(
78
            name, flavor_id, image_id,
79
            metadata=metadata, personality=personality)
80

    
81
    def start_server(self, server_id):
82
        """Submit a startup request
83

84
        :param server_id: integer (str or int)
85

86
        :returns: (dict) response headers
87
        """
88
        req = {'start': {}}
89
        r = self.servers_action_post(server_id, json_data=req, success=202)
90
        return r.headers
91

    
92
    def shutdown_server(self, server_id):
93
        """Submit a shutdown request
94

95
        :param server_id: integer (str or int)
96

97
        :returns: (dict) response headers
98
        """
99
        req = {'shutdown': {}}
100
        r = self.servers_action_post(server_id, json_data=req, success=202)
101
        return r.headers
102

    
103
    def get_server_console(self, server_id):
104
        """
105
        :param server_id: integer (str or int)
106

107
        :returns: (dict) info to set a VNC connection to virtual server
108
        """
109
        req = {'console': {'type': 'vnc'}}
110
        r = self.servers_action_post(server_id, json_data=req, success=200)
111
        return r.json['console']
112

    
113
    def get_firewall_profile(self, server_id):
114
        """
115
        :param server_id: integer (str or int)
116

117
        :returns: (str) ENABLED | DISABLED | PROTECTED
118

119
        :raises ClientError: 520 No Firewall Profile
120
        """
121
        r = self.get_server_details(server_id)
122
        try:
123
            return r['attachments'][0]['firewallProfile']
124
        except KeyError:
125
            raise ClientError(
126
                'No Firewall Profile',
127
                details='Server %s is missing a firewall profile' % server_id)
128

    
129
    def set_firewall_profile(self, server_id, profile):
130
        """Set the firewall profile for the public interface of a server
131

132
        :param server_id: integer (str or int)
133

134
        :param profile: (str) ENABLED | DISABLED | PROTECTED
135

136
        :returns: (dict) response headers
137
        """
138
        req = {'firewallProfile': {'profile': profile}}
139
        r = self.servers_action_post(server_id, json_data=req, success=202)
140
        return r.headers
141

    
142
    def list_server_nics(self, server_id):
143
        """
144
        :param server_id: integer (str or int)
145

146
        :returns: (dict) network interface connections
147
        """
148
        r = self.servers_ips_get(server_id)
149
        return r.json['attachments']
150

    
151
    def get_server_stats(self, server_id):
152
        """
153
        :param server_id: integer (str or int)
154

155
        :returns: (dict) auto-generated graphs of statistics (urls)
156
        """
157
        r = self.servers_stats_get(server_id)
158
        return r.json['stats']
159

    
160
    def list_networks(self, detail=False):
161
        """
162
        :param detail: (bool)
163

164
        :returns: (list) id,name if not detail else full info per network
165
        """
166
        detail = 'detail' if detail else ''
167
        r = self.networks_get(command=detail)
168
        return r.json['networks']
169

    
170
    def list_network_nics(self, network_id):
171
        """
172
        :param network_id: integer (str or int)
173

174
        :returns: (list)
175
        """
176
        r = self.networks_get(network_id=network_id)
177
        return r.json['network']['attachments']
178

    
179
    def create_network(
180
            self, name,
181
            cidr=None, gateway=None, type=None, dhcp=False):
182
        """
183
        :param name: (str)
184

185
        :param cidr: (str)
186

187
        :param geteway: (str)
188

189
        :param type: (str) if None, will use MAC_FILTERED as default
190
            Valid values: CUSTOM, IP_LESS_ROUTED, MAC_FILTERED, PHYSICAL_VLAN
191

192
        :param dhcp: (bool)
193

194
        :returns: (dict) network detailed info
195
        """
196
        net = dict(name=name)
197
        if cidr:
198
            net['cidr'] = cidr
199
        if gateway:
200
            net['gateway'] = gateway
201
        net['type'] = type or 'MAC_FILTERED'
202
        net['dhcp'] = True if dhcp else False
203
        req = dict(network=net)
204
        r = self.networks_post(json_data=req, success=202)
205
        return r.json['network']
206

    
207
    def get_network_details(self, network_id):
208
        """
209
        :param network_id: integer (str or int)
210

211
        :returns: (dict)
212
        """
213
        r = self.networks_get(network_id=network_id)
214
        return r.json['network']
215

    
216
    def update_network_name(self, network_id, new_name):
217
        """
218
        :param network_id: integer (str or int)
219

220
        :param new_name: (str)
221

222
        :returns: (dict) response headers
223
        """
224
        req = {'network': {'name': new_name}}
225
        r = self.networks_put(network_id=network_id, json_data=req)
226
        return r.headers
227

    
228
    def delete_network(self, network_id):
229
        """
230
        :param network_id: integer (str or int)
231

232
        :returns: (dict) response headers
233

234
        :raises ClientError: 421 Network in use
235
        """
236
        try:
237
            r = self.networks_delete(network_id)
238
            return r.headers
239
        except ClientError as err:
240
            if err.status == 421:
241
                err.details = [
242
                    'Network may be still connected to at least one server']
243
            raise
244

    
245
    def connect_server(self, server_id, network_id):
246
        """ Connect a server to a network
247

248
        :param server_id: integer (str or int)
249

250
        :param network_id: integer (str or int)
251

252
        :returns: (dict) response headers
253
        """
254
        req = {'add': {'serverRef': server_id}}
255
        r = self.networks_post(network_id, 'action', json_data=req)
256
        return r.headers
257

    
258
    def disconnect_server(self, server_id, nic_id):
259
        """
260
        :param server_id: integer (str or int)
261

262
        :param nic_id: (str)
263

264
        :returns: (int) the number of nics disconnected
265
        """
266
        vm_nets = self.list_server_nics(server_id)
267
        num_of_disconnections = 0
268
        for (nic_id, network_id) in [(
269
                net['id'],
270
                net['network_id']) for net in vm_nets if nic_id == net['id']]:
271
            req = {'remove': {'attachment': '%s' % nic_id}}
272
            self.networks_post(network_id, 'action', json_data=req)
273
            num_of_disconnections += 1
274
        return num_of_disconnections
275

    
276
    def disconnect_network_nics(self, netid):
277
        """
278
        :param netid: integer (str or int)
279
        """
280
        for nic in self.list_network_nics(netid):
281
            req = dict(remove=dict(attachment=nic))
282
            self.networks_post(netid, 'action', json_data=req)
283

    
284
    def wait_server(
285
            self, server_id,
286
            current_status='BUILD',
287
            delay=1, max_wait=100, wait_cb=None):
288
        """Wait for server while its status is current_status
289

290
        :param server_id: integer (str or int)
291

292
        :param current_status: (str) BUILD|ACTIVE|STOPPED|DELETED|REBOOT
293

294
        :param delay: time interval between retries
295

296
        :max_wait: (int) timeout in secconds
297

298
        :param wait_cb: if set a progressbar is used to show progress
299

300
        :returns: (str) the new mode if succesfull, (bool) False if timed out
301
        """
302

    
303
        def get_status(self, server_id):
304
            r = self.get_server_details(server_id)
305
            return r['status'], (r.get('progress', None) if (
306
                            current_status in ('BUILD', )) else None)
307

    
308
        return self._wait(
309
            server_id, current_status, get_status, delay, max_wait, wait_cb)
310

    
311
    def wait_network(
312
            self, net_id,
313
            current_status='PENDING', delay=1, max_wait=100, wait_cb=None):
314
        """Wait for network while its status is current_status
315

316
        :param net_id: integer (str or int)
317

318
        :param current_status: (str) PENDING | ACTIVE | DELETED
319

320
        :param delay: time interval between retries
321

322
        :max_wait: (int) timeout in secconds
323

324
        :param wait_cb: if set a progressbar is used to show progress
325

326
        :returns: (str) the new mode if succesfull, (bool) False if timed out
327
        """
328

    
329
        def get_status(self, net_id):
330
            r = self.get_network_details(net_id)
331
            return r['status'], None
332

    
333
        return self._wait(
334
            net_id, current_status, get_status, delay, max_wait, wait_cb)
335

    
336
    def wait_firewall(
337
            self, server_id,
338
            current_status='DISABLED', delay=1, max_wait=100, wait_cb=None):
339
        """Wait while the public network firewall status is current_status
340

341
        :param server_id: integer (str or int)
342

343
        :param current_status: (str) DISABLED | ENABLED | PROTECTED
344

345
        :param delay: time interval between retries
346

347
        :max_wait: (int) timeout in secconds
348

349
        :param wait_cb: if set a progressbar is used to show progress
350

351
        :returns: (str) the new mode if succesfull, (bool) False if timed out
352
        """
353

    
354
        def get_status(self, server_id):
355
            return self.get_firewall_profile(server_id), None
356

    
357
        return self._wait(
358
            server_id, current_status, get_status, delay, max_wait, wait_cb)
359

    
360

    
361
class CycladesNetworkClient(NetworkClient, Waiter):
362
    """Cyclades Network API extentions"""
363

    
364
    network_types = (
365
        'CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
366

    
367
    def list_networks(self, detail=None):
368
        path = path4url('networks', 'detail' if detail else '')
369
        r = self.get(path, success=200)
370
        return r.json['networks']
371

    
372
    def create_network(self, type, name=None, shared=None):
373
        req = dict(network=dict(type=type, admin_state_up=True))
374
        if name:
375
            req['network']['name'] = name
376
        if shared not in (None, ):
377
            req['network']['shared'] = bool(shared)
378
        r = self.networks_post(json_data=req, success=201)
379
        return r.json['network']
380

    
381
    def create_port(
382
            self, network_id, device_id,
383
            security_groups=None, name=None, fixed_ips=None):
384
        port = dict(network_id=network_id, device_id=device_id)
385
        if security_groups:
386
            port['security_groups'] = security_groups
387
        if name:
388
            port['name'] = name
389
        for fixed_ip in fixed_ips:
390
            diff = set(['subnet_id', 'ip_address']).difference(fixed_ip)
391
            if diff:
392
                raise ValueError(
393
                    'Invalid format for "fixed_ips", %s missing' % diff)
394
        if fixed_ips:
395
            port['fixed_ips'] = fixed_ips
396
        r = self.ports_post(json_data=dict(port=port), success=201)
397
        return r.json['port']
398

    
399
    def wait_network(
400
            self, net_id,
401
            current_status='PENDING', delay=1, max_wait=100, wait_cb=None):
402

    
403
        def get_status(self, net_id):
404
            r = self.get_network_details(net_id)
405
            return r['status'], None
406

    
407
        return self._wait(
408
            net_id, current_status, get_status, delay, max_wait, wait_cb)
409

    
410
    def wait_port(
411
            self, port_id,
412
            current_status='PENDING', delay=1, max_wait=100, wait_cb=None):
413

    
414
        def get_status(self, net_id):
415
            r = self.get_port_details(port_id)
416
            return r['status'], None
417

    
418
        return self._wait(
419
            port_id, current_status, get_status, delay, max_wait, wait_cb)