network.py 31.3 KB
Newer Older
1
# Copyright 2011-2015 GRNET S.A. All rights reserved.
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
34
35
36
37
#
# 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.

from io import StringIO
from pydoc import pager

from kamaki.cli import command
38
from kamaki.cli.cmdtree import CommandTree
39
from kamaki.cli.errors import CLIInvalidArgument, raiseCLIError
40
from kamaki.clients.cyclades import (
41
    CycladesNetworkClient, ClientError, CycladesComputeClient)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
42
from kamaki.cli.argument import (
43
44
    FlagArgument, ValueArgument, RepeatableArgument, IntArgument,
    StatusArgument)
45
from kamaki.cli.cmds import (
46
    CommandInit, OptionalOutput, NameFilter, IDFilter, errors, client_log)
47
from kamaki.cli.cmds import Wait
48
49


Stavros Sachtouris's avatar
Stavros Sachtouris committed
50
51
52
53
network_cmds = CommandTree('network', 'Network API network commands')
port_cmds = CommandTree('port', 'Network API port commands')
subnet_cmds = CommandTree('subnet', 'Network API subnet commands')
ip_cmds = CommandTree('ip', 'Network API floatingip commands')
54
namespaces = [network_cmds, port_cmds, subnet_cmds, ip_cmds]
55

56
57
port_states = ('BUILD', 'ACTIVE', 'DOWN', 'ERROR')

58

59
class _PortWait(Wait):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
60

61
    def wait_while(self, port_id, current_status, timeout=60):
62
        super(_PortWait, self).wait(
63
            'Port', port_id, self.client.wait_port_while, current_status,
Stavros Sachtouris's avatar
Stavros Sachtouris committed
64
65
            timeout=timeout)

66
    def wait_until(self, port_id, target_status, timeout=60):
67
        super(_PortWait, self).wait(
68
            'Port', port_id, self.client.wait_port_until, target_status,
69
70
            timeout=timeout, msg='not yet')

Stavros Sachtouris's avatar
Stavros Sachtouris committed
71

72
class _NetworkInit(CommandInit):
73
    @errors.Generic.all
74
    @client_log
75
76
    def _run(self):
        self.client = self.get_client(CycladesNetworkClient, 'network')
77

78
79
80
81
    def _filter_by_user_id(self, nets):
        return [net for net in nets if net['user_id'] == self['user_id']] if (
            self['user_id']) else nets

82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    def _get_compute_client(self):
        compute = getattr(self, '_compute_client', None)
        if not compute:
            compute = self.get_client(CycladesComputeClient, 'cyclades')
            self._compute_client = compute
        return compute

    @errors.Cyclades.network_id
    def _network_exists(self, network_id):
        self.client.get_network_details(network_id)

    @errors.Cyclades.server_id
    def _server_exists(self, server_id):
        compute_client = self._get_compute_client()
        compute_client.get_server_details(server_id)

    def _ip_exists(self, ip, network_id, error):
        for ip_item in self.client.list_floatingips():
            if ip_item['floating_ip_address'] == ip:
                if network_id and ip_item['floating_network_id'] != network_id:
                    raiseCLIError(error, details=[
                        'Floating IP %s does not belong to network %s ,' % (
                            ip, network_id),
                        'To get information on IP %s' % ip,
                        '  kamaki ip info %s' % ip_item['id']])
                return
        raiseCLIError(error, details=[
            'Floating IP %s not found' % ip] + errors.Cyclades.about_ips)

111
112
    def main(self):
        self._run()
113
114
115


@command(network_cmds)
116
class network_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter):
117
    """List networks
118
    Use filtering arguments (e.g., --name-like) to manage long lists
119
120
121
122
    """

    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
123
124
125
        more=FlagArgument(
            'output results in pages (-n to set items per page, default 10)',
            '--more'),
126
127
        user_id=ValueArgument(
            'show only networks belonging to user with this id', '--user-id')
128
129
    )

130
131
    @errors.Generic.all
    @errors.Cyclades.connection
132
    def _run(self):
133
        nets = self.client.list_networks(detail=True)
