Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / network.py @ c77131f4

History | View | Annotate | Download (27.3 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 io import StringIO
35
from pydoc import pager
36

    
37
from kamaki.cli import command
38
from kamaki.cli.command_tree import CommandTree
39
from kamaki.cli.errors import (
40
    CLIBaseUrlError, CLIInvalidArgument, raiseCLIError)
41
from kamaki.clients.cyclades import CycladesNetworkClient, ClientError
42
from kamaki.cli.argument import (
43
    FlagArgument, ValueArgument, RepeatableArgument, IntArgument)
44
from kamaki.cli.commands import _command_init, errors, addLogSettings
45
from kamaki.cli.commands import (
46
    _optional_output_cmd, _optional_json, _name_filter, _id_filter)
47
from kamaki.cli.commands.cyclades import _service_wait
48

    
49

    
50
network_cmds = CommandTree('network', 'Networking API network commands')
51
port_cmds = CommandTree('port', 'Networking API network commands')
52
subnet_cmds = CommandTree('subnet', 'Networking API network commands')
53
ip_cmds = CommandTree('ip', 'Networking API floatingip commands')
54
_commands = [network_cmds, port_cmds, subnet_cmds, ip_cmds]
55

    
56

    
57
about_authentication = '\nUser Authentication:\
58
    \n  to check authentication: [kamaki] ]user authenticate\
59
    \n  to set authentication token: \
60
    [kamaki] config set cloud.<CLOUD>.token <TOKEN>'
61

    
62

    
63
class _port_wait(_service_wait):
64

    
65
    def _wait(self, port_id, current_status, timeout=60):
66
        super(_port_wait, self)._wait(
67
            'Port', port_id, self.client.wait_port, current_status,
68
            timeout=timeout)
69

    
70

    
71
class _init_network(_command_init):
72
    @errors.generic.all
73
    @addLogSettings
74
    def _run(self, service='network'):
75
        if getattr(self, 'cloud', None):
76
            base_url = self._custom_url(service) or self._custom_url(
77
                'network')
78
            if base_url:
79
                token = self._custom_token(service) or self._custom_token(
80
                    'network') or self.config.get_cloud('token')
81
                self.client = CycladesNetworkClient(
82
                  base_url=base_url, token=token)
83
                return
84
        else:
85
            self.cloud = 'default'
86
        if getattr(self, 'auth_base', False):
87
            network_endpoints = self.auth_base.get_service_endpoints(
88
                self._custom_type('network') or 'network',
89
                self._custom_version('network') or '')
90
            base_url = network_endpoints['publicURL']
91
            token = self.auth_base.token
92
            self.client = CycladesNetworkClient(base_url=base_url, token=token)
93
        else:
94
            raise CLIBaseUrlError(service='network')
95

    
96
    def _filter_by_user_id(self, nets):
97
        return [net for net in nets if net['user_id'] == self['user_id']] if (
98
            self['user_id']) else nets
99

    
100
    def main(self):
101
        self._run()
102

    
103

    
104
@command(network_cmds)
105
class network_list(_init_network, _optional_json, _name_filter, _id_filter):
106
    """List networks
107
    Use filtering arguments (e.g., --name-like) to manage long server lists
108
    """
109

    
110
    arguments = dict(
111
        detail=FlagArgument('show detailed output', ('-l', '--details')),
112
        more=FlagArgument(
113
            'output results in pages (-n to set items per page, default 10)',
114
            '--more'),
115
        user_id=ValueArgument(
116
            'show only networks belonging to user with this id', '--user-id')
117
    )
118

    
119
    @errors.generic.all
120
    @errors.cyclades.connection
121
    def _run(self):
122
        nets = self.client.list_networks(detail=True)
123
        nets = self._filter_by_user_id(nets)
124
        nets = self._filter_by_name(nets)
125
        nets = self._filter_by_id(nets)
126
        if not self['detail']:
127
            nets = [dict(
128
                _0_id=n['id'],
129
                _1_name=n['name'],
130
                _2_public='( %s )' % 'public' if (
131
                    n.get('public', None)) else 'private') for n in nets]
