Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (28 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=ValueArgument('Assign the IP to project', '--project'),
631
    )
632
    required = ('network_id', )
633

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

    
644
    def main(self):
645
        super(self.__class__, self)._run()
646
        self._run(network_id=self['network_id'])
647

    
648

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

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

    
662

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

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

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

    
674

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

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

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

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

    
718

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

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

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

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

    
752

    
753
#  Warn users for some importand changes
754

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

    
759
    arguments = dict(
760
        name=ValueArgument('A human readable name for the port', '--name'),
761
        security_group_id=RepeatableArgument(
762
            'Add a security group id (can be repeated)',
763
            ('-g', '--security-group')),
764
        subnet_id=ValueArgument(
765
            'Subnet id for fixed ips (used with --ip-address)',
766
            '--subnet-id'),
767
        ip_address=ValueArgument(
768
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
769
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
770
        device_id=RepeatableArgument(
771
            'Connect this device to the network (can be repeated)',
772
            '--device-id')
773
    )
774
    required = ('device_id', )
775

    
776
    @errors.generic.all
777
    @errors.cyclades.connection
778
    @errors.cyclades.network_id
779
    @errors.cyclades.server_id
780
    def _run(self, network_id, server_id):
781
        self.error('Creating a port to connect network %s with device %s' % (
782
            network_id, server_id))
783
        self.connect(network_id, server_id)
784

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

    
790

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

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

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

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

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