134
        nets = self._filter_by_user_id(nets)
135
136
        nets = self._filter_by_name(nets)
        nets = self._filter_by_id(nets)
137
        if not self['detail']:
138
            nets = [dict(
139
140
141
                id=n['id'],
                name=n['name'],
                public='( %s )' % ('public' if (
142
                    n.get('public', None)) else 'private')) for n in nets]
143
            kwargs = dict(title=('id', 'name', 'public'))
144
145
        else:
            kwargs = dict()
Stavros Sachtouris's avatar
Stavros Sachtouris committed
146
147
148
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
149
        self.print_(nets, **kwargs)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
150
151
        if self['more']:
            pager(kwargs['out'].getvalue())
152
153
154
155

    def main(self):
        super(self.__class__, self)._run()
        self._run()
Stavros Sachtouris's avatar
Stavros Sachtouris committed
156
157
158


@command(network_cmds)
159
class network_info(_NetworkInit, OptionalOutput):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
160
161
    """Get details about a network"""

162
163
164
    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.network_id
Stavros Sachtouris's avatar
Stavros Sachtouris committed
165
166
    def _run(self, network_id):
        net = self.client.get_network_details(network_id)
167
        self.print_(net, self.print_dict)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
168
169
170
171

    def main(self, network_id):
        super(self.__class__, self)._run()
        self._run(network_id=network_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
172
173


174
175
class NetworkTypeArgument(ValueArgument):

176
    types = ('MAC_FILTERED', 'CUSTOM', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
177
178
179

    @property
    def value(self):
180
        return getattr(self, '_value', self.types[0])
181
182
183
184
185
186
187
188
189
190
191

    @value.setter
    def value(self, new_value):
        if new_value and new_value.upper() in self.types:
            self._value = new_value.upper()
        elif new_value:
            raise CLIInvalidArgument(
                'Invalid network type %s' % new_value, details=[
                    'Valid types: %s' % ', '.join(self.types), ])


Stavros Sachtouris's avatar
Stavros Sachtouris committed
192
@command(network_cmds)
193
class network_create(_NetworkInit, OptionalOutput):
194
    """Create a new network (default type: MAC_FILTERED)"""
Stavros Sachtouris's avatar
Stavros Sachtouris committed
195

196
197
198
    arguments = dict(
        name=ValueArgument('Network name', '--name'),
        shared=FlagArgument(
199
            'Make network shared (special privileges required)', '--shared'),
200
        project_id=ValueArgument('Assign network to project', '--project-id'),
201
202
        network_type=NetworkTypeArgument(
            'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
203
            '--type')
Stavros Sachtouris's avatar
Stavros Sachtouris committed
204
205
    )

206
207
    @errors.Generic.all
    @errors.Cyclades.connection
208
    def _run(self):
209
210
211
212
213
214
215
216
217
        try:
            net = self.client.create_network(
                self['network_type'],
                name=self['name'],
                shared=self['shared'],
                project_id=self['project_id'])
        except ClientError as ce:
            if self['project_id'] and ce.status in (400, 403, 404):
                self._project_id_exists(project_id=self['project_id'])
218
            raise
219
        self.print_(net, self.print_dict)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
220

221
    def main(self):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
222
        super(self.__class__, self)._run()
223
        self._run()
224
225


226
@command(network_cmds)
227
class network_reassign(_NetworkInit, OptionalOutput):
228
229
230
231
232
233
    """Assign a network to a different project"""

    arguments = dict(
        project_id=ValueArgument('Assign network to project', '--project-id'),
    )
    required = ('project_id', )
234

235
236
    @errors.Generic.all
    @errors.Cyclades.connection
237
    @errors.Cyclades.network_permissions
238
    @errors.Cyclades.network_id
239
    def _run(self, network_id):
240
241
242
243
244
245
        try:
            self.client.reassign_network(network_id, self['project_id'])
        except ClientError as ce:
            if ce.status in (400, 403, 404):
                self._project_id_exists(project_id=self['project_id'])
            raise
246

247
    def main(self, network_id):
248
        super(self.__class__, self)._run()
249
        self._run(network_id=network_id)
250
251


252
@command(network_cmds)
253
class network_delete(_NetworkInit):
254
255
    """Delete a network"""

256
257
    @errors.Generic.all
    @errors.Cyclades.connection
258
259
    @errors.Cyclades.network_permissions
    @errors.Cyclades.network_in_use
260
    @errors.Cyclades.network_id
261
    def _run(self, network_id):
262
        self.client.delete_network(network_id)
263
264
265
266

    def main(self, network_id):
        super(self.__class__, self)._run()
        self._run(network_id=network_id)
267
268
269


@command(network_cmds)
270
class network_modify(_NetworkInit, OptionalOutput):
271
    """Modify network attributes"""
272

273
274
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
    required = ['new_name', ]
275

276
277
    @errors.Generic.all
    @errors.Cyclades.connection
278
    @errors.Cyclades.network_permissions
279
    @errors.Cyclades.network_id
280
    def _run(self, network_id):
281
        r = self.client.update_network(network_id, name=self['new_name'])
282
        self.print_(r, self.print_dict)
283
284
285
286

    def main(self, network_id):
        super(self.__class__, self)._run()
        self._run(network_id=network_id)
287
288
289


@command(subnet_cmds)
290
class subnet_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter):
291
292
293
294
295
296
    """List subnets
    Use filtering arguments (e.g., --name-like) to manage long server lists
    """

    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
297
        more=FlagArgument('output results in pages', '--more')
298
299
    )

