Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (27.1 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
        detail = bool(self['detail'] or self['user_id'])
123
        nets = self.client.list_networks(detail=detail)
124
        nets = self._filter_by_user_id(nets)
125
        nets = self._filter_by_name(nets)
126
        nets = self._filter_by_id(nets)
127
        if detail and not self['detail']:
128
            nets = [dict(
129
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
130
        kwargs = dict()
131
        if self['more']:
132
            kwargs['out'] = StringIO()
133
            kwargs['title'] = ()
134
        self._print(nets, **kwargs)
135
        if self['more']:
136
            pager(kwargs['out'].getvalue())
137

    
138
    def main(self):
139
        super(self.__class__, self)._run()
140
        self._run()
141

    
142

    
143
@command(network_cmds)
144
class network_info(_init_network, _optional_json):
145
    """Get details about a network"""
146

    
147
    @errors.generic.all
148
    @errors.cyclades.connection
149
    @errors.cyclades.network_id
150
    def _run(self, network_id):
151
        net = self.client.get_network_details(network_id)
152
        self._print(net, self.print_dict)
153

    
154
    def main(self, network_id):
155
        super(self.__class__, self)._run()
156
        self._run(network_id=network_id)
157

    
158

    
159
class NetworkTypeArgument(ValueArgument):
160

    
161
    types = ('MAC_FILTERED', 'CUSTOM', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
162

    
163
    @property
164
    def value(self):
165
        return getattr(self, '_value', self.types[0])
166

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

    
176

    
177
@command(network_cmds)
178
class network_create(_init_network, _optional_json):
179
    """Create a new network (default type: MAC_FILTERED)"""
180

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

    
190
    @errors.generic.all
191
    @errors.cyclades.connection
192
    @errors.cyclades.network_type
193
    def _run(self, network_type):
194
        net = self.client.create_network(
195
            network_type, name=self['name'], shared=self['shared'])
196
        self._print(net, self.print_dict)
197

    
198
    def main(self):
199
        super(self.__class__, self)._run()
200
        self._run(network_type=self['network_type'])
201

    
202

    
203
@command(network_cmds)
204
class network_delete(_init_network, _optional_output_cmd):
205
    """Delete a network"""
206

    
207
    @errors.generic.all
208
    @errors.cyclades.connection
209
    @errors.cyclades.network_id
210
    def _run(self, network_id):
211
        r = self.client.delete_network(network_id)
212
        self._optional_output(r)
213

    
214
    def main(self, network_id):
215
        super(self.__class__, self)._run()
216
        self._run(network_id=network_id)
217

    
218

    
219
@command(network_cmds)
220
class network_modify(_init_network, _optional_json):
221
    """Modify network attributes"""
222

    
223
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
224
    required = ['new_name', ]
225

    
226
    @errors.generic.all
227
    @errors.cyclades.connection
228
    @errors.cyclades.network_id
229
    def _run(self, network_id):
230
        r = self.client.update_network(network_id, name=self['new_name'])
231
        self._print(r, self.print_dict)
232

    
233
    def main(self, network_id):
234
        super(self.__class__, self)._run()
235
        self._run(network_id=network_id)
236

    
237

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

    
244
    arguments = dict(
245
        detail=FlagArgument('show detailed output', ('-l', '--details')),
246
        more=FlagArgument(
247
            'output results in pages (-n to set items per page, default 10)',
248
            '--more')
249
    )
250

    
251
    @errors.generic.all
252
    @errors.cyclades.connection
253
    def _run(self):
254
        nets = self.client.list_subnets()
255
        nets = self._filter_by_name(nets)
256
        nets = self._filter_by_id(nets)
257
        if not self['detail']:
258
            nets = [dict(
259
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
260
        kwargs = dict()
261
        if self['more']:
262
            kwargs['out'] = StringIO()
263
            kwargs['title'] = ()
264
        self._print(nets, **kwargs)
265
        if self['more']:
266
            pager(kwargs['out'].getvalue())
267

    
268
    def main(self):
269
        super(self.__class__, self)._run()
270
        self._run()
271

    
272

    
273
@command(subnet_cmds)
274
class subnet_info(_init_network, _optional_json):
275
    """Get details about a subnet"""
276

    
277
    @errors.generic.all
278
    @errors.cyclades.connection
279
    def _run(self, subnet_id):
280
        net = self.client.get_subnet_details(subnet_id)
281
        self._print(net, self.print_dict)
282

    
283
    def main(self, subnet_id):
284
        super(self.__class__, self)._run()
285
        self._run(subnet_id=subnet_id)
286

    
287

    
288
class AllocationPoolArgument(RepeatableArgument):
289

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

    
294
    @value.setter
295
    def value(self, new_pools):
296
        if not new_pools:
297
            return
298
        new_list = []
299
        for pool in new_pools:
300
            start, comma, end = pool.partition(',')
301
            if not (start and comma and end):
302
                raise CLIInvalidArgument(
303
                    'Invalid allocation pool argument %s' % pool, details=[
304
                    'Allocation values must be of the form:',
305
                    '  <start address>,<end address>'])
306
            new_list.append(dict(start=start, end=end))
307
        self._value = new_list
308

    
309

    
310
@command(subnet_cmds)
311
class subnet_create(_init_network, _optional_json):
312
    """Create a new subnet"""
313

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

    
329
    @errors.generic.all
330
    @errors.cyclades.connection
331
    @errors.cyclades.network_id
332
    def _run(self, network_id, cidr):
333
        net = self.client.create_subnet(
334
            network_id, cidr,
335
            self['name'], self['allocation_pools'], self['gateway'],
336
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
337
        self._print(net, self.print_dict)
338

    
339
    def main(self):
340
        super(self.__class__, self)._run()
341
        self._run(network_id=self['network_id'], cidr=self['cidr'])
342

    
343

    
344
# @command(subnet_cmds)
345
# class subnet_delete(_init_network, _optional_output_cmd):
346
#     """Delete a subnet"""
347

    
348
#     @errors.generic.all
349
#     @errors.cyclades.connection
350
#     def _run(self, subnet_id):
351
#         r = self.client.delete_subnet(subnet_id)
352
#         self._optional_output(r)
353

    
354
#     def main(self, subnet_id):
355
#         super(self.__class__, self)._run()
356
#         self._run(subnet_id=subnet_id)
357

    
358

    
359
@command(subnet_cmds)
360
class subnet_modify(_init_network, _optional_json):
361
    """Modify the attributes of a subnet"""
362

    
363
    arguments = dict(
364
        new_name=ValueArgument('New name of the subnet', '--name')
365
    )
366
    required = ['new_name']
367

    
368
    @errors.generic.all
369
    @errors.cyclades.connection
370
    def _run(self, subnet_id):
371
        r = self.client.get_subnet_details(subnet_id)
372
        r = self.client.update_subnet(
373
            subnet_id, r['network_id'], name=self['new_name'])
374
        self._print(r, self.print_dict)
375

    
376
    def main(self, subnet_id):
377
        super(self.__class__, self)._run()
378
        self._run(subnet_id=subnet_id)
379

    
380

    
381
@command(port_cmds)
382
class port_list(_init_network, _optional_json, _name_filter, _id_filter):
383
    """List all ports"""
384

    
385
    arguments = dict(
386
        detail=FlagArgument('show detailed output', ('-l', '--details')),
387
        more=FlagArgument(
388
            'output results in pages (-n to set items per page, default 10)',
389
            '--more'),
390
        user_id=ValueArgument(
391
            'show only networks belonging to user with this id', '--user-id')
392
    )
393

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

    
413
    def main(self):
414
        super(self.__class__, self)._run()
415
        self._run()
416

    
417

    
418
@command(port_cmds)
419
class port_info(_init_network, _optional_json):
420
    """Get details about a port"""
421

    
422
    @errors.generic.all
423
    @errors.cyclades.connection
424
    def _run(self, port_id):
425
        port = self.client.get_port_details(port_id)
426
        self._print(port, self.print_dict)
427

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

    
432

    
433
@command(port_cmds)
434
class port_delete(_init_network, _optional_output_cmd, _port_wait):
435
    """Delete a port (== disconnect server from network)"""
436

    
437
    arguments = dict(
438
        wait=FlagArgument('Wait port to be established', ('-w', '--wait'))
439
    )
440

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

    
456
    def main(self, port_id):
457
        super(self.__class__, self)._run()
458
        self._run(port_id=port_id)
459

    
460

    
461
@command(port_cmds)
462
class port_modify(_init_network, _optional_json):
463
    """Modify the attributes of a port"""
464

    
465
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
466
    required = ['new_name', ]
467

    
468
    @errors.generic.all
469
    @errors.cyclades.connection
470
    def _run(self, port_id):
471
        r = self.client.get_port_details(port_id)
472
        r = self.client.update_port(
473
            port_id, r['network_id'], name=self['new_name'])
474
        self._print(r, self.print_dict)
475

    
476
    def main(self, port_id):
477
        super(self.__class__, self)._run()
478
        self._run(port_id=port_id)
479

    
480

    
481
class PortStatusArgument(ValueArgument):
482

    
483
    valid = ('BUILD', 'ACTIVE', 'DOWN', 'ERROR')
484

    
485
    @property
486
    def value(self):
487
        return getattr(self, '_value', None)
488

    
489
    @value.setter
490
    def value(self, new_status):
491
        if new_status:
492
            new_status = new_status.upper()
493
            if new_status in self.valid:
494
                raise CLIInvalidArgument(
495
                    'Invalid argument %s' % new_status, details=[
496
                    'Status valid values: %s'] % ', '.join(self.valid))
497
            self._value = new_status
498

    
499

    
500
class _port_create(_init_network, _optional_json, _port_wait):
501

    
502
    def connect(self, network_id, device_id):
503
        fixed_ips = [dict(ip_address=self['ip_address'])] if (
504
            self['ip_address']) else None
505
        if fixed_ips and self['subnet_id']:
506
            fixed_ips[0]['subnet_id'] = self['subnet_id']
507
        r = self.client.create_port(
508
            network_id, device_id,
509
            name=self['name'],
510
            security_groups=self['security_group_id'],
511
            fixed_ips=fixed_ips)
512
        if self['wait']:
513
            self._wait(r['id'], r['status'])
514
            r = self.client.get_port_details(r['id'])
515
        self._print([r])
516

    
517

    
518
@command(port_cmds)
519
class port_create(_port_create):
520
    """Create a new port (== connect server to network)"""
521

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

    
540
    @errors.generic.all
541
    @errors.cyclades.connection
542
    @errors.cyclades.network_id
543
    @errors.cyclades.server_id
544
    def _run(self, network_id, server_id):
545
        self.connect(network_id, server_id)
546

    
547
    def main(self):
548
        super(self.__class__, self)._run()
549
        self._run(network_id=self['network_id'], server_id=self['device_id'])
550

    
551

    
552
@command(port_cmds)
553
class port_wait(_init_network, _port_wait):
554
    """Wait for port to finish [ACTIVE, DOWN, BUILD, ERROR]"""
555

    
556
    arguments = dict(
557
        current_status=PortStatusArgument(
558
            'Wait while in this status', '--status'),
559
        timeout=IntArgument(
560
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
561
    )
562

    
563
    @errors.generic.all
564
    @errors.cyclades.connection
565
    def _run(self, port_id, current_status):
566
        port = self.client.get_port_details(port_id)
567
        if port['status'].lower() == current_status.lower():
568
            self._wait(port_id, current_status, timeout=self['timeout'])
569
        else:
570
            self.error(
571
                'Port %s: Cannot wait for status %s, '
572
                'status is already %s' % (
573
                    port_id, current_status, port['status']))
574

    
575
    def main(self, port_id):
576
        super(self.__class__, self)._run()
577
        current_status = self['current_status'] or self.arguments[
578
            'current_status'].valid[0]
579
        self._run(port_id=port_id, current_status=current_status)
580

    
581

    
582
@command(ip_cmds)
583
class ip_list(_init_network, _optional_json):
584
    """List reserved floating IPs"""
585

    
586
    @errors.generic.all
587
    @errors.cyclades.connection
588
    def _run(self):
589
        self._print(self.client.list_floatingips())
590

    
591
    def main(self):
592
        super(self.__class__, self)._run()
593
        self._run()
594

    
595

    
596
@command(ip_cmds)
597
class ip_info(_init_network, _optional_json):
598
    """Get details on a floating IP"""
599

    
600
    @errors.generic.all
601
    @errors.cyclades.connection
602
    def _run(self, ip_id):
603
        self._print(
604
            self.client.get_floatingip_details(ip_id), self.print_dict)
605

    
606
    def main(self, ip_id):
607
        super(self.__class__, self)._run()
608
        self._run(ip_id=ip_id)
609

    
610

    
611
@command(ip_cmds)
612
class ip_create(_init_network, _optional_json):
613
    """Reserve an IP on a network"""
614

    
615
    arguments = dict(
616
        network_id=ValueArgument(
617
            'The network to preserve the IP on', '--network-id'),
618
        ip_address=ValueArgument('Allocate an IP address', '--address')
619
    )
620
    required = ('network_id', )
621

    
622
    @errors.generic.all
623
    @errors.cyclades.connection
624
    @errors.cyclades.network_id
625
    def _run(self, network_id):
626
        self._print(
627
            self.client.create_floatingip(
628
                network_id, floating_ip_address=self['ip_address']),
629
            self.print_dict)
630

    
631
    def main(self):
632
        super(self.__class__, self)._run()
633
        self._run(network_id=self['network_id'])
634

    
635

    
636
@command(ip_cmds)
637
class ip_delete(_init_network, _optional_output_cmd):
638
    """Unreserve an IP (also delete the port, if attached)"""
639

    
640
    def _run(self, ip_id):
641
        self._optional_output(self.client.delete_floatingip(ip_id))
642

    
643
    def main(self, ip_id):
644
        super(self.__class__, self)._run()
645
        self._run(ip_id=ip_id)
646

    
647

    
648
@command(ip_cmds)
649
class ip_attach(_port_create):
650
    """Attach an IP on a virtual server"""
651

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

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

    
687
    def main(self, ip_address):
688
        super(self.__class__, self)._run()
689
        self._run(ip_address=ip_address, server_id=self['server_id'])
690

    
691

    
692
@command(ip_cmds)
693
class ip_detach(_init_network, _port_wait, _optional_json):
694
    """Detach an IP from a virtual server"""
695

    
696
    arguments = dict(
697
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
698
    )
699

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

    
721
    def main(self, ip_address):
722
        super(self.__class__, self)._run()
723
        self._run(ip_address)
724

    
725

    
726
#  Warn users for some importand changes
727

    
728
@command(network_cmds)
729
class network_connect(_port_create):
730
    """Connect a network with a device (server or router)"""
731

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

    
749
    @errors.generic.all
750
    @errors.cyclades.connection
751
    @errors.cyclades.network_id
752
    @errors.cyclades.server_id
753
    def _run(self, network_id, server_id):
754
        self.error('Creating a port to connect network %s with device %s' % (
755
            network_id, server_id))
756
        self.connect(network_id, server_id)
757

    
758
    def main(self, network_id):
759
        super(self.__class__, self)._run()
760
        for sid in self['device_id']:
761
            self._run(network_id=network_id, server_id=sid)
762

    
763

    
764
@command(network_cmds)
765
class network_disconnect(_init_network, _port_wait, _optional_json):
766
    """Disconnect a network from a device"""
767

    
768
    def _cyclades_client(self):
769
        auth = getattr(self, 'auth_base')
770
        endpoints = auth.get_service_endpoints('compute')
771
        URL = endpoints['publicURL']
772
        from kamaki.clients.cyclades import CycladesClient
773
        return CycladesClient(URL, self.client.token)
774

    
775
    arguments = dict(
776
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
777
        device_id=RepeatableArgument(
778
            'Disconnect device from the network (can be repeated)',
779
            '--device-id')
780
    )
781
    required = ('device_id', )
782

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

    
809
    def main(self, network_id):
810
        super(self.__class__, self)._run()
811
        for sid in self['device_id']:
812
            self._run(network_id=network_id, server_id=sid)