Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / network.py @ 75ae8a08

History | View | Annotate | Download (27.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
        project=ValueArgument('Assign the network to project', '--project'),
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, name=self['name'],
204
            shared=self['shared'],
205
            project=self['project'])
206
        self._print(net, self.print_dict)
207

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

    
212

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

    
218
    @errors.generic.all
219
    @errors.cyclades.connection
220
    @errors.cyclades.network_id
221
    def _run(self, network_id, project):
222
        self.client.reassign_network(network_id, project)
223

    
224
    def main(self, network_id, project):
225
        super(self.__class__, self)._run()
226
        self._run(network_id=network_id, project=project)
227

    
228

    
229
@command(network_cmds)
230
class network_delete(_init_network, _optional_output_cmd):
231
    """Delete a network"""
232

    
233
    @errors.generic.all
234
    @errors.cyclades.connection
235
    @errors.cyclades.network_id
236
    def _run(self, network_id):
237
        r = self.client.delete_network(network_id)
238
        self._optional_output(r)
239

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

    
244

    
245
@command(network_cmds)
246
class network_modify(_init_network, _optional_json):
247
    """Modify network attributes"""
248

    
249
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
250
    required = ['new_name', ]
251

    
252
    @errors.generic.all
253
    @errors.cyclades.connection
254
    @errors.cyclades.network_id
255
    def _run(self, network_id):
256
        r = self.client.update_network(network_id, name=self['new_name'])
257
        self._print(r, self.print_dict)
258

    
259
    def main(self, network_id):
260
        super(self.__class__, self)._run()
261
        self._run(network_id=network_id)
262

    
263

    
264
@command(subnet_cmds)
265
class subnet_list(_init_network, _optional_json, _name_filter, _id_filter):
266
    """List subnets
267
    Use filtering arguments (e.g., --name-like) to manage long server lists
268
    """
269

    
270
    arguments = dict(
271
        detail=FlagArgument('show detailed output', ('-l', '--details')),
272
        more=FlagArgument(
273
            'output results in pages (-n to set items per page, default 10)',
274
            '--more')
275
    )
276

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

    
298
    def main(self):
299
        super(self.__class__, self)._run()
300
        self._run()
301

    
302

    
303
@command(subnet_cmds)
304
class subnet_info(_init_network, _optional_json):
305
    """Get details about a subnet"""
306

    
307
    @errors.generic.all
308
    @errors.cyclades.connection
309
    def _run(self, subnet_id):
310
        net = self.client.get_subnet_details(subnet_id)
311
        self._print(net, self.print_dict)
312

    
313
    def main(self, subnet_id):
314
        super(self.__class__, self)._run()
315
        self._run(subnet_id=subnet_id)
316

    
317

    
318
class AllocationPoolArgument(RepeatableArgument):
319

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

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

    
339

    
340
@command(subnet_cmds)
341
class subnet_create(_init_network, _optional_json):
342
    """Create a new subnet"""
343

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

    
359
    @errors.generic.all
360
    @errors.cyclades.connection
361
    @errors.cyclades.network_id
362
    def _run(self, network_id, cidr):