300
301
    @errors.Generic.all
    @errors.Cyclades.connection
302
303
304
305
    def _run(self):
        nets = self.client.list_subnets()
        nets = self._filter_by_name(nets)
        nets = self._filter_by_id(nets)
306
        if not self['detail']:
307
            nets = [dict(
308
309
310
311
                id=n['id'],
                name=n['name'],
                net='( of network %s )' % n['network_id']) for n in nets]
            kwargs = dict(title=('id', 'name', 'net'))
312
313
        else:
            kwargs = dict()
314
315
316
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
317
        self.print_(nets, **kwargs)
318
        if self['more']:
319
            pager('%s' % kwargs['out'].getvalue())
320
321
322
323
324
325
326

    def main(self):
        super(self.__class__, self)._run()
        self._run()


@command(subnet_cmds)
327
class subnet_info(_NetworkInit, OptionalOutput):
328
329
    """Get details about a subnet"""

330
331
    @errors.Generic.all
    @errors.Cyclades.connection
332
    @errors.Cyclades.subnet_id
333
334
    def _run(self, subnet_id):
        net = self.client.get_subnet_details(subnet_id)
335
        self.print_(net, self.print_dict)
336
337
338
339
340
341
342
343
344
345
346
347
348
349

    def main(self, subnet_id):
        super(self.__class__, self)._run()
        self._run(subnet_id=subnet_id)


class AllocationPoolArgument(RepeatableArgument):

    @property
    def value(self):
        return super(AllocationPoolArgument, self).value or []

    @value.setter
    def value(self, new_pools):
350
351
        if not new_pools:
            return
352
353
354
355
356
357
        new_list = []
        for pool in new_pools:
            start, comma, end = pool.partition(',')
            if not (start and comma and end):
                raise CLIInvalidArgument(
                    'Invalid allocation pool argument %s' % pool, details=[
358
359
                        'Allocation values must be of the form:',
                        '  <start address>,<end address>'])
360
361
362
363
364
            new_list.append(dict(start=start, end=end))
        self._value = new_list


@command(subnet_cmds)
365
class subnet_create(_NetworkInit, OptionalOutput):
366
    """Create a new subnet"""
367
368
369
370
371
372
373
374

    arguments = dict(
        name=ValueArgument('Subnet name', '--name'),
        allocation_pools=AllocationPoolArgument(
            'start_address,end_address of allocation pool (can be repeated)'
            ' e.g., --alloc-pool=123.45.67.1,123.45.67.8',
            '--alloc-pool'),
        gateway=ValueArgument('Gateway IP', '--gateway'),
375
        no_gateway=FlagArgument('Do not assign a gateway IP', '--no-gateway'),
376
377
        subnet_id=ValueArgument('The id for the subnet', '--id'),
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
378
379
380
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'),
        network_id=ValueArgument('Set the network ID', '--network-id'),
        cidr=ValueArgument('Set the CIDR', '--cidr')
381
    )
