Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (20.6 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
    CLISyntaxError, CLIBaseUrlError, CLIInvalidArgument)
41
from kamaki.clients.cyclades import CycladesNetworkClient
42
from kamaki.cli.argument import (
43
    FlagArgument, ValueArgument, RepeatableArgument, IntArgument)
44
from kamaki.cli.commands import _command_init, errors, addLogSettings
45
from kamaki.cli.commands import (
46
    _optional_output_cmd, _optional_json, _name_filter, _id_filter)
47
from kamaki.cli.utils import filter_dicts_by_dict
48
from kamaki.cli.commands.cyclades import _service_wait
49

    
50

    
51
network_cmds = CommandTree('network', 'Networking API network commands')
52
port_cmds = CommandTree('port', 'Networking API network commands')
53
subnet_cmds = CommandTree('subnet', 'Networking API network commands')
54
ip_cmds = CommandTree('ip', 'Networking 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: /user authenticate\
60
    \n* to set authentication token: /config set cloud.<cloud>.token <token>'
61

    
62

    
63
class _network_wait(_service_wait):
64

    
65
    def _wait(self, net_id, current_status, timeout=60):
66
        super(_network_wait, self)._wait(
67
            'Network', net_id, self.client.wait_network, current_status,
68
            timeout=timeout)
69

    
70

    
71
class _port_wait(_service_wait):
72

    
73
    def _wait(self, port_id, current_status, timeout=60):
74
        super(_port_wait, self)._wait(
75
            'Port', port_id, self.client.wait_port, current_status,
76
            timeout=timeout)
77

    
78

    
79
class _port_wait(_service_wait):
80

    
81
    def _wait(self, net_id, current_status, timeout=60):
82
        super(_network_wait, self)._wait(
83
            'Network', net_id, self.client.wait_network, current_status,
84
            timeout=timeout)
85

    
86

    
87
class _init_network(_command_init):
88
    @errors.generic.all
89
    @addLogSettings
90
    def _run(self, service='network'):
91
        if getattr(self, 'cloud', None):
92
            base_url = self._custom_url(service) or self._custom_url(
93
                'network')
94
            if base_url:
95
                token = self._custom_token(service) or self._custom_token(
96
                    'network') or self.config.get_cloud('token')
97
                self.client = CycladesNetworkClient(
98
                  base_url=base_url, token=token)
99
                return
100
        else:
101
            self.cloud = 'default'
102
        if getattr(self, 'auth_base', False):
103
            network_endpoints = self.auth_base.get_service_endpoints(
104
                self._custom_type('network') or 'network',
105
                self._custom_version('network') or '')
106
            base_url = network_endpoints['publicURL']
107
            token = self.auth_base.token
108
            self.client = CycladesNetworkClient(base_url=base_url, token=token)
109
        else:
110
            raise CLIBaseUrlError(service='network')
111

    
112
    def main(self):
113
        self._run()
114

    
115

    
116
@command(network_cmds)
117
class network_list(_init_network, _optional_json, _name_filter, _id_filter):
118
    """List networks
119
    Use filtering arguments (e.g., --name-like) to manage long server lists
120
    """
121

    
122
    arguments = dict(
123
        detail=FlagArgument('show detailed output', ('-l', '--details')),
124
        more=FlagArgument(
125
            'output results in pages (-n to set items per page, default 10)',
126
            '--more'),
127
        user_id=ValueArgument(
128
            'show only networks belonging to user with this id', '--user-id')
129
    )
130

    
131
    def _filter_by_user_id(self, nets):
132
        return filter_dicts_by_dict(nets, dict(user_id=self['user_id'])) if (
133
            self['user_id']) else nets
134

    
135
    @errors.generic.all
136
    @errors.cyclades.connection
137
    def _run(self):
138
        nets = self.client.list_networks()
139
        nets = self._filter_by_user_id(nets)
140
        nets = self._filter_by_name(nets)
141
        nets = self._filter_by_id(nets)
142
        if not self['detail']:
143
            nets = [dict(
144
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
145
        kwargs = dict()
146
        if self['more']:
147
            kwargs['out'] = StringIO()
148
            kwargs['title'] = ()
149
        self._print(nets, **kwargs)
150
        if self['more']:
151
            pager(kwargs['out'].getvalue())
152

    
153
    def main(self):
154
        super(self.__class__, self)._run()
155
        self._run()
156

    
157

    
158
@command(network_cmds)
159
class network_info(_init_network, _optional_json):
160
    """Get details about a network"""
161

    
162
    @errors.generic.all
163
    @errors.cyclades.connection
164
    @errors.cyclades.network_id
165
    def _run(self, network_id):
166
        net = self.client.get_network_details(network_id)
167
        self._print(net, self.print_dict)
168

    
169
    def main(self, network_id):
170
        super(self.__class__, self)._run()
171
        self._run(network_id=network_id)
172

    
173

    
174
class NetworkTypeArgument(ValueArgument):
175

    
176
    types = ('CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
177

    
178
    @property
179
    def value(self):
180
        return getattr(self, '_value', None)
181

    
182
    @value.setter
183
    def value(self, new_value):
184
        if new_value and new_value.upper() in self.types:
185
            self._value = new_value.upper()
186
        elif new_value:
187
            raise CLIInvalidArgument(
188
                'Invalid network type %s' % new_value, details=[
189
                    'Valid types: %s' % ', '.join(self.types), ])
190

    
191

    
192
@command(network_cmds)
193
class network_create(_init_network, _optional_json, _network_wait):
194
    """Create a new network"""
195

    
196
    arguments = dict(
197
        name=ValueArgument('Network name', '--name'),
198
        shared=FlagArgument(
199
            'Make network shared (special privileges required)', '--shared'),
200
        network_type=NetworkTypeArgument(
201
            'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
202
            '--type'),
203
        wait=FlagArgument('Wait network to build', ('-w', '--wait')),
204
    )
205
    required = ('network_type', )
206

    
207
    @errors.generic.all
208
    @errors.cyclades.connection
209
    @errors.cyclades.network_type
210
    def _run(self, network_type):
211
        net = self.client.create_network(
212
            network_type, name=self['name'], shared=self['shared'])
213
        if self['wait']:
214
            self._wait(net['id'], net['status'])
215
        self._print(net, self.print_dict)
216

    
217
    def main(self):
218
        super(self.__class__, self)._run()
219
        self._run(network_type=self['network_type'])
220

    
221

    
222
@command(network_cmds)
223
class network_delete(_init_network, _optional_output_cmd):
224
    """Delete a network"""
225

    
226
    @errors.generic.all
227
    @errors.cyclades.connection
228
    @errors.cyclades.network_id
229
    def _run(self, network_id):
230
        r = self.client.delete_network(network_id)
231
        self._optional_output(r)
232

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

    
237

    
238
@command(network_cmds)
239
class network_modify(_init_network, _optional_json):
240
    """Modify network attributes"""
241

    
242
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
243
    required = ['new_name', ]
244

    
245
    @errors.generic.all
246
    @errors.cyclades.connection
247
    @errors.cyclades.network_id
248
    def _run(self, network_id):
249
        r = self.client.update_network(network_id, name=self['new_name'])
250
        self._print(r, self.print_dict)
251

    
252
    def main(self, network_id):
253
        super(self.__class__, self)._run()
254
        self._run(network_id=network_id)
255

    
256

    
257
@command(network_cmds)
258
class network_wait(_init_network, _network_wait):
259
    """Wait for network to finish [PENDING, ACTIVE, DELETED]"""
260

    
261
    arguments = dict(
262
        timeout=IntArgument(
263
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
264
    )
265

    
266
    @errors.generic.all
267
    @errors.cyclades.connection
268
    @errors.cyclades.network_id
269
    def _run(self, network_id, current_status):
270
        net = self.client.get_network_details(network_id)
271
        if net['status'].lower() == current_status.lower():
272
            self._wait(network_id, current_status, timeout=self['timeout'])
273
        else:
274
            self.error(
275
                'Network %s: Cannot wait for status %s, '
276
                'status is already %s' % (
277
                    network_id, current_status, net['status']))
278

    
279
    def main(self, network_id, current_status='PENDING'):
280
        super(self.__class__, self)._run()
281
        self._run(network_id=network_id, current_status=current_status)
282

    
283

    
284
@command(subnet_cmds)
285
class subnet_list(_init_network, _optional_json, _name_filter, _id_filter):
286
    """List subnets
287
    Use filtering arguments (e.g., --name-like) to manage long server lists
288
    """
289

    
290
    arguments = dict(
291
        detail=FlagArgument('show detailed output', ('-l', '--details')),
292
        more=FlagArgument(
293
            'output results in pages (-n to set items per page, default 10)',
294
            '--more')
295
    )
296

    
297
    @errors.generic.all
298
    @errors.cyclades.connection
299
    def _run(self):
300
        nets = self.client.list_subnets()
301
        nets = self._filter_by_name(nets)
302
        nets = self._filter_by_id(nets)
303
        if not self['detail']:
304
            nets = [dict(
305
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
306
        kwargs = dict()
307
        if self['more']:
308
            kwargs['out'] = StringIO()
309
            kwargs['title'] = ()
310
        self._print(nets, **kwargs)
311
        if self['more']:
312
            pager(kwargs['out'].getvalue())
313

    
314
    def main(self):
315
        super(self.__class__, self)._run()
316
        self._run()
317

    
318

    
319
@command(subnet_cmds)
320
class subnet_info(_init_network, _optional_json):
321
    """Get details about a subnet"""
322

    
323
    @errors.generic.all
324
    @errors.cyclades.connection
325
    def _run(self, subnet_id):
326
        net = self.client.get_subnet_details(subnet_id)
327
        self._print(net, self.print_dict)
328

    
329
    def main(self, subnet_id):
330
        super(self.__class__, self)._run()
331
        self._run(subnet_id=subnet_id)
332

    
333

    
334
class AllocationPoolArgument(RepeatableArgument):
335

    
336
    @property
337
    def value(self):
338
        return super(AllocationPoolArgument, self).value or []
339

    
340
    @value.setter
341
    def value(self, new_pools):
342
        new_list = []
343
        for pool in new_pools:
344
            start, comma, end = pool.partition(',')
345
            if not (start and comma and end):
346
                raise CLIInvalidArgument(
347
                    'Invalid allocation pool argument %s' % pool, details=[
348
                    'Allocation values must be of the form:',
349
                    '  <start address>,<end address>'])
350
            new_list.append(dict(start=start, end=end))
351
        self._value = new_list
352

    
353

    
354
@command(subnet_cmds)
355
class subnet_create(_init_network, _optional_json):
356
    """Create a new subnet"""
357

    
358
    arguments = dict(
359
        name=ValueArgument('Subnet name', '--name'),
360
        allocation_pools=AllocationPoolArgument(
361
            'start_address,end_address of allocation pool (can be repeated)'
362
            ' e.g., --alloc-pool=123.45.67.1,123.45.67.8',
363
            '--alloc-pool'),
364
        gateway=ValueArgument('Gateway IP', '--gateway'),
365
        subnet_id=ValueArgument('The id for the subnet', '--id'),
366
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
367
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'),
368
        network_id=ValueArgument('Set the network ID', '--network-id'),
369
        cidr=ValueArgument('Set the CIDR', '--cidr')
370
    )
371
    required = ('network_id', 'cidr')
372

    
373
    @errors.generic.all
374
    @errors.cyclades.connection
375
    @errors.cyclades.network_id
376
    def _run(self, network_id, cidr):
377
        net = self.client.create_subnet(
378
            network_id, cidr,
379
            self['name'], self['allocation_pools'], self['gateway'],
380
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
381
        self._print(net, self.print_dict)
382

    
383
    def main(self):
384
        super(self.__class__, self)._run()
385
        self._run(network_id=self['network_id'], cidr=self['cidr'])
386

    
387

    
388
# @command(subnet_cmds)
389
# class subnet_delete(_init_network, _optional_output_cmd):
390
#     """Delete a subnet"""
391

    
392
#     @errors.generic.all
393
#     @errors.cyclades.connection
394
#     def _run(self, subnet_id):
395
#         r = self.client.delete_subnet(subnet_id)
396
#         self._optional_output(r)
397

    
398
#     def main(self, subnet_id):
399
#         super(self.__class__, self)._run()
400
#         self._run(subnet_id=subnet_id)
401

    
402

    
403
@command(subnet_cmds)
404
class subnet_modify(_init_network, _optional_json):
405
    """Modify the attributes of a subnet"""
406

    
407
    arguments = dict(
408
        new_name=ValueArgument('New name of the subnet', '--name')
409
    )
410
    required = ['new_name']
411

    
412
    @errors.generic.all
413
    @errors.cyclades.connection
414
    def _run(self, subnet_id):
415
        r = self.client.get_subnet_details(subnet_id)
416
        r = self.client.update_subnet(
417
            subnet_id, r['network_id'], name=self['new_name'])
418
        self._print(r, self.print_dict)
419

    
420
    def main(self, subnet_id):
421
        super(self.__class__, self)._run()
422
        self._run(subnet_id=subnet_id)
423

    
424

    
425
@command(port_cmds)
426
class port_list(_init_network, _optional_json):
427
    """List all ports"""
428

    
429
    @errors.generic.all
430
    @errors.cyclades.connection
431
    def _run(self):
432
        net = self.client.list_ports()
433
        self._print(net)
434

    
435
    def main(self):
436
        super(self.__class__, self)._run()
437
        self._run()
438

    
439

    
440
@command(port_cmds)
441
class port_info(_init_network, _optional_json):
442
    """Get details about a port"""
443

    
444
    @errors.generic.all
445
    @errors.cyclades.connection
446
    def _run(self, port_id):
447
        net = self.client.get_port_details(port_id)
448
        self._print(net, self.print_dict)
449

    
450
    def main(self, port_id):
451
        super(self.__class__, self)._run()
452
        self._run(port_id=port_id)
453

    
454

    
455
@command(port_cmds)
456
class port_delete(_init_network, _optional_output_cmd):
457
    """Delete a port (== disconnect server from network)"""
458

    
459
    @errors.generic.all
460
    @errors.cyclades.connection
461
    def _run(self, port_id):
462
        r = self.client.delete_port(port_id)
463
        self._optional_output(r)
464

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

    
469

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

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

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

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

    
489

    
490
@command(port_cmds)
491
class port_create(_init_network, _optional_json, _port_wait):
492
    """Create a new port (== connect server to network)"""
493

    
494
    arguments = dict(
495
        name=ValueArgument('A human readable name', '--name'),
496
        security_group_id=RepeatableArgument(
497
            'Add a security group id (can be repeated)',
498
            ('-g', '--security-group')),
499
        subnet_id=ValueArgument(
500
            'Subnet id for fixed ips (used with --ip-address)',
501
            '--subnet-id'),
502
        ip_address=ValueArgument(
503
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
504
        network_id=ValueArgument('Set the network ID', '--network-id'),
505
        device_id=ValueArgument(
506
            'The device is either a virtual server or a virtual router',
507
            '--device-id'),
508
        wait=FlagArgument('Wait port to be established', ('-w', '--wait')),
509
    )
510
    required = ('network_id', 'device_id')
511

    
512
    @errors.generic.all
513
    @errors.cyclades.connection
514
    @errors.cyclades.network_id
515
    def _run(self, network_id, device_id):
516
        fixed_ips = [dict(
517
            subnet_id=self['subnet_id'], ip_address=self['ip_address'])] if (
518
                self['subnet_id']) else None
519
        r = self.client.create_port(
520
            network_id, device_id,
521
            name=self['name'],
522
            security_groups=self['security_group_id'],
523
            fixed_ips=fixed_ips)
524
        if self['wait']:
525
            self._wait(r['id'], r['status'])
526
        self._print(r, self.print_dict)
527

    
528
    def main(self):
529
        super(self.__class__, self)._run()
530
        self._run(network_id=self['network_id'], device_id=self['device_id'])
531

    
532

    
533
@command(port_cmds)
534
class port_wait(_init_network, _port_wait):
535
    """Wait for port to finish [PENDING, ACTIVE, DELETED]"""
536

    
537
    arguments = dict(
538
        timeout=IntArgument(
539
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
540
    )
541

    
542
    @errors.generic.all
543
    @errors.cyclades.connection
544
    def _run(self, port_id, current_status):
545
        port = self.client.get_port_details(port_id)
546
        if port['status'].lower() == current_status.lower():
547
            self._wait(port_id, current_status, timeout=self['timeout'])
548
        else:
549
            self.error(
550
                'Port %s: Cannot wait for status %s, '
551
                'status is already %s' % (
552
                    port_id, current_status, port['status']))
553

    
554
    def main(self, port_id, current_status='PENDING'):
555
        super(self.__class__, self)._run()
556
        self._run(port_id=port_id, current_status=current_status)
557

    
558

    
559
@command(ip_cmds)
560
class ip_list(_init_network, _optional_json):
561
    """List reserved floating IPs"""
562

    
563
    @errors.generic.all
564
    @errors.cyclades.connection
565
    def _run(self):
566
        self._print(self.client.list_floatingips())
567

    
568
    def main(self):
569
        super(self.__class__, self)._run()
570
        self._run()
571

    
572

    
573
@command(ip_cmds)
574
class ip_info(_init_network, _optional_json):
575
    """Get details on a floating IP"""
576

    
577
    @errors.generic.all
578
    @errors.cyclades.connection
579
    def _run(self, ip_id):
580
        self._print(self.client.get_floatingip_details(ip_id))
581

    
582
    def main(self, ip_id):
583
        super(self.__class__, self)._run()
584
        self._run(ip_id=ip_id)
585

    
586

    
587
@command(ip_cmds)
588
class ip_create(_init_network, _optional_json):
589
    """Reserve an IP on a network"""
590

    
591
    arguments = dict(
592
        network_id=ValueArgument(
593
            'The network to preserve the IP on', '--network-id'),
594
        ip_address=ValueArgument('Allocate a specific IP address', '--address')
595
    )
596
    required = ('network_id', )
597

    
598
    @errors.generic.all
599
    @errors.cyclades.connection
600
    @errors.cyclades.network_id
601
    def _run(self, network_id):
602
        self._print(
603
            self.client.create_floatingip(
604
                network_id, floating_ip_address=self['ip_address']),
605
            self.print_dict)
606

    
607
    def main(self):
608
        super(self.__class__, self)._run()
609
        self._run(network_id=self['network_id'])
610

    
611

    
612
@command(ip_cmds)
613
class ip_delete(_init_network, _optional_output_cmd):
614
    """Unreserve an IP (also delete the port, if attached)"""
615

    
616
    def _run(self, ip_id):
617
        self._optional_output(self.client.floatingip_delete(ip_id))
618

    
619
    def main(self, ip_id):
620
        super(self.__class__, self)._run()
621
        self._run(ip_id=ip_id)
622

    
623

    
624
#  Warn users for some importand changes
625

    
626
@command(network_cmds)
627
class network_connect(_init_network, _optional_output_cmd):
628
    """DEPRECATED, use /port create"""
629

    
630
    def main(self):
631
        raise CLISyntaxError('DEPRECATED, replaced by kamaki port create')
632

    
633

    
634
@command(network_cmds)
635
class network_disconnect(_init_network):
636
    """DEPRECATED, /use port delete"""
637

    
638
    def main(self):
639
        raise CLISyntaxError('DEPRECATED, replaced by kamaki port delete')