Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / network.py @ 7fbda4e5

History | View | Annotate | Download (26.9 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
    StatusArgument)
45
from kamaki.cli.commands import _command_init, errors, addLogSettings
46
from kamaki.cli.commands import (
47
    _optional_output_cmd, _optional_json, _name_filter, _id_filter)
48
from kamaki.cli.commands.cyclades import _service_wait
49

    
50

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

    
57

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

    
63
port_states = ('BUILD', 'ACTIVE', 'DOWN', 'ERROR')
64

    
65

    
66
class _port_wait(_service_wait):
67

    
68
    def _wait(self, port_id, current_status, timeout=60):
69
        super(_port_wait, self)._wait(
70
            'Port', port_id, self.client.wait_port, current_status,
71
            timeout=timeout)
72

    
73

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

    
99
    def _filter_by_user_id(self, nets):
100
        return [net for net in nets if net['user_id'] == self['user_id']] if (
101
            self['user_id']) else nets
102

    
103
    def main(self):
104
        self._run()
105

    
106

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

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

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

    
145
    def main(self):
146
        super(self.__class__, self)._run()
147
        self._run()
148

    
149

    
150
@command(network_cmds)
151
class network_info(_init_network, _optional_json):
152
    """Get details about a network"""
153

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

    
161
    def main(self, network_id):
162
        super(self.__class__, self)._run()
163
        self._run(network_id=network_id)