363
        net = self.client.create_subnet(
364
            network_id, cidr,
365
            self['name'], self['allocation_pools'], self['gateway'],
366
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
367
        self._print(net, self.print_dict)
368

    
369
    def main(self):
370
        super(self.__class__, self)._run()
371
        self._run(network_id=self['network_id'], cidr=self['cidr'])
372

    
373

    
374
# @command(subnet_cmds)
375
# class subnet_delete(_init_network, _optional_output_cmd):
376
#     """Delete a subnet"""
377

    
378
#     @errors.generic.all
379
#     @errors.cyclades.connection
380
#     def _run(self, subnet_id):
381
#         r = self.client.delete_subnet(subnet_id)
382
#         self._optional_output(r)
383

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

    
388

    
389
@command(subnet_cmds)
390
class subnet_modify(_init_network, _optional_json):
391
    """Modify the attributes of a subnet"""
392

    
393
    arguments = dict(
394
        new_name=ValueArgument('New name of the subnet', '--name')
395
    )
396
    required = ['new_name']
397

    
398
    @errors.generic.all
399
    @errors.cyclades.connection
400
    def _run(self, subnet_id):
401
        r = self.client.update_subnet(subnet_id, name=self['new_name'])
402
        self._print(r, self.print_dict)
403

    
404
    def main(self, subnet_id):
405
        super(self.__class__, self)._run()
406
        self._run(subnet_id=subnet_id)
407

    
408

    
409
@command(port_cmds)
410
class port_list(_init_network, _optional_json, _name_filter, _id_filter):
411
    """List all ports"""
412

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

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

    
441
    def main(self):
442
        super(self.__class__, self)._run()
443
        self._run()
444

    
445

    
446
@command(port_cmds)
447
class port_info(_init_network, _optional_json):
448
    """Get details about a port"""
449

    
450
    @errors.generic.all
451
    @errors.cyclades.connection
452
    def _run(self, port_id):
453
        port = self.client.get_port_details(port_id)
454
        self._print(port, self.print_dict)
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_delete(_init_network, _optional_output_cmd, _port_wait):
463
    """Delete a port (== disconnect server from network)"""
464

    
465
    arguments = dict(
466
        wait=FlagArgument('Wait port to be established', ('-w', '--wait'))
467
    )
468

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

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

    
488

    
489
@command(port_cmds)
490
class port_modify(_init_network, _optional_json):
491
    """Modify the attributes of a port"""
492

    
493
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
494
    required = ['new_name', ]
495

    
496
    @errors.generic.all
497
    @errors.cyclades.connection
498
    def _run(self, port_id):
499
        r = self.client.get_port_details(port_id)
500
        r = self.client.update_port(
501
            port_id, r['network_id'], name=self['new_name'])
502
        self._print(r, self.print_dict)
503

    
504
    def main(self, port_id):
505
        super(self.__class__, self)._run()
506
        self._run(port_id=port_id)
507

    
508

    
509
class _port_create(_init_network, _optional_json, _port_wait):
510

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

    
526

    
527
@command(port_cmds)
528
class port_create(_port_create):
529
    """Create a new port (== connect server to network)"""
530

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

    
549
    @errors.generic.all
550
    @errors.cyclades.connection
551
    @errors.cyclades.network_id
552
    @errors.cyclades.server_id
553
    def _run(self, network_id, server_id):
554
        self.connect(network_id, server_id)
555

    
556
    def main(self):
557
        super(self.__class__, self)._run()
558
        self._run(network_id=self['network_id'], server_id=self['device_id'])
559

    
560

    
561
@command(port_cmds)
562
class port_wait(_init_network, _port_wait):
563
    """Wait for port to finish (default: BUILD)"""
564

    
565
    arguments = dict(
566
        port_status=StatusArgument(
567
            'Wait while in this status (%s, default: %s)' % (
568
                ', '.join(port_states), port_states[0]),
569
            '--status',
570
            valid_states=port_states),
571
        timeout=IntArgument(
572
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
573
    )
574

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

    
587
    def main(self, port_id):
588
        super(self.__class__, self)._run()
589
        port_status = self['port_status'] or port_states[0]
590
        self._run(port_id=port_id, port_status=port_status)
591

    
592

    
593
@command(ip_cmds)
594
class ip_list(_init_network, _optional_json):
595
    """List reserved floating IPs"""
596

    
597
    @errors.generic.all
598
    @errors.cyclades.connection
599
    def _run(self):
600
        self._print(self.client.list_floatingips())
601

    
602
    def main(self):
603
        super(self.__class__, self)._run()
604
        self._run()
605

    
606

    
607
@command(ip_cmds)
608
class ip_info(_init_network, _optional_json):
609
    """Get details on a floating IP"""
610

    
611
    @errors.generic.all
612
    @errors.cyclades.connection
613
    def _run(self, ip_id):
614
        self._print(
615
            self.client.get_floatingip_details(ip_id), self.print_dict)
616

    
617
    def main(self, ip_id):
618
        super(self.__class__, self)._run()
619
        self._run(ip_id=ip_id)
620

    
621

    
622
@command(ip_cmds)
623
class ip_create(_init_network, _optional_json):
624
    """Reserve an IP on a network"""
625

    
626
    arguments = dict(
627
        network_id=ValueArgument(
628
            'The network to preserve the IP on', '--network-id'),
629
        ip_address=ValueArgument('Allocate an IP address', '--address'),
630
        project_id=ValueArgument('Assign the IP to project', '--project-id'),
631
    )
632

    
633
    @errors.generic.all
634
    @errors.cyclades.connection
635
    def _run(self):
636
        self._print(
637
            self.client.create_floatingip(
638
                self['network_id'],
639
                floating_ip_address=self['ip_address'],
640
                project_id=self['project_id']),
641
            self.print_dict)
642

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

    
647

    
648
@command(ip_cmds)
649
class ip_reassign(_init_network, _optional_output_cmd):
650
    """Assign a floating IP to a different project
651
    """
652
    @errors.generic.all
653
    @errors.cyclades.connection
654
    def _run(self, ip, project):
655
        self._optional_output(self.client.reassign_floating_ip(ip, project))
656

    
657
    def main(self, IP, project):
658
        super(self.__class__, self)._run()
659
        self._run(ip=IP, project=project)
660

    
661

    
662
@command(ip_cmds)
663
class ip_delete(_init_network, _optional_output_cmd):
664
    """Unreserve an IP (also delete the port, if attached)"""
665

    
666
    def _run(self, ip_id):
667
        self._optional_output(self.client.delete_floatingip(ip_id))
668

    
669
    def main(self, ip_id):
670
        super(self.__class__, self)._run()
671
        self._run(ip_id=ip_id)
672

    
673

    
674
@command(ip_cmds)
675
class ip_attach(_port_create):
676
    """Attach an IP on a virtual server"""
677

    
678
    arguments = dict(
679
        name=ValueArgument('A human readable name for the port', '--name'),
680
        security_group_id=RepeatableArgument(
681
            'Add a security group id (can be repeated)',
682
            ('-g', '--security-group')),
683
        subnet_id=ValueArgument('Subnet id', '--subnet-id'),
684
        wait=FlagArgument('Wait IP to be attached', ('-w', '--wait')),
685
        server_id=ValueArgument(
686
            'Server to attach to this IP', '--server-id')
687
    )
688
    required = ('server_id', )
689

    
690
    @errors.generic.all
691
    @errors.cyclades.connection
692
    @errors.cyclades.server_id
693
    def _run(self, ip_or_ip_id, server_id):
694
        netid = None
695
        for ip in self.client.list_floatingips():
696
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
697
                netid = ip['floating_network_id']
698
                iparg = ValueArgument(parsed_name='--ip')
699
                iparg.value = ip['floating_ip_address']
700
                self.arguments['ip_address'] = iparg
701
                break
702
        if netid:
703
            self.error('Creating a port to attach IP %s to server %s' % (
704
                ip_or_ip_id, server_id))
705
            self.connect(netid, server_id)
706
        else:
707
            raiseCLIError(
708
                '%s does not match any reserved IPs or IP ids' % ip_or_ip_id,
709
                details=[
710
                    'To reserve an IP:', '  [kamaki] ip create',
711
                    'To see all reserved IPs:', '  [kamaki] ip list'])
712

    
713
    def main(self, ip_or_ip_id):
714
        super(self.__class__, self)._run()
715
        self._run(ip_or_ip_id=ip_or_ip_id, server_id=self['server_id'])
716

    
717

    
718
@command(ip_cmds)
719
class ip_detach(_init_network, _port_wait, _optional_json):
720
    """Detach an IP from a virtual server"""
721

    
722
    arguments = dict(
723
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
724
    )
725

    
726
    @errors.generic.all
727
    @errors.cyclades.connection
728
    def _run(self, ip_or_ip_id):
729
        for ip in self.client.list_floatingips():
730
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
731
                if not ip['port_id']:
732
                    raiseCLIError('IP %s is not attached' % ip_or_ip_id)
733
                self.error('Deleting port %s:' % ip['port_id'])
734
                self.client.delete_port(ip['port_id'])
735
                if self['wait']:
736
                    port_status = self.client.get_port_details(ip['port_id'])[
737
                        'status']
738
                    try:
739
                        self._wait(ip['port_id'], port_status)
740
                    except ClientError as ce:
741
                        if ce.status not in (404, ):
742
                            raise
743
                        self.error('Port %s is deleted' % ip['port_id'])
744
                return
745
        raiseCLIError('IP or IP id %s not found' % ip_or_ip_id)
746

    
747
    def main(self, ip_or_ip_id):
748
        super(self.__class__, self)._run()
749
        self._run(ip_or_ip_id)
750

    
751

    
752
#  Warn users for some importand changes
753

    
754
@command(network_cmds)
755
class network_connect(_port_create):
756
    """Connect a network with a device (server or router)"""
757

    
758
    arguments = dict(
759
        name=ValueArgument('A human readable name for the port', '--name'),
760
        security_group_id=RepeatableArgument(
761
            'Add a security group id (can be repeated)',
762
            ('-g', '--security-group')),
763
        subnet_id=ValueArgument(
764
            'Subnet id for fixed ips (used with --ip-address)',
765
            '--subnet-id'),
766
        ip_address=ValueArgument(
767
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
768
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
769
        device_id=RepeatableArgument(
770
            'Connect this device to 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
        self.error('Creating a port to connect network %s with device %s' % (
781
            network_id, server_id))
782
        self.connect(network_id, server_id)
783

    
784
    def main(self, network_id):
785
        super(self.__class__, self)._run()
786
        for sid in self['device_id']:
787
            self._run(network_id=network_id, server_id=sid)
788

    
789

    
790
@command(network_cmds)
791
class network_disconnect(_init_network, _port_wait, _optional_json):
792
    """Disconnect a network from a device"""
793

    
794
    def _cyclades_client(self):
795
        auth = getattr(self, 'auth_base')
796
        endpoints = auth.get_service_endpoints('compute')
797
        URL = endpoints['publicURL']
798
        from kamaki.clients.cyclades import CycladesClient
799
        return CycladesClient(URL, self.client.token)
800

    
801
    arguments = dict(
802
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
803
        device_id=RepeatableArgument(
804
            'Disconnect device from the network (can be repeated)',
805
            '--device-id')
806
    )
807
    required = ('device_id', )
808

    
809
    @errors.generic.all
810
    @errors.cyclades.connection
811
    @errors.cyclades.network_id
812
    @errors.cyclades.server_id
813
    def _run(self, network_id, server_id):
814
        vm = self._cyclades_client().get_server_details(server_id)
815
        ports = [port for port in vm['attachments'] if (
816
            port['network_id'] in (network_id, ))]
817
        if not ports:
818
            raiseCLIError('Network %s is not connected to device %s' % (
819
                network_id, server_id))
820
        for port in ports:
821
            if self['wait']:
822
                port['status'] = self.client.get_port_details(port['id'])[
823
                    'status']
824
            self.client.delete_port(port['id'])
825
            self.error('Deleting port %s (net-id: %s, device-id: %s):' % (
826
                port['id'], network_id, server_id))
827
            if self['wait']:
828
                try:
829
                    self._wait(port['id'], port['status'])
830
                except ClientError as ce:
831
                    if ce.status not in (404, ):
832
                        raise
833
                    self.error('Port %s is deleted' % port['id'])
834

    
835
    def main(self, network_id):
836
        super(self.__class__, self)._run()
837
        for sid in self['device_id']:
838
            self._run(network_id=network_id, server_id=sid)