Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / network.py @ 89cf28ef

History | View | Annotate | Download (28.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
    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
        project_id=ValueArgument('Assign network to project', '--project-id'),
193
        network_type=NetworkTypeArgument(
194
            'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
195
            '--type')
196
    )
197

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

    
209
    def main(self):
210
        super(self.__class__, self)._run()
211
        self._run(network_type=self['network_type'])
212

    
213

    
214
@command(network_cmds)
215
class network_reassign(_init_network, _optional_json):
216
    """Assign a network to a different project"""
217

    
218
    arguments = dict(
219
        project_id=ValueArgument('Assign network to project', '--project-id'),
220
    )
221
    required = ('project_id', )
222

    
223
    @errors.generic.all
224
    @errors.cyclades.connection
225
    @errors.cyclades.network_id
226
    def _run(self, network_id, project):
227
        self.client.reassign_network(network_id, project)
228

    
229
    def main(self, network_id):
230
        super(self.__class__, self)._run()
231
        self._run(network_id=network_id, project=self['project'])
232

    
233

    
234
@command(network_cmds)
235
class network_delete(_init_network, _optional_output_cmd):
236
    """Delete a network"""
237

    
238
    @errors.generic.all
239
    @errors.cyclades.connection
240
    @errors.cyclades.network_id
241
    def _run(self, network_id):
242
        r = self.client.delete_network(network_id)
243
        self._optional_output(r)
244

    
245
    def main(self, network_id):
246
        super(self.__class__, self)._run()
247
        self._run(network_id=network_id)
248

    
249

    
250
@command(network_cmds)
251
class network_modify(_init_network, _optional_json):
252
    """Modify network attributes"""
253

    
254
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
255
    required = ['new_name', ]
256

    
257
    @errors.generic.all
258
    @errors.cyclades.connection
259
    @errors.cyclades.network_id
260
    def _run(self, network_id):
261
        r = self.client.update_network(network_id, name=self['new_name'])
262
        self._print(r, self.print_dict)
263

    
264
    def main(self, network_id):
265
        super(self.__class__, self)._run()
266
        self._run(network_id=network_id)
267

    
268

    
269
@command(subnet_cmds)
270
class subnet_list(_init_network, _optional_json, _name_filter, _id_filter):
271
    """List subnets
272
    Use filtering arguments (e.g., --name-like) to manage long server lists
273
    """
274

    
275
    arguments = dict(
276
        detail=FlagArgument('show detailed output', ('-l', '--details')),
277
        more=FlagArgument(
278
            'output results in pages (-n to set items per page, default 10)',
279
            '--more')
280
    )
281

    
282
    @errors.generic.all
283
    @errors.cyclades.connection
284
    def _run(self):
285
        nets = self.client.list_subnets()
286
        nets = self._filter_by_name(nets)
287
        nets = self._filter_by_id(nets)
288
        if not self['detail']:
289
            nets = [dict(
290
                _0_id=n['id'],
291
                _1_name=n['name'],
292
                _2_net='( of network %s )' % n['network_id']) for n in nets]
293
            kwargs = dict(title=('_0_id', '_1_name', '_2_net'))
294
        else:
295
            kwargs = dict()
296
        if self['more']:
297
            kwargs['out'] = StringIO()
298
            kwargs['title'] = ()
299
        self._print(nets, **kwargs)
300
        if self['more']:
301
            pager(kwargs['out'].getvalue())
302

    
303
    def main(self):
304
        super(self.__class__, self)._run()
305
        self._run()
306

    
307

    
308
@command(subnet_cmds)
309
class subnet_info(_init_network, _optional_json):
310
    """Get details about a subnet"""
311

    
312
    @errors.generic.all
313
    @errors.cyclades.connection
314
    def _run(self, subnet_id):
315
        net = self.client.get_subnet_details(subnet_id)
316
        self._print(net, self.print_dict)
317

    
318
    def main(self, subnet_id):
319
        super(self.__class__, self)._run()
320
        self._run(subnet_id=subnet_id)
321

    
322

    
323
class AllocationPoolArgument(RepeatableArgument):
324

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

    
329
    @value.setter
330
    def value(self, new_pools):
331
        if not new_pools:
332
            return
333
        new_list = []
334
        for pool in new_pools:
335
            start, comma, end = pool.partition(',')
336
            if not (start and comma and end):