382
    required = ('network_id', 'cidr')
383

384
385
    @errors.Generic.all
    @errors.Cyclades.connection
386
    def _run(self):
387
        gateway = '' if self['no_gateway'] else self['gateway']
388
389
390
        try:
            net = self.client.create_subnet(
                self['network_id'], self['cidr'],
391
                self['name'], self['allocation_pools'], gateway,
392
393
394
395
396
                self['subnet_id'], self['ipv6'], self['enable_dhcp'])
        except ClientError as ce:
            if ce.status in (404, 400):
                self._network_exists(network_id=self['network_id'])
            raise
397
        self.print_(net, self.print_dict)
398

399
    def main(self):
400
        super(self.__class__, self)._run()
401
402
403
404
405
        if self['gateway'] and self['no_gateway']:
            raise CLIInvalidArgument('Conflicting arguments', details=[
                'Arguments %s and %s cannot be used together' % (
                    self.arguments['gateway'].lvalue,
                    self.arguments['no_gateway'].lvalue)])
406
        self._run()
407
408
409


@command(subnet_cmds)
410
class subnet_modify(_NetworkInit, OptionalOutput):
411
    """Modify the attributes of a subnet"""
412

413
414
415
416
    arguments = dict(
        new_name=ValueArgument('New name of the subnet', '--name')
    )
    required = ['new_name']
417

418
419
    @errors.Generic.all
    @errors.Cyclades.connection
420
421
    @errors.Cyclades.subnet_permissions
    @errors.Cyclades.subnet_id
422
    def _run(self, subnet_id):
423
        r = self.client.update_subnet(subnet_id, name=self['new_name'])
424
        self.print_(r, self.print_dict)
425
426
427
428

    def main(self, subnet_id):
        super(self.__class__, self)._run()
        self._run(subnet_id=subnet_id)
429
430
431


@command(port_cmds)
432
class port_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
433
    """List all ports"""
434

435
436
    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
437
        more=FlagArgument('output results in pages', '--more'),
438
439
440
441
        user_id=ValueArgument(
            'show only networks belonging to user with this id', '--user-id')
    )

442
443
    @errors.Generic.all
    @errors.Cyclades.connection
Stavros Sachtouris's avatar
Stavros Sachtouris committed
444
    def _run(self):
445
        ports = self.client.list_ports()
446
447
448
        ports = self._filter_by_user_id(ports)
        ports = self._filter_by_name(ports)
        ports = self._filter_by_id(ports)
449
450
        if not self['detail']:
            ports = [dict(id=p['id'], name=p['name']) for p in ports]
451
452
453
454
        kwargs = dict()
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
455
        self.print_(ports, **kwargs)
456
457
        if self['more']:
            pager(kwargs['out'].getvalue())
458

Stavros Sachtouris's avatar
Stavros Sachtouris committed
459
    def main(self):
460
        super(self.__class__, self)._run()
Stavros Sachtouris's avatar
Stavros Sachtouris committed
461
        self._run()
462
463
464


@command(port_cmds)
465
class port_info(_NetworkInit, OptionalOutput):
466
467
    """Get details about a port"""

468
469
    @errors.Generic.all
    @errors.Cyclades.connection
470
    @errors.Cyclades.port_id
471
    def _run(self, port_id):
472
        port = self.client.get_port_details(port_id)
473
        self.print_(port, self.print_dict)
474
475
476
477
478
479
480

    def main(self, port_id):
        super(self.__class__, self)._run()
        self._run(port_id=port_id)


@command(port_cmds)
481
class port_delete(_NetworkInit, _PortWait):
482
    """Delete a port (== disconnect server from network)"""
483

