__init__.py 13.7 KB
Newer Older
1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
Giorgos Verigakis's avatar
Giorgos Verigakis committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

34
from kamaki.clients.cyclades.rest_api import CycladesRestClient
35
from kamaki.clients.network import NetworkClient
36
from kamaki.clients.utils import path4url
Stavros Sachtouris's avatar
Stavros Sachtouris committed
37
from kamaki.clients import ClientError, Waiter
Giorgos Verigakis's avatar
Giorgos Verigakis committed
38

39

Stavros Sachtouris's avatar
Stavros Sachtouris committed
40
class CycladesClient(CycladesRestClient, Waiter):
41
    """Synnefo Cyclades Compute API client"""
42

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

        :param name: (str)

        :param flavor_id: integer id denoting a preset hardware configuration

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

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

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

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

65
        :returns: a dict with the new virtual server details
66
67
68
69
70
71
72
73
74
75
76

        :raises ClientError: wraps request errors
        """
        image = self.get_image_details(image_id)
        metadata = metadata or dict()
        for key in ('os', 'users'):
            try:
                metadata[key] = image['metadata'][key]
            except KeyError:
                pass

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

Giorgos Verigakis's avatar
Giorgos Verigakis committed
81
    def start_server(self, server_id):
82
83
84
        """Submit a startup request

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

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

Giorgos Verigakis's avatar
Giorgos Verigakis committed
92
    def shutdown_server(self, server_id):
93
94
95
        """Submit a shutdown request

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

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

Giorgos Verigakis's avatar
Giorgos Verigakis committed
103
    def get_server_console(self, server_id):
104
105
106
        """
        :param server_id: integer (str or int)

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

    def get_firewall_profile(self, server_id):
114
115
116
117
118
119
120
        """
        :param server_id: integer (str or int)

        :returns: (str) ENABLED | DISABLED | PROTECTED

        :raises ClientError: 520 No Firewall Profile
        """
121
122
        r = self.get_server_details(server_id)
        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

Giorgos Verigakis's avatar
Giorgos Verigakis committed
129
130
    def set_firewall_profile(self, server_id, profile):
        """Set the firewall profile for the public interface of a server
131
132
133
134

        :param server_id: integer (str or int)

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

        :returns: (dict) response headers
Giorgos Verigakis's avatar
Giorgos Verigakis committed
137
        """
Giorgos Verigakis's avatar
Giorgos Verigakis committed
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
145
146
147
        """
        :param server_id: integer (str or int)

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

Giorgos Verigakis's avatar
Giorgos Verigakis committed
151
    def get_server_stats(self, server_id):
152
153
154
155
156
        """
        :param server_id: integer (str or int)

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

Giorgos Verigakis's avatar
Giorgos Verigakis committed
160
    def list_networks(self, detail=False):
161
162
163
164
165
        """
        :param detail: (bool)

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

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

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

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

        :param cidr: (str)

        :param geteway: (str)

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

192
        :param dhcp: (bool)
193
194

        :returns: (dict) network detailed info
195
196
197
        """
        net = dict(name=name)
        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)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
205
        return r.json['network']
Giorgos Verigakis's avatar
Giorgos Verigakis committed
206
207

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

        :returns: (dict)
        """
213
        r = self.networks_get(network_id=network_id)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
214
        return r.json['network']
Giorgos Verigakis's avatar
Giorgos Verigakis committed
215
216

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

        :param new_name: (str)
221
222

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

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

232
233
        :returns: (dict) response headers

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

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

        :param server_id: integer (str or int)

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

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

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

        :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
270
                net['id'],
                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
274
            num_of_disconnections += 1
        return num_of_disconnections
275
276

    def disconnect_network_nics(self, netid):
277
278
279
280
        """
        :param netid: integer (str or int)
        """
        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
285
286
287
288
289
290
291
292
293
294
295
    def wait_server(
            self, server_id,
            current_status='BUILD',
            delay=1, max_wait=100, wait_cb=None):
        """Wait for server while its status is current_status

        :param server_id: integer (str or int)

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

        :param delay: time interval between retries

296
297
        :max_wait: (int) timeout in secconds

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
        :param wait_cb: if set a progressbar is used to show progress

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

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

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

    def wait_network(
            self, net_id,
313
            current_status='PENDING', delay=1, max_wait=100, wait_cb=None):
314
315
316
317
318
319
320
321
        """Wait for network while its status is current_status

        :param net_id: integer (str or int)

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

        :param delay: time interval between retries

322
323
        :max_wait: (int) timeout in secconds

324
325
326
327
328
329
330
331
332
333
334
        :param wait_cb: if set a progressbar is used to show progress

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

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

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

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
    def wait_firewall(
            self, server_id,
            current_status='DISABLED', delay=1, max_wait=100, wait_cb=None):
        """Wait while the public network firewall status is current_status

        :param server_id: integer (str or int)

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

        :param delay: time interval between retries

        :max_wait: (int) timeout in secconds

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

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

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

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

360

Stavros Sachtouris's avatar
Stavros Sachtouris committed
361
class CycladesNetworkClient(NetworkClient, Waiter):
362
363
364
365
366
    """Cyclades Network API extentions"""

    network_types = (
        'CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')

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

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

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

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

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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
407
408
        return self._wait(
            net_id, current_status, get_status, delay, max_wait, wait_cb)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
409

Stavros Sachtouris's avatar
Stavros Sachtouris committed
410
411
412
    def wait_port(
            self, port_id,
            current_status='PENDING', delay=1, max_wait=100, wait_cb=None):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
413
414

        def get_status(self, net_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
415
            r = self.get_port_details(port_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
416
417
418
            return r['status'], None

        return self._wait(
Stavros Sachtouris's avatar
Stavros Sachtouris committed
419
            port_id, current_status, get_status, delay, max_wait, wait_cb)