337
                raise CLIInvalidArgument(
338
                    'Invalid allocation pool argument %s' % pool, details=[
339
                    'Allocation values must be of the form:',
340
                    '  <start address>,<end address>'])
341
            new_list.append(dict(start=start, end=end))
342
        self._value = new_list
343

    
344

    
345
@command(subnet_cmds)
346
class subnet_create(_init_network, _optional_json):
347
    """Create a new subnet"""
348

    
349
    arguments = dict(
350
        name=ValueArgument('Subnet name', '--name'),
351
        allocation_pools=AllocationPoolArgument(
352
            'start_address,end_address of allocation pool (can be repeated)'
353
            ' e.g., --alloc-pool=123.45.67.1,123.45.67.8',
354
            '--alloc-pool'),
355
        gateway=ValueArgument('Gateway IP', '--gateway'),
356
        subnet_id=ValueArgument('The id for the subnet', '--id'),
357
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
358
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'),
359
        network_id=ValueArgument('Set the network ID', '--network-id'),
360
        cidr=ValueArgument('Set the CIDR', '--cidr')
361
    )
362
    required = ('network_id', 'cidr')
363

    
364
    @errors.generic.all
365
    @errors.cyclades.connection
366
    @errors.cyclades.network_id
367
    def _run(self, network_id, cidr):