484
    arguments = dict(
485
        wait=FlagArgument('Wait port to be deleted', ('-w', '--wait'))
486
487
    )

488
489
    @errors.Generic.all
    @errors.Cyclades.connection
490
    @errors.Cyclades.port_id
491
    def _run(self, port_id):
492
493
        if self['wait']:
            status = self.client.get_port_details(port_id)['status']
494
        self.client.delete_port(port_id)
495
        if self['wait']:
496
            try:
497
                self.wait_while(port_id, status)
498
499
500
501
            except ClientError as ce:
                if ce.status not in (404, ):
                    raise
                self.error('Port %s is deleted' % port_id)
502
503
504
505
506
507
508

    def main(self, port_id):
        super(self.__class__, self)._run()
        self._run(port_id=port_id)


@command(port_cmds)
509
class port_modify(_NetworkInit, OptionalOutput):
510
    """Modify the attributes of a port"""
511

512
513
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
    required = ['new_name', ]
514

515
516
    @errors.Generic.all
    @errors.Cyclades.connection
517
    @errors.Cyclades.port_id
518
519
520
    def _run(self, port_id):
        r = self.client.get_port_details(port_id)
        r = self.client.update_port(
521
            port_id, r['network_id'], name=self['new_name'])
522
        self.print_(r, self.print_dict)
523
524
525
526
527
528

    def main(self, port_id):
        super(self.__class__, self)._run()
        self._run(port_id=port_id)


529
class _port_create(_NetworkInit, OptionalOutput, _PortWait):
530

531
532
533
534
    @errors.Cyclades.subnet_id
    def _subnet_exists(self, subnet_id):
        self.client.get_subnet_details(subnet_id)

535
    def connect(self, network_id, device_id):
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
        subnet_id, ip = self['subnet_id'], self['ip_address']
        fixed_ips = [dict(ip_address=ip)] if (ip) else None
        if fixed_ips and subnet_id:
            fixed_ips[0]['subnet_id'] = subnet_id
        try:
            r = self.client.create_port(
                network_id, device_id,
                name=self['name'],
                security_groups=self['security_group_id'],
                fixed_ips=fixed_ips)
        except ClientError as ce:
            if ce.status in (400, 404):
                self._network_exists(network_id=network_id)
                self._server_exists(server_id=device_id)
                if subnet_id:
                    self._subnet_exists(subnet_id=subnet_id)
                if self['ip_address']:
                    self._ip_exists(ip=ip, network_id=network_id, error=ce)
            raise
555
        if self['wait']:
556
            self.wait_while(r['id'], r['status'])
557
            r = self.client.get_port_details(r['id'])
558
        self.print_([r])
559
560


561
@command(port_cmds)
562
class port_create(_port_create):
563
    """Create a new port (== connect server to network)"""
564
565

    arguments = dict(
566
        name=ValueArgument('A human readable name', '--name'),
567
568
        security_group_id=RepeatableArgument(
            'Add a security group id (can be repeated)',
569
570
571
572
            ('-g', '--security-group')),
        subnet_id=ValueArgument(
            'Subnet id for fixed ips (used with --ip-address)',
            '--subnet-id'),
573
        ip_address=ValueArgument('IP address for subnet id', '--ip-address'),
574
575
576
        network_id=ValueArgument('Set the network ID', '--network-id'),
        device_id=ValueArgument(
            'The device is either a virtual server or a virtual router',
Stavros Sachtouris's avatar
Stavros Sachtouris committed
577
578
            '--device-id'),
        wait=FlagArgument('Wait port to be established', ('-w', '--wait')),
579
    )
580
    required = ('network_id', 'device_id')
581

582
583
    @errors.Generic.all
    @errors.Cyclades.connection
584
585
    def _run(self):
        self.connect(self['network_id'], self['device_id'])
586

587
    def main(self):
588
        super(self.__class__, self)._run()
589
        self._run()
590
591


Stavros Sachtouris's avatar
Stavros Sachtouris committed
592
@command(port_cmds)
593
class port_wait(_NetworkInit, _PortWait):
594
    """Wait for port to finish (default: --while BUILD)"""