164

    
165

    
166
class NetworkTypeArgument(ValueArgument):
167

    
168
    types = ('MAC_FILTERED', 'CUSTOM', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
169

    
170
    @property
171
    def value(self):
172
        return getattr(self, '_value', self.types[0])
173

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

    
183

    
184
@command(network_cmds)
185
class network_create(_init_network, _optional_json):
186
    """Create a new network (default type: MAC_FILTERED)"""
187

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

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

    
205
    def main(self):
206
        super(self.__class__, self)._run()
207
        self._run(network_type=self['network_type'])
208

    
209

    
210
@command(network_cmds)
211
class network_delete(_init_network, _optional_output_cmd):
212
    """Delete a network"""
213

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

    
221
    def main(self, network_id):
222
        super(self.__class__, self)._run()
223
        self._run(network_id=network_id)
224

    
225

    
226
@command(network_cmds)
227
class network_modify(_init_network, _optional_json):
228
    """Modify network attributes"""
229

    
230
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
231
    required = ['new_name', ]
232

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

    
240
    def main(self, network_id):
241
        super(self.__class__, self)._run()
242
        self._run(network_id=network_id)
243

    
244

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

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

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

    
279
    def main(self):
280
        super(self.__class__, self)._run()
281
        self._run()
282

    
283

    
284
@command(subnet_cmds)
285
class subnet_info(_init_network, _optional_json):
286
    """Get details about a subnet"""
287

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

    
294
    def main(self, subnet_id):
295
        super(self.__class__, self)._run()
296
        self._run(subnet_id=subnet_id)
297

    
298

    
299
class AllocationPoolArgument(RepeatableArgument):
300

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

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

    
320

    
321
@command(subnet_cmds)
322
class subnet_create(_init_network, _optional_json):
323
    """Create a new subnet"""
324

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

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

    
350
    def main(self):
351
        super(self.__class__, self)._run()
352
        self._run(network_id=self['network_id'], cidr=self['cidr'])
353

    
354

    
355
# @command(subnet_cmds)
356
# class subnet_delete(_init_network, _optional_output_cmd):
357
#     """Delete a subnet"""
358

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

    
365
#     def main(self, subnet_id):
366
#         super(self.__class__, self)._run()
367
#         self._run(subnet_id=subnet_id)
368

    
369

    
370
@command(subnet_cmds)
371
class subnet_modify(_init_network, _optional_json):
372
    """Modify the attributes of a subnet"""
373

    
374
    arguments = dict(
375
        new_name=ValueArgument('New name of the subnet', '--name')
376
    )
377
    required = ['new_name']
378

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

    
385
    def main(self, subnet_id):
386
        super(self.__class__, self)._run()
387
        self._run(subnet_id=subnet_id)
388

    
389

    
390
@command(port_cmds)
391
class port_list(_init_network, _optional_json, _name_filter, _id_filter):
392
    """List all ports"""
393

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

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

    
422
    def main(self):
423
        super(self.__class__, self)._run()
424
        self._run()
425

    
426

    
427
@command(port_cmds)
428
class port_info(_init_network, _optional_json):
429
    """Get details about a port"""
430

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

    
437
    def main(self, port_id):
438
        super(self.__class__, self)._run()
439
        self._run(port_id=port_id)
440

    
441

    
442
@command(port_cmds)
443
class port_delete(_init_network, _optional_output_cmd, _port_wait):
444
    """Delete a port (== disconnect server from network)"""
445

    
446
    arguments = dict(
447
        wait=FlagArgument('Wait port to be established', ('-w', '--wait'))
448
    )
449

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

    
465
    def main(self, port_id):
466
        super(self.__class__, self)._run()
467
        self._run(port_id=port_id)
468

    
469

    
470
@command(port_cmds)
471
class port_modify(_init_network, _optional_json):
472
    """Modify the attributes of a port"""
473

    
474
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
475
    required = ['new_name', ]
476

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

    
485
    def main(self, port_id):
486
        super(self.__class__, self)._run()
487
        self._run(port_id=port_id)
488

    
489

    
490
class _port_create(_init_network, _optional_json, _port_wait):
491

    
492
    def connect(self, network_id, device_id):
493
        fixed_ips = [dict(ip_address=self['ip_address'])] if (
494
            self['ip_address']) else None
495
        if fixed_ips and self['subnet_id']:
496
            fixed_ips[0]['subnet_id'] = self['subnet_id']
497
        r = self.client.create_port(
498
            network_id, device_id,
499
            name=self['name'],
500
            security_groups=self['security_group_id'],
501
            fixed_ips=fixed_ips)
502
        if self['wait']:
503
            self._wait(r['id'], r['status'])
504
            r = self.client.get_port_details(r['id'])
505
        self._print([r])
506

    
507

    
508
@command(port_cmds)
509
class port_create(_port_create):
510
    """Create a new port (== connect server to network)"""
511

    
512
    arguments = dict(
513
        name=ValueArgument('A human readable name', '--name'),
514
        security_group_id=RepeatableArgument(
515
            'Add a security group id (can be repeated)',
516
            ('-g', '--security-group')),
517
        subnet_id=ValueArgument(
518
            'Subnet id for fixed ips (used with --ip-address)',
519
            '--subnet-id'),
520
        ip_address=ValueArgument(
521
            'IP address for subnet id', '--ip-address'),
522
        network_id=ValueArgument('Set the network ID', '--network-id'),
523
        device_id=ValueArgument(
524
            'The device is either a virtual server or a virtual router',
525
            '--device-id'),
526
        wait=FlagArgument('Wait port to be established', ('-w', '--wait')),
527
    )
528
    required = ('network_id', 'device_id')
529

    
530
    @errors.generic.all
531
    @errors.cyclades.connection
532
    @errors.cyclades.network_id
533
    @errors.cyclades.server_id
534
    def _run(self, network_id, server_id):
535
        self.connect(network_id, server_id)
536

    
537
    def main(self):
538
        super(self.__class__, self)._run()
539
        self._run(network_id=self['network_id'], server_id=self['device_id'])
540

    
541

    
542
@command(port_cmds)
543
class port_wait(_init_network, _port_wait):
544
    """Wait for port to finish (default: BUILD)"""
545

    
546
    arguments = dict(
547
        port_status=StatusArgument(
548
            'Wait while in this status (%s, default: %s)' % (
549
                ', '.join(port_states), port_states[0]),
550
            '--status',
551
            valid_states=port_states),
552
        timeout=IntArgument(
553
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
554
    )
555

    
556
    @errors.generic.all
557
    @errors.cyclades.connection
558
    def _run(self, port_id, port_status):
559
        port = self.client.get_port_details(port_id)
560
        if port['status'].lower() == port_status.lower():
561
            self._wait(port_id, port_status, timeout=self['timeout'])
562
        else:
563
            self.error(
564
                'Port %s: Cannot wait for status %s, '
565
                'status is already %s' % (
566
                    port_id, port_status, port['status']))
567

    
568
    def main(self, port_id):
569
        super(self.__class__, self)._run()
570
        port_status = self['port_status'] or port_states[0]
571
        self._run(port_id=port_id, port_status=port_status)
572

    
573

    
574
@command(ip_cmds)
575
class ip_list(_init_network, _optional_json):
576
    """List reserved floating IPs"""
577

    
578
    @errors.generic.all
579
    @errors.cyclades.connection
580
    def _run(self):
581
        self._print(self.client.list_floatingips())
582

    
583
    def main(self):
584
        super(self.__class__, self)._run()
585
        self._run()
586

    
587

    
588
@command(ip_cmds)
589
class ip_info(_init_network, _optional_json):
590
    """Get details on a floating IP"""
591

    
592
    @errors.generic.all
593
    @errors.cyclades.connection
594
    def _run(self, ip_id):
595
        self._print(
596
            self.client.get_floatingip_details(ip_id), self.print_dict)
597

    
598
    def main(self, ip_id):
599
        super(self.__class__, self)._run()
600
        self._run(ip_id=ip_id)
601

    
602

    
603
@command(ip_cmds)
604
class ip_create(_init_network, _optional_json):
605
    """Reserve an IP on a network"""
606

    
607
    arguments = dict(
608
        network_id=ValueArgument(
609
            'The network to preserve the IP on', '--network-id'),
610
        ip_address=ValueArgument('Allocate an IP address', '--address')
611
    )
612
    required = ('network_id', )
613

    
614
    @errors.generic.all
615
    @errors.cyclades.connection
616
    @errors.cyclades.network_id
617
    def _run(self, network_id):
618
        self._print(
619
            self.client.create_floatingip(
620
                network_id, floating_ip_address=self['ip_address']),
621
            self.print_dict)
622

    
623
    def main(self):
624
        super(self.__class__, self)._run()
625
        self._run(network_id=self['network_id'])
626

    
627

    
628
@command(ip_cmds)
629
class ip_delete(_init_network, _optional_output_cmd):
630
    """Unreserve an IP (also delete the port, if attached)"""
631

    
632
    def _run(self, ip_id):
633
        self._optional_output(self.client.delete_floatingip(ip_id))
634

    
635
    def main(self, ip_id):
636
        super(self.__class__, self)._run()
637
        self._run(ip_id=ip_id)
638

    
639

    
640
@command(ip_cmds)
641
class ip_attach(_port_create):
642
    """Attach an IP on a virtual server"""
643

    
644
    arguments = dict(
645
        name=ValueArgument('A human readable name for the port', '--name'),
646
        security_group_id=RepeatableArgument(
647
            'Add a security group id (can be repeated)',
648
            ('-g', '--security-group')),
649
        subnet_id=ValueArgument('Subnet id', '--subnet-id'),
650
        wait=FlagArgument('Wait IP to be attached', ('-w', '--wait')),
651
        server_id=ValueArgument(
652
            'Server to attach to this IP', '--server-id')
653
    )
654
    required = ('server_id', )
655

    
656
    @errors.generic.all
657
    @errors.cyclades.connection
658
    @errors.cyclades.server_id
659
    def _run(self, ip_or_ip_id, server_id):
660
        netid = None
661
        for ip in self.client.list_floatingips():
662
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
663
                netid = ip['floating_network_id']
664
                iparg = ValueArgument(parsed_name='--ip')
665
                iparg.value = ip['floating_ip_address']
666
                self.arguments['ip_address'] = iparg
667
                break
668
        if netid:
669
            self.error('Creating a port to attach IP %s to server %s' % (
670
                ip_or_ip_id, server_id))
671
            self.connect(netid, server_id)
672
        else:
673
            raiseCLIError(
674
                '%s does not match any reserved IPs or IP ids' % ip_or_ip_id,
675
                details=[
676
                    'To reserve an IP:', '  [kamaki] ip create',
677
                    'To see all reserved IPs:', '  [kamaki] ip list'])
678

    
679
    def main(self, ip_or_ip_id):
680
        super(self.__class__, self)._run()
681
        self._run(ip_or_ip_id=ip_or_ip_id, server_id=self['server_id'])
682

    
683

    
684
@command(ip_cmds)
685
class ip_detach(_init_network, _port_wait, _optional_json):
686
    """Detach an IP from a virtual server"""
687

    
688
    arguments = dict(
689
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
690
    )
691

    
692
    @errors.generic.all
693
    @errors.cyclades.connection
694
    def _run(self, ip_or_ip_id):
695
        for ip in self.client.list_floatingips():
696
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
697
                if not ip['port_id']:
698
                    raiseCLIError('IP %s is not attached' % ip_or_ip_id)
699
                self.error('Deleting port %s:' % ip['port_id'])
700
                self.client.delete_port(ip['port_id'])
701
                if self['wait']:
702
                    port_status = self.client.get_port_details(ip['port_id'])[
703
                        'status']
704
                    try:
705
                        self._wait(ip['port_id'], port_status)
706
                    except ClientError as ce:
707
                        if ce.status not in (404, ):
708
                            raise
709
                        self.error('Port %s is deleted' % ip['port_id'])
710
                return
711
        raiseCLIError('IP or IP id %s not found' % ip_or_ip_id)
712

    
713
    def main(self, ip_or_ip_id):
714
        super(self.__class__, self)._run()
715
        self._run(ip_or_ip_id)
716

    
717

    
718
#  Warn users for some importand changes
719

    
720
@command(network_cmds)
721
class network_connect(_port_create):
722
    """Connect a network with a device (server or router)"""
723

    
724
    arguments = dict(
725
        name=ValueArgument('A human readable name for the port', '--name'),
726
        security_group_id=RepeatableArgument(
727
            'Add a security group id (can be repeated)',
728
            ('-g', '--security-group')),
729
        subnet_id=ValueArgument(
730
            'Subnet id for fixed ips (used with --ip-address)',
731
            '--subnet-id'),
732
        ip_address=ValueArgument(
733
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
734
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
735
        device_id=RepeatableArgument(
736
            'Connect this device to the network (can be repeated)',
737
            '--device-id')
738
    )
739
    required = ('device_id', )
740

    
741
    @errors.generic.all
742
    @errors.cyclades.connection
743
    @errors.cyclades.network_id
744
    @errors.cyclades.server_id
745
    def _run(self, network_id, server_id):
746
        self.error('Creating a port to connect network %s with device %s' % (
747
            network_id, server_id))
748
        self.connect(network_id, server_id)
749

    
750
    def main(self, network_id):
751
        super(self.__class__, self)._run()
752
        for sid in self['device_id']:
753
            self._run(network_id=network_id, server_id=sid)
754

    
755

    
756
@command(network_cmds)
757
class network_disconnect(_init_network, _port_wait, _optional_json):
758
    """Disconnect a network from a device"""
759

    
760
    def _cyclades_client(self):
761
        auth = getattr(self, 'auth_base')
762
        endpoints = auth.get_service_endpoints('compute')
763
        URL = endpoints['publicURL']
764
        from kamaki.clients.cyclades import CycladesClient
765
        return CycladesClient(URL, self.client.token)
766

    
767
    arguments = dict(
768
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
769
        device_id=RepeatableArgument(
770
            'Disconnect device from the network (can be repeated)',
771
            '--device-id')
772
    )
773
    required = ('device_id', )
774

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

    
801
    def main(self, network_id):
802
        super(self.__class__, self)._run()
803
        for sid in self['device_id']:
804
            self._run(network_id=network_id, server_id=sid)