132
            kwargs = dict(title=('_0_id', '_1_name', '_2_public'))
133
        else:
134
            kwargs = dict()
135
        if self['more']:
136
            kwargs['out'] = StringIO()
137
            kwargs['title'] = ()
138
        self._print(nets, **kwargs)
139
        if self['more']:
140
            pager(kwargs['out'].getvalue())
141

    
142
    def main(self):
143
        super(self.__class__, self)._run()
144
        self._run()
145

    
146

    
147
@command(network_cmds)
148
class network_info(_init_network, _optional_json):
149
    """Get details about a network"""
150

    
151
    @errors.generic.all
152
    @errors.cyclades.connection
153
    @errors.cyclades.network_id
154
    def _run(self, network_id):
155
        net = self.client.get_network_details(network_id)
156
        self._print(net, self.print_dict)
157

    
158
    def main(self, network_id):
159
        super(self.__class__, self)._run()
160
        self._run(network_id=network_id)
161

    
162

    
163
class NetworkTypeArgument(ValueArgument):
164

    
165
    types = ('MAC_FILTERED', 'CUSTOM', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
166

    
167
    @property
168
    def value(self):
169
        return getattr(self, '_value', self.types[0])
170

    
171
    @value.setter
172
    def value(self, new_value):
173
        if new_value and new_value.upper() in self.types:
174
            self._value = new_value.upper()
175
        elif new_value:
176
            raise CLIInvalidArgument(
177
                'Invalid network type %s' % new_value, details=[
178
                    'Valid types: %s' % ', '.join(self.types), ])
179

    
180

    
181
@command(network_cmds)
182
class network_create(_init_network, _optional_json):
183
    """Create a new network (default type: MAC_FILTERED)"""
184

    
185
    arguments = dict(
186
        name=ValueArgument('Network name', '--name'),
187
        shared=FlagArgument(
188
            'Make network shared (special privileges required)', '--shared'),
189
        network_type=NetworkTypeArgument(
190
            'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
191
            '--type')
192
    )
193

    
194
    @errors.generic.all
195
    @errors.cyclades.connection
196
    @errors.cyclades.network_type
197
    def _run(self, network_type):
198
        net = self.client.create_network(
199
            network_type, name=self['name'], shared=self['shared'])
200
        self._print(net, self.print_dict)
201

    
202
    def main(self):
203
        super(self.__class__, self)._run()
204
        self._run(network_type=self['network_type'])
205

    
206

    
207
@command(network_cmds)
208
class network_delete(_init_network, _optional_output_cmd):
209
    """Delete a network"""
210

    
211
    @errors.generic.all
212
    @errors.cyclades.connection
213
    @errors.cyclades.network_id
214
    def _run(self, network_id):
215
        r = self.client.delete_network(network_id)
216
        self._optional_output(r)
217

    
218
    def main(self, network_id):
219
        super(self.__class__, self)._run()
220
        self._run(network_id=network_id)
221

    
222

    
223
@command(network_cmds)
224
class network_modify(_init_network, _optional_json):
225
    """Modify network attributes"""
226

    
227
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
228
    required = ['new_name', ]
229

    
230
    @errors.generic.all
231
    @errors.cyclades.connection
232
    @errors.cyclades.network_id
233
    def _run(self, network_id):
234
        r = self.client.update_network(network_id, name=self['new_name'])
235
        self._print(r, self.print_dict)
236

    
237
    def main(self, network_id):
238
        super(self.__class__, self)._run()
239
        self._run(network_id=network_id)
240

    
241

    
242
@command(subnet_cmds)
243
class subnet_list(_init_network, _optional_json, _name_filter, _id_filter):
244
    """List subnets
245
    Use filtering arguments (e.g., --name-like) to manage long server lists
246
    """
247

    
248
    arguments = dict(
249
        detail=FlagArgument('show detailed output', ('-l', '--details')),
250
        more=FlagArgument(
251
            'output results in pages (-n to set items per page, default 10)',
252
            '--more')
253
    )
254

    
255
    @errors.generic.all
256
    @errors.cyclades.connection
257
    def _run(self):
258
        nets = self.client.list_subnets()
259
        nets = self._filter_by_name(nets)
260
        nets = self._filter_by_id(nets)
261
        if not self['detail']:
262
            nets = [dict(
263
                _0_id=n['id'],
264
                _1_name=n['name'],
265
                _2_net='( of network %s )' % n['network_id']) for n in nets]
266
            kwargs = dict(title=('_0_id', '_1_name', '_2_net'))
267
        else:
268
            kwargs = dict()
269
        if self['more']:
270
            kwargs['out'] = StringIO()
271
            kwargs['title'] = ()
272
        self._print(nets, **kwargs)
273
        if self['more']:
274
            pager(kwargs['out'].getvalue())
275

    
276
    def main(self):
277
        super(self.__class__, self)._run()
278
        self._run()
279

    
280

    
281
@command(subnet_cmds)
282
class subnet_info(_init_network, _optional_json):
283
    """Get details about a subnet"""
284

    
285
    @errors.generic.all
286
    @errors.cyclades.connection
287
    def _run(self, subnet_id):
288
        net = self.client.get_subnet_details(subnet_id)
289
        self._print(net, self.print_dict)
290

    
291
    def main(self, subnet_id):
292
        super(self.__class__, self)._run()
293
        self._run(subnet_id=subnet_id)
294

    
295

    
296
class AllocationPoolArgument(RepeatableArgument):
297

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

    
302
    @value.setter
303
    def value(self, new_pools):
304
        if not new_pools:
305
            return
306
        new_list = []
307
        for pool in new_pools:
308
            start, comma, end = pool.partition(',')
309
            if not (start and comma and end):
310
                raise CLIInvalidArgument(
311
                    'Invalid allocation pool argument %s' % pool, details=[
312
                    'Allocation values must be of the form:',
313
                    '  <start address>,<end address>'])
314
            new_list.append(dict(start=start, end=end))
315
        self._value = new_list
316

    
317

    
318
@command(subnet_cmds)
319
class subnet_create(_init_network, _optional_json):
320
    """Create a new subnet"""
321

    
322
    arguments = dict(
323
        name=ValueArgument('Subnet name', '--name'),
324
        allocation_pools=AllocationPoolArgument(
325
            'start_address,end_address of allocation pool (can be repeated)'
326
            ' e.g., --alloc-pool=123.45.67.1,123.45.67.8',
327
            '--alloc-pool'),
328
        gateway=ValueArgument('Gateway IP', '--gateway'),
329
        subnet_id=ValueArgument('The id for the subnet', '--id'),
330
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
331
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'),
332
        network_id=ValueArgument('Set the network ID', '--network-id'),
333
        cidr=ValueArgument('Set the CIDR', '--cidr')
334
    )
335
    required = ('network_id', 'cidr')
336

    
337
    @errors.generic.all
338
    @errors.cyclades.connection
339
    @errors.cyclades.network_id
340
    def _run(self, network_id, cidr):
341
        net = self.client.create_subnet(
342
            network_id, cidr,
343
            self['name'], self['allocation_pools'], self['gateway'],
344
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
345
        self._print(net, self.print_dict)
346

    
347
    def main(self):
348
        super(self.__class__, self)._run()
349
        self._run(network_id=self['network_id'], cidr=self['cidr'])
350

    
351

    
352
# @command(subnet_cmds)
353
# class subnet_delete(_init_network, _optional_output_cmd):
354
#     """Delete a subnet"""
355

    
356
#     @errors.generic.all
357
#     @errors.cyclades.connection
358
#     def _run(self, subnet_id):
359
#         r = self.client.delete_subnet(subnet_id)
360
#         self._optional_output(r)
361

    
362
#     def main(self, subnet_id):
363
#         super(self.__class__, self)._run()
364
#         self._run(subnet_id=subnet_id)
365

    
366

    
367
@command(subnet_cmds)
368
class subnet_modify(_init_network, _optional_json):
369
    """Modify the attributes of a subnet"""
370

    
371
    arguments = dict(
372
        new_name=ValueArgument('New name of the subnet', '--name')
373
    )
374
    required = ['new_name']
375

    
376
    @errors.generic.all
377
    @errors.cyclades.connection
378
    def _run(self, subnet_id):
379
        r = self.client.update_subnet(subnet_id, name=self['new_name'])
380
        self._print(r, self.print_dict)
381

    
382
    def main(self, subnet_id):
383
        super(self.__class__, self)._run()
384
        self._run(subnet_id=subnet_id)
385

    
386

    
387
@command(port_cmds)
388
class port_list(_init_network, _optional_json, _name_filter, _id_filter):
389
    """List all ports"""
390

    
391
    arguments = dict(
392
        detail=FlagArgument('show detailed output', ('-l', '--details')),
393
        more=FlagArgument(
394
            'output results in pages (-n to set items per page, default 10)',
395
            '--more'),
396
        user_id=ValueArgument(
397
            'show only networks belonging to user with this id', '--user-id')
398
    )
399

    
400
    @errors.generic.all
401
    @errors.cyclades.connection
402
    def _run(self):
403
        detail = bool(self['detail'] or self['user_id'])
404
        ports = self.client.list_ports(detail=detail)
405
        ports = self._filter_by_user_id(ports)
406
        ports = self._filter_by_name(ports)
407
        ports = self._filter_by_id(ports)
408
        if detail and not self['detail']:
409
            ports = [dict(
410
                id=p['id'], name=p['name'], links=p['links']) for p in ports]
411
        kwargs = dict()
412
        if self['more']:
413
            kwargs['out'] = StringIO()
414
            kwargs['title'] = ()
415
        self._print(ports, **kwargs)
416
        if self['more']:
417
            pager(kwargs['out'].getvalue())
418

    
419
    def main(self):
420
        super(self.__class__, self)._run()
421
        self._run()
422

    
423

    
424
@command(port_cmds)
425
class port_info(_init_network, _optional_json):
426
    """Get details about a port"""
427

    
428
    @errors.generic.all
429
    @errors.cyclades.connection
430
    def _run(self, port_id):
431
        port = self.client.get_port_details(port_id)
432
        self._print(port, self.print_dict)
433

    
434
    def main(self, port_id):
435
        super(self.__class__, self)._run()
436
        self._run(port_id=port_id)
437

    
438

    
439
@command(port_cmds)
440
class port_delete(_init_network, _optional_output_cmd, _port_wait):
441
    """Delete a port (== disconnect server from network)"""
442

    
443
    arguments = dict(
444
        wait=FlagArgument('Wait port to be established', ('-w', '--wait'))
445
    )
446

    
447
    @errors.generic.all
448
    @errors.cyclades.connection
449
    def _run(self, port_id):
450
        if self['wait']:
451
            status = self.client.get_port_details(port_id)['status']
452
        r = self.client.delete_port(port_id)
453
        if self['wait']:
454
            try:
455
                self._wait(port_id, status)
456
            except ClientError as ce:
457
                if ce.status not in (404, ):
458
                    raise
459
                self.error('Port %s is deleted' % port_id)
460
        self._optional_output(r)
461

    
462
    def main(self, port_id):
463
        super(self.__class__, self)._run()
464
        self._run(port_id=port_id)
465

    
466

    
467
@command(port_cmds)
468
class port_modify(_init_network, _optional_json):
469
    """Modify the attributes of a port"""
470

    
471
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
472
    required = ['new_name', ]
473

    
474
    @errors.generic.all
475
    @errors.cyclades.connection
476
    def _run(self, port_id):
477
        r = self.client.get_port_details(port_id)
478
        r = self.client.update_port(
479
            port_id, r['network_id'], name=self['new_name'])
480
        self._print(r, self.print_dict)
481

    
482
    def main(self, port_id):
483
        super(self.__class__, self)._run()
484
        self._run(port_id=port_id)
485

    
486

    
487
class PortStatusArgument(ValueArgument):
488

    
489
    valid = ('BUILD', 'ACTIVE', 'DOWN', 'ERROR')
490

    
491
    @property
492
    def value(self):
493
        return getattr(self, '_value', None)
494

    
495
    @value.setter
496
    def value(self, new_status):
497
        if new_status:
498
            new_status = new_status.upper()
499
            if new_status in self.valid:
500
                raise CLIInvalidArgument(
501
                    'Invalid argument %s' % new_status, details=[
502
                    'Status valid values: %s'] % ', '.join(self.valid))
503
            self._value = new_status
504

    
505

    
506
class _port_create(_init_network, _optional_json, _port_wait):
507

    
508
    def connect(self, network_id, device_id):
509
        fixed_ips = [dict(ip_address=self['ip_address'])] if (
510
            self['ip_address']) else None
511
        if fixed_ips and self['subnet_id']:
512
            fixed_ips[0]['subnet_id'] = self['subnet_id']
513
        r = self.client.create_port(
514
            network_id, device_id,
515
            name=self['name'],
516
            security_groups=self['security_group_id'],
517
            fixed_ips=fixed_ips)
518
        if self['wait']:
519
            self._wait(r['id'], r['status'])
520
            r = self.client.get_port_details(r['id'])
521
        self._print([r])
522

    
523

    
524
@command(port_cmds)
525
class port_create(_port_create):
526
    """Create a new port (== connect server to network)"""
527

    
528
    arguments = dict(
529
        name=ValueArgument('A human readable name', '--name'),
530
        security_group_id=RepeatableArgument(
531
            'Add a security group id (can be repeated)',
532
            ('-g', '--security-group')),
533
        subnet_id=ValueArgument(
534
            'Subnet id for fixed ips (used with --ip-address)',
535
            '--subnet-id'),
536
        ip_address=ValueArgument(
537
            'IP address for subnet id', '--ip-address'),
538
        network_id=ValueArgument('Set the network ID', '--network-id'),
539
        device_id=ValueArgument(
540
            'The device is either a virtual server or a virtual router',
541
            '--device-id'),
542
        wait=FlagArgument('Wait port to be established', ('-w', '--wait')),
543
    )
544
    required = ('network_id', 'device_id')
545

    
546
    @errors.generic.all
547
    @errors.cyclades.connection
548
    @errors.cyclades.network_id
549
    @errors.cyclades.server_id
550
    def _run(self, network_id, server_id):
551
        self.connect(network_id, server_id)
552

    
553
    def main(self):
554
        super(self.__class__, self)._run()
555
        self._run(network_id=self['network_id'], server_id=self['device_id'])
556

    
557

    
558
@command(port_cmds)
559
class port_wait(_init_network, _port_wait):
560
    """Wait for port to finish [ACTIVE, DOWN, BUILD, ERROR]"""
561

    
562
    arguments = dict(
563
        current_status=PortStatusArgument(
564
            'Wait while in this status', '--status'),
565
        timeout=IntArgument(
566
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
567
    )
568

    
569
    @errors.generic.all
570
    @errors.cyclades.connection
571
    def _run(self, port_id, current_status):
572
        port = self.client.get_port_details(port_id)
573
        if port['status'].lower() == current_status.lower():
574
            self._wait(port_id, current_status, timeout=self['timeout'])
575
        else:
576
            self.error(
577
                'Port %s: Cannot wait for status %s, '
578
                'status is already %s' % (
579
                    port_id, current_status, port['status']))
580

    
581
    def main(self, port_id):
582
        super(self.__class__, self)._run()
583
        current_status = self['current_status'] or self.arguments[
584
            'current_status'].valid[0]
585
        self._run(port_id=port_id, current_status=current_status)
586

    
587

    
588
@command(ip_cmds)
589
class ip_list(_init_network, _optional_json):
590
    """List reserved floating IPs"""
591

    
592
    @errors.generic.all
593
    @errors.cyclades.connection
594
    def _run(self):
595
        self._print(self.client.list_floatingips())
596

    
597
    def main(self):
598
        super(self.__class__, self)._run()
599
        self._run()
600

    
601

    
602
@command(ip_cmds)
603
class ip_info(_init_network, _optional_json):
604
    """Get details on a floating IP"""
605

    
606
    @errors.generic.all
607
    @errors.cyclades.connection
608
    def _run(self, ip_id):
609
        self._print(
610
            self.client.get_floatingip_details(ip_id), self.print_dict)
611

    
612
    def main(self, ip_id):
613
        super(self.__class__, self)._run()
614
        self._run(ip_id=ip_id)
615

    
616

    
617
@command(ip_cmds)
618
class ip_create(_init_network, _optional_json):
619
    """Reserve an IP on a network"""
620

    
621
    arguments = dict(
622
        network_id=ValueArgument(
623
            'The network to preserve the IP on', '--network-id'),
624
        ip_address=ValueArgument('Allocate an IP address', '--address')
625
    )
626
    required = ('network_id', )
627

    
628
    @errors.generic.all
629
    @errors.cyclades.connection
630
    @errors.cyclades.network_id
631
    def _run(self, network_id):
632
        self._print(
633
            self.client.create_floatingip(
634
                network_id, floating_ip_address=self['ip_address']),
635
            self.print_dict)
636

    
637
    def main(self):
638
        super(self.__class__, self)._run()
639
        self._run(network_id=self['network_id'])
640

    
641

    
642
@command(ip_cmds)
643
class ip_delete(_init_network, _optional_output_cmd):
644
    """Unreserve an IP (also delete the port, if attached)"""
645

    
646
    def _run(self, ip_id):
647
        self._optional_output(self.client.delete_floatingip(ip_id))
648

    
649
    def main(self, ip_id):
650
        super(self.__class__, self)._run()
651
        self._run(ip_id=ip_id)
652

    
653

    
654
@command(ip_cmds)
655
class ip_attach(_port_create):
656
    """Attach an IP on a virtual server"""
657

    
658
    arguments = dict(
659
        name=ValueArgument('A human readable name for the port', '--name'),
660
        security_group_id=RepeatableArgument(
661
            'Add a security group id (can be repeated)',
662
            ('-g', '--security-group')),
663
        subnet_id=ValueArgument('Subnet id', '--subnet-id'),
664
        wait=FlagArgument('Wait IP to be attached', ('-w', '--wait')),
665
        server_id=ValueArgument(
666
            'Server to attach to this IP', '--server-id')
667
    )
668
    required = ('server_id', )
669

    
670
    @errors.generic.all
671
    @errors.cyclades.connection
672
    @errors.cyclades.server_id
673
    def _run(self, ip_address, server_id):
674
        netid = None
675
        for ip in self.client.list_floatingips():
676
            if ip['floating_ip_address'] == ip_address:
677
                netid = ip['floating_network_id']
678
                iparg = ValueArgument(parsed_name='--ip')
679
                iparg.value = ip_address
680
                self.arguments['ip_address'] = iparg
681
                break
682
        if netid:
683
            self.error('Creating a port to attach IP %s to server %s' % (
684
                ip_address, server_id))
685
            self.connect(netid, server_id)
686
        else:
687
            raiseCLIError(
688
                'IP address %s does not match any reserved IPs' % ip_address,
689
                details=[
690
                    'To reserve an IP:', '  [kamaki] ip create',
691
                    'To see all reserved IPs:', '  [kamaki] ip list'])
692

    
693
    def main(self, ip_address):
694
        super(self.__class__, self)._run()
695
        self._run(ip_address=ip_address, server_id=self['server_id'])
696

    
697

    
698
@command(ip_cmds)
699
class ip_detach(_init_network, _port_wait, _optional_json):
700
    """Detach an IP from a virtual server"""
701

    
702
    arguments = dict(
703
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
704
    )
705

    
706
    @errors.generic.all
707
    @errors.cyclades.connection
708
    def _run(self, ip_address):
709
        for ip in self.client.list_floatingips():
710
            if ip['floating_ip_address'] == ip_address:
711
                if not ip['port_id']:
712
                    raiseCLIError('IP %s is not attached' % ip_address)
713
                self.error('Deleting port %s:' % ip['port_id'])
714
                self.client.delete_port(ip['port_id'])
715
                if self['wait']:
716
                    port_status = self.client.get_port_details(ip['port_id'])[
717
                        'status']
718
                    try:
719
                        self._wait(ip['port_id'], port_status)
720
                    except ClientError as ce:
721
                        if ce.status not in (404, ):
722
                            raise
723
                        self.error('Port %s is deleted' % ip['port_id'])
724
                return
725
        raiseCLIError('IP %s not found' % ip_address)
726

    
727
    def main(self, ip_address):
728
        super(self.__class__, self)._run()
729
        self._run(ip_address)
730

    
731

    
732
#  Warn users for some importand changes
733

    
734
@command(network_cmds)
735
class network_connect(_port_create):
736
    """Connect a network with a device (server or router)"""
737

    
738
    arguments = dict(
739
        name=ValueArgument('A human readable name for the port', '--name'),
740
        security_group_id=RepeatableArgument(
741
            'Add a security group id (can be repeated)',
742
            ('-g', '--security-group')),
743
        subnet_id=ValueArgument(
744
            'Subnet id for fixed ips (used with --ip-address)',
745
            '--subnet-id'),
746
        ip_address=ValueArgument(
747
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
748
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
749
        device_id=RepeatableArgument(
750
            'Connect this device to the network (can be repeated)',
751
            '--device-id')
752
    )
753
    required = ('device_id', )
754

    
755
    @errors.generic.all
756
    @errors.cyclades.connection
757
    @errors.cyclades.network_id
758
    @errors.cyclades.server_id
759
    def _run(self, network_id, server_id):
760
        self.error('Creating a port to connect network %s with device %s' % (
761
            network_id, server_id))
762
        self.connect(network_id, server_id)
763

    
764
    def main(self, network_id):
765
        super(self.__class__, self)._run()
766
        for sid in self['device_id']:
767
            self._run(network_id=network_id, server_id=sid)
768

    
769

    
770
@command(network_cmds)
771
class network_disconnect(_init_network, _port_wait, _optional_json):
772
    """Disconnect a network from a device"""
773

    
774
    def _cyclades_client(self):
775
        auth = getattr(self, 'auth_base')
776
        endpoints = auth.get_service_endpoints('compute')
777
        URL = endpoints['publicURL']
778
        from kamaki.clients.cyclades import CycladesClient
779
        return CycladesClient(URL, self.client.token)
780

    
781
    arguments = dict(
782
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
783
        device_id=RepeatableArgument(
784
            'Disconnect device from the network (can be repeated)',
785
            '--device-id')
786
    )
787
    required = ('device_id', )
788

    
789
    @errors.generic.all
790
    @errors.cyclades.connection
791
    @errors.cyclades.network_id
792
    @errors.cyclades.server_id
793
    def _run(self, network_id, server_id):
794
        vm = self._cyclades_client().get_server_details(server_id)
795
        ports = [port for port in vm['attachments'] if (
796
            port['network_id'] in (network_id, ))]
797
        if not ports:
798
            raiseCLIError('Network %s is not connected to device %s' % (
799
                network_id, server_id))
800
        for port in ports:
801
            if self['wait']:
802
                port['status'] = self.client.get_port_details(port['id'])[
803
                    'status']
804
            self.client.delete_port(port['id'])
805
            self.error('Deleting port %s:' % port['id'])
806
            self.print_dict(port)
807
            if self['wait']:
808
                try:
809
                    self._wait(port['id'], port['status'])
810
                except ClientError as ce:
811
                    if ce.status not in (404, ):
812
                        raise
813
                    self.error('Port %s is deleted' % port['id'])
814

    
815
    def main(self, network_id):
816
        super(self.__class__, self)._run()
817
        for sid in self['device_id']:
818
            self._run(network_id=network_id, server_id=sid)