Stavros Sachtouris's avatar
Stavros Sachtouris committed
595
596
597

    arguments = dict(
        timeout=IntArgument(
598
599
600
601
602
603
604
605
606
607
608
            'Wait limit in seconds (default: 60)', '--timeout', default=60),
        status=StatusArgument(
            'DEPRECATED in next version, equivalent to "--while"', '--status',
            valid_states=port_states),
        status_w=StatusArgument(
            'Wait while in status (%s)' % ','.join(port_states), '--while',
            valid_states=port_states),
        status_u=StatusArgument(
            'Wait until status is reached (%s)' % ','.join(port_states),
            '--until',
            valid_states=port_states),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
609
610
    )

611
612
    @errors.Generic.all
    @errors.Cyclades.connection
613
614
615
616
617
618
619
620
621
    def _run(self, port_id):
        r = self.client.get_port_details(port_id)

        if self['status_u']:
            if r['status'].lower() == self['status_u'].lower():
                self.error('Port %s: already in %s' % (port_id, r['status']))
            else:
                self.wait_until(
                    port_id, self['status_u'], timeout=self['timeout'])
Stavros Sachtouris's avatar
Stavros Sachtouris committed
622
        else:
623
624
625
626
627
            status_w = self['status_w'] or self['status'] or 'BUILD'
            if r['status'].lower() == status_w.lower():
                self.wait_while(port_id, status_w, timeout=self['timeout'])
            else:
                self.error('Port %s status: %s' % (port_id, r['status']))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
628