368
        net = self.client.create_subnet(
369
            network_id, cidr,
370
            self['name'], self['allocation_pools'], self['gateway'],
371
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
372
        self._print(net, self.print_dict)
373

    
374
    def main(self):
375
        super(self.__class__, self)._run()
376
        self._run(network_id=self['network_id'], cidr=self['cidr'])
377

    
378

    
379
# @command(subnet_cmds)
380
# class subnet_delete(_init_network, _optional_output_cmd):
381
#     """Delete a subnet"""
382

    
383
#     @errors.generic.all
384
#     @errors.cyclades.connection
385
#     def _run(self, subnet_id):
386
#         r = self.client.delete_subnet(subnet_id)
387
#         self._optional_output(r)
388

    
389
#     def main(self, subnet_id):
390
#         super(self.__class__, self)._run()
391
#         self._run(subnet_id=subnet_id)
392

    
393

    
394
@command(subnet_cmds)
395
class subnet_modify(_init_network, _optional_json):
396
    """Modify the attributes of a subnet"""
397

    
398
    arguments = dict(
399
        new_name=ValueArgument('New name of the subnet', '--name')
400
    )
401
    required = ['new_name']
402

    
403
    @errors.generic.all
404
    @errors.cyclades.connection
405
    def _run(self, subnet_id):
406
        r = self.client.update_subnet(subnet_id, name=self['new_name'])
407
        self._print(r, self.print_dict)
408

    
409
    def main(self, subnet_id):
410
        super(self.__class__, self)._run()
411
        self._run(subnet_id=subnet_id)
412

    
413

    
414
@command(port_cmds)
415
class port_list(_init_network, _optional_json, _name_filter, _id_filter):
416
    """List all ports"""
417

    
418
    arguments = dict(
419
        detail=FlagArgument('show detailed output', ('-l', '--details')),
420
        more=FlagArgument(
421
            'output results in pages (-n to set items per page, default 10)',
422
            '--more'),
423
        user_id=ValueArgument(
424
            'show only networks belonging to user with this id', '--user-id')
425
    )
426

    
427
    @errors.generic.all
428
    @errors.cyclades.connection
429
    def _run(self):
430
        detail = bool(self['detail'] or self['user_id'])
431
        ports = self.client.list_ports(detail=detail)
432
        ports = self._filter_by_user_id(ports)
433
        ports = self._filter_by_name(ports)
434
        ports = self._filter_by_id(ports)
435
        if detail and not self['detail']:
436
            ports = [dict(
437
                id=p['id'], name=p['name'], links=p['links']) for p in ports]
438
        kwargs = dict()
439
        if self['more']:
440
            kwargs['out'] = StringIO()
441
            kwargs['title'] = ()
442
        self._print(ports, **kwargs)
443
        if self['more']:
444
            pager(kwargs['out'].getvalue())
445

    
446
    def main(self):
447
        super(self.__class__, self)._run()
448
        self._run()
449

    
450

    
451
@command(port_cmds)
452
class port_info(_init_network, _optional_json):
453
    """Get details about a port"""
454

    
455
    @errors.generic.all
456
    @errors.cyclades.connection
457
    def _run(self, port_id):
458
        port = self.client.get_port_details(port_id)
459
        self._print(port, self.print_dict)
460

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

    
465

    
466
@command(port_cmds)
467
class port_delete(_init_network, _optional_output_cmd, _port_wait):
468
    """Delete a port (== disconnect server from network)"""
469

    
470
    arguments = dict(
471
        wait=FlagArgument('Wait port to be established', ('-w', '--wait'))
472
    )
473

    
474
    @errors.generic.all
475
    @errors.cyclades.connection
476
    def _run(self, port_id):
477
        if self['wait']:
478
            status = self.client.get_port_details(port_id)['status']
479
        r = self.client.delete_port(port_id)
480
        if self['wait']:
481
            try:
482
                self._wait(port_id, status)
483
            except ClientError as ce:
484
                if ce.status not in (404, ):
485
                    raise
486
                self.error('Port %s is deleted' % port_id)
487
        self._optional_output(r)
488

    
489
    def main(self, port_id):
490
        super(self.__class__, self)._run()
491
        self._run(port_id=port_id)
492

    
493

    
494
@command(port_cmds)
495
class port_modify(_init_network, _optional_json):
496
    """Modify the attributes of a port"""
497

    
498
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
499
    required = ['new_name', ]
500

    
501
    @errors.generic.all
502
    @errors.cyclades.connection
503
    def _run(self, port_id):
504
        r = self.client.get_port_details(port_id)
505
        r = self.client.update_port(
506
            port_id, r['network_id'], name=self['new_name'])
507
        self._print(r, self.print_dict)
508

    
509
    def main(self, port_id):
510
        super(self.__class__, self)._run()
511
        self._run(port_id=port_id)
512

    
513

    
514
class _port_create(_init_network, _optional_json, _port_wait):
515

    
516
    def connect(self, network_id, device_id):
517
        fixed_ips = [dict(ip_address=self['ip_address'])] if (
518
            self['ip_address']) else None
519
        if fixed_ips and self['subnet_id']:
520
            fixed_ips[0]['subnet_id'] = self['subnet_id']
521
        r = self.client.create_port(
522
            network_id, device_id,
523
            name=self['name'],
524
            security_groups=self['security_group_id'],
525
            fixed_ips=fixed_ips)
526
        if self['wait']:
527
            self._wait(r['id'], r['status'])
528
            r = self.client.get_port_details(r['id'])
529
        self._print([r])
530

    
531

    
532
@command(port_cmds)
533
class port_create(_port_create):
534
    """Create a new port (== connect server to network)"""
535

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

    
554
    @errors.generic.all
555
    @errors.cyclades.connection
556
    @errors.cyclades.network_id
557
    @errors.cyclades.server_id
558
    def _run(self, network_id, server_id):
559
        self.connect(network_id, server_id)
560

    
561
    def main(self):
562
        super(self.__class__, self)._run()
563
        self._run(network_id=self['network_id'], server_id=self['device_id'])
564

    
565

    
566
@command(port_cmds)
567
class port_wait(_init_network, _port_wait):
568
    """Wait for port to finish (default: BUILD)"""
569

    
570
    arguments = dict(
571
        port_status=StatusArgument(
572
            'Wait while in this status (%s, default: %s)' % (
573
                ', '.join(port_states), port_states[0]),
574
            '--status',
575
            valid_states=port_states),
576
        timeout=IntArgument(
577
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
578
    )
579

    
580
    @errors.generic.all
581
    @errors.cyclades.connection
582
    def _run(self, port_id, port_status):
583
        port = self.client.get_port_details(port_id)
584
        if port['status'].lower() == port_status.lower():
585
            self._wait(port_id, port_status, timeout=self['timeout'])
586
        else:
587
            self.error(
588
                'Port %s: Cannot wait for status %s, '
589
                'status is already %s' % (
590
                    port_id, port_status, port['status']))
591

    
592
    def main(self, port_id):
593
        super(self.__class__, self)._run()
594
        port_status = self['port_status'] or port_states[0]
595
        self._run(port_id=port_id, port_status=port_status)
596

    
597

    
598
@command(ip_cmds)
599
class ip_list(_init_network, _optional_json):
600
    """List reserved floating IPs"""
601

    
602
    @errors.generic.all
603
    @errors.cyclades.connection
604
    def _run(self):
605
        self._print(self.client.list_floatingips())
606

    
607
    def main(self):
608
        super(self.__class__, self)._run()
609
        self._run()
610

    
611

    
612
@command(ip_cmds)
613
class ip_info(_init_network, _optional_json):
614
    """Get details on a floating IP"""
615

    
616
    @errors.generic.all
617
    @errors.cyclades.connection
618
    def _run(self, ip_id):
619
        self._print(
620
            self.client.get_floatingip_details(ip_id), self.print_dict)
621

    
622
    def main(self, ip_id):
623
        super(self.__class__, self)._run()
624
        self._run(ip_id=ip_id)
625

    
626

    
627
@command(ip_cmds)
628
class ip_create(_init_network, _optional_json):
629
    """Reserve an IP on a network"""
630

    
631
    arguments = dict(
632
        network_id=ValueArgument(
633
            'The network to preserve the IP on', '--network-id'),
634
        ip_address=ValueArgument('Allocate an IP address', '--address'),
635
        project_id=ValueArgument('Assign the IP to project', '--project-id'),
636
    )
637
    required = ('network_id', )
638

    
639
    @errors.generic.all
640
    @errors.cyclades.connection
641
    @errors.cyclades.network_id
642
    def _run(self, network_id):
643
        self._print(
644
            self.client.create_floatingip(
645
                network_id,
646
                floating_ip_address=self['ip_address'],
647
                project=self['project_id']),
648
            self.print_dict)
649

    
650
    def main(self):
651
        super(self.__class__, self)._run()
652
        self._run(network_id=self['network_id'])
653

    
654

    
655
@command(ip_cmds)
656
class ip_reassign(_init_network, _optional_output_cmd):
657
    """Assign a floating IP to a different project"""
658

    
659
    arguments = dict(
660
        project_id=ValueArgument('Assign the IP to project', '--project-id'),
661
    )
662
    required = ('project_id', )
663

    
664
    @errors.generic.all
665
    @errors.cyclades.connection
666
    def _run(self, ip, project):
667
        self._optional_output(self.client.reassign_floating_ip(ip, project))
668

    
669
    def main(self, IP):
670
        super(self.__class__, self)._run()
671
        self._run(ip=IP, project=self['project_id'])
672

    
673

    
674
@command(ip_cmds)
675
class ip_delete(_init_network, _optional_output_cmd):
676
    """Unreserve an IP (also delete the port, if attached)"""
677

    
678
    def _run(self, ip_id):
679
        self._optional_output(self.client.delete_floatingip(ip_id))
680

    
681
    def main(self, ip_id):
682
        super(self.__class__, self)._run()
683
        self._run(ip_id=ip_id)
684

    
685

    
686
@command(ip_cmds)
687
class ip_attach(_port_create):
688
    """Attach an IP on a virtual server"""
689

    
690
    arguments = dict(
691
        name=ValueArgument('A human readable name for the port', '--name'),
692
        security_group_id=RepeatableArgument(
693
            'Add a security group id (can be repeated)',
694
            ('-g', '--security-group')),
695
        subnet_id=ValueArgument('Subnet id', '--subnet-id'),
696
        wait=FlagArgument('Wait IP to be attached', ('-w', '--wait')),
697
        server_id=ValueArgument(
698
            'Server to attach to this IP', '--server-id')
699
    )
700
    required = ('server_id', )
701

    
702
    @errors.generic.all
703
    @errors.cyclades.connection
704
    @errors.cyclades.server_id
705
    def _run(self, ip_or_ip_id, server_id):
706
        netid = None
707
        for ip in self.client.list_floatingips():
708
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
709
                netid = ip['floating_network_id']
710
                iparg = ValueArgument(parsed_name='--ip')
711
                iparg.value = ip['floating_ip_address']
712
                self.arguments['ip_address'] = iparg
713
                break
714
        if netid:
715
            self.error('Creating a port to attach IP %s to server %s' % (
716
                ip_or_ip_id, server_id))
717
            self.connect(netid, server_id)
718
        else:
719
            raiseCLIError(
720
                '%s does not match any reserved IPs or IP ids' % ip_or_ip_id,
721
                details=[
722
                    'To reserve an IP:', '  [kamaki] ip create',
723
                    'To see all reserved IPs:', '  [kamaki] ip list'])
724

    
725
    def main(self, ip_or_ip_id):
726
        super(self.__class__, self)._run()
727
        self._run(ip_or_ip_id=ip_or_ip_id, server_id=self['server_id'])
728

    
729

    
730
@command(ip_cmds)
731
class ip_detach(_init_network, _port_wait, _optional_json):
732
    """Detach an IP from a virtual server"""
733

    
734
    arguments = dict(
735
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
736
    )
737

    
738
    @errors.generic.all
739
    @errors.cyclades.connection
740
    def _run(self, ip_or_ip_id):
741
        for ip in self.client.list_floatingips():
742
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
743
                if not ip['port_id']:
744
                    raiseCLIError('IP %s is not attached' % ip_or_ip_id)
745
                self.error('Deleting port %s:' % ip['port_id'])
746
                self.client.delete_port(ip['port_id'])
747
                if self['wait']:
748
                    port_status = self.client.get_port_details(ip['port_id'])[
749
                        'status']
750
                    try:
751
                        self._wait(ip['port_id'], port_status)
752
                    except ClientError as ce:
753
                        if ce.status not in (404, ):
754
                            raise
755
                        self.error('Port %s is deleted' % ip['port_id'])
756
                return
757
        raiseCLIError('IP or IP id %s not found' % ip_or_ip_id)
758

    
759
    def main(self, ip_or_ip_id):
760
        super(self.__class__, self)._run()
761
        self._run(ip_or_ip_id)
762

    
763

    
764
#  Warn users for some importand changes
765

    
766
@command(network_cmds)
767
class network_connect(_port_create):
768
    """Connect a network with a device (server or router)"""
769

    
770
    arguments = dict(
771
        name=ValueArgument('A human readable name for the port', '--name'),
772
        security_group_id=RepeatableArgument(
773
            'Add a security group id (can be repeated)',
774
            ('-g', '--security-group')),
775
        subnet_id=ValueArgument(
776
            'Subnet id for fixed ips (used with --ip-address)',
777
            '--subnet-id'),
778
        ip_address=ValueArgument(
779
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
780
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
781
        device_id=RepeatableArgument(
782
            'Connect this device to the network (can be repeated)',
783
            '--device-id')
784
    )
785
    required = ('device_id', )
786

    
787
    @errors.generic.all
788
    @errors.cyclades.connection
789
    @errors.cyclades.network_id
790
    @errors.cyclades.server_id
791
    def _run(self, network_id, server_id):
792
        self.error('Creating a port to connect network %s with device %s' % (
793
            network_id, server_id))
794
        self.connect(network_id, server_id)
795

    
796
    def main(self, network_id):
797
        super(self.__class__, self)._run()
798
        for sid in self['device_id']:
799
            self._run(network_id=network_id, server_id=sid)
800

    
801

    
802
@command(network_cmds)
803
class network_disconnect(_init_network, _port_wait, _optional_json):
804
    """Disconnect a network from a device"""
805

    
806
    def _cyclades_client(self):
807
        auth = getattr(self, 'auth_base')
808
        endpoints = auth.get_service_endpoints('compute')
809
        URL = endpoints['publicURL']
810
        from kamaki.clients.cyclades import CycladesClient
811
        return CycladesClient(URL, self.client.token)
812

    
813
    arguments = dict(
814
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
815
        device_id=RepeatableArgument(
816
            'Disconnect device from the network (can be repeated)',
817
            '--device-id')
818
    )
819
    required = ('device_id', )
820

    
821
    @errors.generic.all
822
    @errors.cyclades.connection
823
    @errors.cyclades.network_id
824
    @errors.cyclades.server_id
825
    def _run(self, network_id, server_id):
826
        vm = self._cyclades_client().get_server_details(server_id)
827
        ports = [port for port in vm['attachments'] if (
828
            port['network_id'] in (network_id, ))]
829
        if not ports:
830
            raiseCLIError('Network %s is not connected to device %s' % (
831
                network_id, server_id))
832
        for port in ports:
833
            if self['wait']:
834
                port['status'] = self.client.get_port_details(port['id'])[
835
                    'status']
836
            self.client.delete_port(port['id'])
837
            self.error('Deleting port %s (net-id: %s, device-id: %s):' % (
838
                port['id'], network_id, server_id))
839
            if self['wait']:
840
                try:
841
                    self._wait(port['id'], port['status'])
842
                except ClientError as ce:
843
                    if ce.status not in (404, ):
844
                        raise
845
                    self.error('Port %s is deleted' % port['id'])
846

    
847
    def main(self, network_id):
848
        super(self.__class__, self)._run()
849
        for sid in self['device_id']:
850
            self._run(network_id=network_id, server_id=sid)