Stavros Sachtouris's avatar
Stavros Sachtouris committed
629
    def main(self, port_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
630
        super(self.__class__, self)._run()
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647

        status_args = [self['status'], self['status_w'], self['status_u']]
        if len([x for x in status_args if x]) > 1:
            raise CLIInvalidArgument(
                'Invalid argument combination', importance=2, details=[
                    'Arguments %s, %s and %s are mutually exclusive' % (
                        self.arguments['status'].lvalue,
                        self.arguments['status_w'].lvalue,
                        self.arguments['status_u'].lvalue)])
        if self['status']:
            self.error(
                'WARNING: argument %s will be deprecated '
                'in the next version, use %s instead' % (
                    self.arguments['status'].lvalue,
                    self.arguments['status_w'].lvalue))

        self._run(port_id=port_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
648
649


650
@command(ip_cmds)
651
class ip_list(_NetworkInit, OptionalOutput):
652
653
    """List reserved floating IPs"""

654
655
    @errors.Generic.all
    @errors.Cyclades.connection
656
    def _run(self):
657
        self.print_(self.client.list_floatingips())
658
659
660
661
662
663
664

    def main(self):
        super(self.__class__, self)._run()
        self._run()


@command(ip_cmds)
665
class ip_info(_NetworkInit, OptionalOutput):
666
667
    """Get details on a floating IP"""

668
669
    @errors.Generic.all
    @errors.Cyclades.connection
670
    @errors.Cyclades.ip_id
671
    def _run(self, ip_id):
672
        self.print_(self.client.get_floatingip_details(ip_id), self.print_dict)
673
674
675
676
677
678
679

    def main(self, ip_id):
        super(self.__class__, self)._run()
        self._run(ip_id=ip_id)


@command(ip_cmds)
680
class ip_create(_NetworkInit, OptionalOutput):
681
    """Reserve an IP on a network"""
682

683
684
685
    arguments = dict(
        network_id=ValueArgument(
            'The network to preserve the IP on', '--network-id'),
686
687
        ip_address=ValueArgument('Allocate an IP address', '--address'),
        project_id=ValueArgument('Assign the IP to project', '--project-id'),
688
689
    )

690
691
    @errors.Generic.all
    @errors.Cyclades.connection
692
    def _run(self):
693
694
695
696
697
698
699
700
701
702
703
704
705
        try:
            self.print_(
                self.client.create_floatingip(
                    self['network_id'],
                    floating_ip_address=self['ip_address'],
                    project_id=self['project_id']),
                self.print_dict)
        except ClientError as ce:
            if ce.status in (400, 404):
                network_id, ip = self['network_id'], self['ip_address']
                self._network_exists(network_id=network_id)
                if ip:
                    self._ip_exists(ip, network_id, ce)
706
707
            if self['project_id'] and ce.status in (400, 403, 404):
                self._project_id_exists(project_id=self['project_id'])
708
            raise
709
710
711

    def main(self):
        super(self.__class__, self)._run()
712
        self._run()
713
714


715
@command(ip_cmds)
716
class ip_reassign(_NetworkInit):
717
718
719
720
721
722
723
    """Assign a floating IP to a different project"""

    arguments = dict(
        project_id=ValueArgument('Assign the IP to project', '--project-id'),
    )
    required = ('project_id', )

724
725
    @errors.Generic.all
    @errors.Cyclades.connection
726
    def _run(self, ip):
727
728
729
730
731
732
        try:
            self.client.reassign_floating_ip(ip, self['project_id'])
        except ClientError as ce:
            if ce.status in (400, 404):
                self._ip_exists(ip=ip, network_id=None, error=ce)
            raise
733

734
    def main(self, IP):
735
        super(self.__class__, self)._run()
736
        self._run(ip=IP)
737
738


739
@command(ip_cmds)
740
class ip_delete(_NetworkInit):
741
742
    """Unreserve an IP (also delete the port, if attached)"""

743
744
745
    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.ip_id
746
    def _run(self, ip_id):
747
        self.client.delete_floatingip(ip_id)
748
749
750
751
752
753

    def main(self, ip_id):
        super(self.__class__, self)._run()
        self._run(ip_id=ip_id)


Stavros Sachtouris's avatar
Stavros Sachtouris committed
754
755
756
757
758
759
760
761
762
763
764
@command(ip_cmds)
class ip_attach(_port_create):
    """Attach an IP on a virtual server"""

    arguments = dict(
        name=ValueArgument('A human readable name for the port', '--name'),
        security_group_id=RepeatableArgument(
            'Add a security group id (can be repeated)',
            ('-g', '--security-group')),
        subnet_id=ValueArgument('Subnet id', '--subnet-id'),
        wait=FlagArgument('Wait IP to be attached', ('-w', '--wait')),
765
        server_id=ValueArgument('Server to attach to this IP', '--server-id')
Stavros Sachtouris's avatar
Stavros Sachtouris committed
766
767
768
    )
    required = ('server_id', )

769
770
    @errors.Generic.all
    @errors.Cyclades.connection
771
    def _run(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
772
773
        netid = None
        for ip in self.client.list_floatingips():
774
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
775
776
                netid = ip['floating_network_id']
                iparg = ValueArgument(parsed_name='--ip')
777
                iparg.value = ip['floating_ip_address']
Stavros Sachtouris's avatar
Stavros Sachtouris committed
778
779
780
                self.arguments['ip_address'] = iparg
                break
        if netid:
781
            server_id = self['server_id']
Stavros Sachtouris's avatar
Stavros Sachtouris committed
782
            self.error('Creating a port to attach IP %s to server %s' % (
783
                ip_or_ip_id, server_id))
784
785
786
787
788
789
790
791
792
            try:
                self.connect(netid, server_id)
            except ClientError as ce:
                self.error('Failed to connect network %s with server %s' % (
                    netid, server_id))
                if ce.status in (400, 404):
                    self._server_exists(server_id=server_id)
                    self._network_exists(network_id=netid)
                raise
Stavros Sachtouris's avatar
Stavros Sachtouris committed
793
794
        else:
            raiseCLIError(
795
                '%s does not match any reserved IPs or IP ids' % ip_or_ip_id,
796
                details=errors.Cyclades.about_ips)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
797

798
    def main(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
799
        super(self.__class__, self)._run()
800
        self._run(ip_or_ip_id=ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
801
802
803


@command(ip_cmds)
804
class ip_detach(_NetworkInit, _PortWait, OptionalOutput):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
805
806
807
    """Detach an IP from a virtual server"""

    arguments = dict(
808
        wait=FlagArgument('Wait until IP is detached', ('-w', '--wait')),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
809
810
    )

811
812
    @errors.Generic.all
    @errors.Cyclades.connection
813
    def _run(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
814
        for ip in self.client.list_floatingips():
815
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
816
                if not ip['port_id']:
817
                    raiseCLIError('IP %s is not attached' % ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
818
819
820
821
822
823
                self.error('Deleting port %s:' % ip['port_id'])
                self.client.delete_port(ip['port_id'])
                if self['wait']:
                    port_status = self.client.get_port_details(ip['port_id'])[
                        'status']
                    try:
824
                        self.wait_while(ip['port_id'], port_status)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
825
826
827
828
829
                    except ClientError as ce:
                        if ce.status not in (404, ):
                            raise
                        self.error('Port %s is deleted' % ip['port_id'])
                return
830
        raiseCLIError('IP or IP id %s not found' % ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
831

832
    def main(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
833
        super(self.__class__, self)._run()
834
        self._run(ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
835
836


837
@command(network_cmds)
838
839
class network_connect(_port_create):
    """Connect a network with a device (server or router)"""
840

841
    arguments = dict(
842
        name=ValueArgument('A human readable name for the port', '--name'),
843
844
845
846
847
848
849
850
        security_group_id=RepeatableArgument(
            'Add a security group id (can be repeated)',
            ('-g', '--security-group')),
        subnet_id=ValueArgument(
            'Subnet id for fixed ips (used with --ip-address)',
            '--subnet-id'),
        ip_address=ValueArgument(
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
851
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
852
853
854
        device_id=RepeatableArgument(
            'Connect this device to the network (can be repeated)',
            '--device-id')
855
    )
856
    required = ('device_id', )
857

858
859
860
    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.network_id
861
    def _run(self, network_id, server_id):
862
863
        self.error('Creating a port to connect network %s with device %s' % (
            network_id, server_id))
864
865
866
867
868
869
        try:
            self.connect(network_id, server_id)
        except ClientError as ce:
            if ce.status in (400, 404):
                self._server_exists(server_id=server_id)
            raise
870

871
    def main(self, network_id):
872
        super(self.__class__, self)._run()
873
874
        for sid in self['device_id']:
            self._run(network_id=network_id, server_id=sid)
875
876
877


@command(network_cmds)
878
class network_disconnect(_NetworkInit, _PortWait, OptionalOutput):
Dionysis Grigoropoulos's avatar
Dionysis Grigoropoulos committed
879
    """Disconnect a network from a device"""
880

881
    arguments = dict(
882
883
884
885
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
        device_id=RepeatableArgument(
            'Disconnect device from the network (can be repeated)',
            '--device-id')
886
    )
887
    required = ('device_id', )
888

889
890
891
892
    @errors.Cyclades.server_id
    def _get_vm(self, server_id):
        return self._get_compute_client().get_server_details(server_id)

893
894
895
    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.network_id
896
    def _run(self, network_id, server_id):
897
        vm = self._get_vm(server_id=server_id)
898
        ports = [port for port in vm['attachments'] if (
899
            port['network_id'] in (network_id, ))]
900
        if not ports:
901
902
903
904
            raiseCLIError('Device %s has no network %s attached' % (
                server_id, network_id), importance=2, details=[
                    'To get device networking',
                    '  kamaki server info %s --nics' % server_id])
905
906
907
908
909
        for port in ports:
            if self['wait']:
                port['status'] = self.client.get_port_details(port['id'])[
                    'status']
            self.client.delete_port(port['id'])
910
911
            self.error('Deleting port %s (net-id: %s, device-id: %s):' % (
                port['id'], network_id, server_id))
912
            if self['wait']:
913
                try:
914
                    self.wait_while(port['id'], port['status'])
915
916
917
918
                except ClientError as ce:
                    if ce.status not in (404, ):
                        raise
                    self.error('Port %s is deleted' % port['id'])
919

920
    def main(self, network_id):
921
        super(self.__class__, self)._run()
922
923
        for sid in self['device_id']:
            self._run(network_id=network_id, server_id=sid)