Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / network.py @ 264a13f7

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

    
48

    
49
network_cmds = CommandTree('network', 'Networking API network commands')
50
port_cmds = CommandTree('port', 'Networking API network commands')
51
subnet_cmds = CommandTree('subnet', 'Networking API network commands')
52
_commands = [network_cmds, port_cmds, subnet_cmds]
53

    
54

    
55
about_authentication = '\nUser Authentication:\
56
    \n* to check authentication: /user authenticate\
57
    \n* to set authentication token: /config set cloud.<cloud>.token <token>'
58

    
59

    
60
class _init_network(_command_init):
61
    @errors.generic.all
62
    @addLogSettings
63
    def _run(self, service='network'):
64
        if getattr(self, 'cloud', None):
65
            base_url = self._custom_url(service) or self._custom_url(
66
                'network')
67
            if base_url:
68
                token = self._custom_token(service) or self._custom_token(
69
                    'network') or self.config.get_cloud('token')
70
                self.client = CycladesNetworkClient(
71
                  base_url=base_url, token=token)
72
                return
73
        else:
74
            self.cloud = 'default'
75
        if getattr(self, 'auth_base', False):
76
            cyclades_endpoints = self.auth_base.get_service_endpoints(
77
                self._custom_type('network') or 'network',
78
                self._custom_version('network') or '')
79
            base_url = cyclades_endpoints['publicURL']
80
            token = self.auth_base.token
81
            self.client = CycladesNetworkClient(base_url=base_url, token=token)
82
        else:
83
            raise CLIBaseUrlError(service='network')
84

    
85
    def main(self):
86
        self._run()
87

    
88

    
89
@command(network_cmds)
90
class network_list(_init_network, _optional_json, _name_filter, _id_filter):
91
    """List networks
92
    Use filtering arguments (e.g., --name-like) to manage long server lists
93
    """
94

    
95
    arguments = dict(
96
        detail=FlagArgument('show detailed output', ('-l', '--details')),
97
        more=FlagArgument(
98
            'output results in pages (-n to set items per page, default 10)',
99
            '--more'),
100
        user_id=ValueArgument(
101
            'show only networks belonging to user with this id', '--user-id')
102
    )
103

    
104
    def _filter_by_user_id(self, nets):
105
        return filter_dicts_by_dict(nets, dict(user_id=self['user_id'])) if (
106
            self['user_id']) else nets
107

    
108
    @errors.generic.all
109
    @errors.cyclades.connection
110
    def _run(self):
111
        nets = self.client.list_networks()
112
        nets = self._filter_by_user_id(nets)
113
        nets = self._filter_by_name(nets)
114
        nets = self._filter_by_id(nets)
115
        if not self['detail']:
116
            nets = [dict(
117
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
118
        kwargs = dict()
119
        if self['more']:
120
            kwargs['out'] = StringIO()
121
            kwargs['title'] = ()
122
        self._print(nets, **kwargs)
123
        if self['more']:
124
            pager(kwargs['out'].getvalue())
125

    
126
    def main(self):
127
        super(self.__class__, self)._run()
128
        self._run()
129

    
130

    
131
@command(network_cmds)
132
class network_info(_init_network, _optional_json):
133
    """Get details about a network"""
134

    
135
    @errors.generic.all
136
    @errors.cyclades.connection
137
    @errors.cyclades.network_id
138
    def _run(self, network_id):
139
        net = self.client.get_network_details(network_id)
140
        self._print(net, self.print_dict)
141

    
142
    def main(self, network_id):
143
        super(self.__class__, self)._run()
144
        self._run(network_id=network_id)
145

    
146

    
147
class NetworkTypeArgument(ValueArgument):
148

    
149
    types = ('CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
150

    
151
    @property
152
    def value(self):
153
        return getattr(self, '_value', None)
154

    
155
    @value.setter
156
    def value(self, new_value):
157
        if new_value and new_value.upper() in self.types:
158
            self._value = new_value.upper()
159
        elif new_value:
160
            raise CLIInvalidArgument(
161
                'Invalid network type %s' % new_value, details=[
162
                    'Valid types: %s' % ', '.join(self.types), ])
163

    
164

    
165
@command(network_cmds)
166
class network_create(_init_network, _optional_json):
167
    """Create a new network"""
168

    
169
    arguments = dict(
170
        name=ValueArgument('Network name', '--name'),
171
        shared=FlagArgument(
172
            'Make network shared (special privileges required)', '--shared'),
173
        network_type=NetworkTypeArgument(
174
            'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
175
            '--type')
176
    )
177
    required = ('network_type', )
178

    
179
    @errors.generic.all
180
    @errors.cyclades.connection
181
    @errors.cyclades.network_type
182
    def _run(self, network_type):
183
        net = self.client.create_network(
184
            network_type, name=self['name'], shared=self['shared'])
185
        self._print(net, self.print_dict)
186

    
187
    def main(self):
188
        super(self.__class__, self)._run()
189
        self._run(network_type=self['network_type'])
190

    
191

    
192
@command(network_cmds)
193
class network_delete(_init_network, _optional_output_cmd):
194
    """Delete a network"""
195

    
196
    @errors.generic.all
197
    @errors.cyclades.connection
198
    @errors.cyclades.network_id
199
    def _run(self, network_id):
200
        r = self.client.delete_network(network_id)
201
        self._optional_output(r)
202

    
203
    def main(self, network_id):
204
        super(self.__class__, self)._run()
205
        self._run(network_id=network_id)
206

    
207

    
208
@command(network_cmds)
209
class network_modify(_init_network, _optional_json):
210
    """Modify network attributes"""
211

    
212
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
213
    required = ['new_name', ]
214

    
215
    @errors.generic.all
216
    @errors.cyclades.connection
217
    @errors.cyclades.network_id
218
    def _run(self, network_id):
219
        r = self.client.update_network(network_id, name=self['new_name'])
220
        self._print(r, self.print_dict)
221

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

    
226

    
227
@command(subnet_cmds)
228
class subnet_list(_init_network, _optional_json, _name_filter, _id_filter):
229
    """List subnets
230
    Use filtering arguments (e.g., --name-like) to manage long server lists
231
    """
232

    
233
    arguments = dict(
234
        detail=FlagArgument('show detailed output', ('-l', '--details')),
235
        more=FlagArgument(
236
            'output results in pages (-n to set items per page, default 10)',
237
            '--more')
238
    )
239

    
240
    @errors.generic.all
241
    @errors.cyclades.connection
242
    def _run(self):
243
        nets = self.client.list_subnets()
244
        nets = self._filter_by_name(nets)
245
        nets = self._filter_by_id(nets)
246
        if not self['detail']:
247
            nets = [dict(
248
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
249
        kwargs = dict()
250
        if self['more']:
251
            kwargs['out'] = StringIO()
252
            kwargs['title'] = ()
253
        self._print(nets, **kwargs)
254
        if self['more']:
255
            pager(kwargs['out'].getvalue())
256

    
257
    def main(self):
258
        super(self.__class__, self)._run()
259
        self._run()
260

    
261

    
262
@command(subnet_cmds)
263
class subnet_info(_init_network, _optional_json):
264
    """Get details about a subnet"""
265

    
266
    @errors.generic.all
267
    @errors.cyclades.connection
268
    def _run(self, subnet_id):
269
        net = self.client.get_subnet_details(subnet_id)
270
        self._print(net, self.print_dict)
271

    
272
    def main(self, subnet_id):
273
        super(self.__class__, self)._run()
274
        self._run(subnet_id=subnet_id)
275

    
276

    
277
class AllocationPoolArgument(RepeatableArgument):
278

    
279
    @property
280
    def value(self):
281
        return super(AllocationPoolArgument, self).value or []
282

    
283
    @value.setter
284
    def value(self, new_pools):
285
        new_list = []
286
        for pool in new_pools:
287
            start, comma, end = pool.partition(',')
288
            if not (start and comma and end):
289
                raise CLIInvalidArgument(
290
                    'Invalid allocation pool argument %s' % pool, details=[
291
                    'Allocation values must be of the form:',
292
                    '  <start address>,<end address>'])
293
            new_list.append(dict(start=start, end=end))
294
        self._value = new_list
295

    
296

    
297
@command(subnet_cmds)
298
class subnet_create(_init_network, _optional_json):
299
    """Create a new subnet"""
300

    
301
    arguments = dict(
302
        name=ValueArgument('Subnet name', '--name'),
303
        allocation_pools=AllocationPoolArgument(
304
            'start_address,end_address of allocation pool (can be repeated)'
305
            ' e.g., --alloc-pool=123.45.67.1,123.45.67.8',
306
            '--alloc-pool'),
307
        gateway=ValueArgument('Gateway IP', '--gateway'),
308
        subnet_id=ValueArgument('The id for the subnet', '--id'),
309
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
310
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'),
311
        network_id=ValueArgument('Set the network ID', '--network-id'),
312
        cidr=ValueArgument('Set the CIDR', '--cidr')
313
    )
314
    required = ('network_id', 'cidr')
315

    
316
    @errors.generic.all
317
    @errors.cyclades.connection
318
    @errors.cyclades.network_id
319
    def _run(self, network_id, cidr):
320
        net = self.client.create_subnet(
321
            network_id, cidr,
322
            self['name'], self['allocation_pools'], self['gateway'],
323
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
324
        self._print(net, self.print_dict)
325

    
326
    def main(self):
327
        super(self.__class__, self)._run()
328
        self._run(network_id=self['network_id'], cidr=self['cidr'])
329

    
330

    
331
# @command(subnet_cmds)
332
# class subnet_delete(_init_network, _optional_output_cmd):
333
#     """Delete a subnet"""
334

    
335
#     @errors.generic.all
336
#     @errors.cyclades.connection
337
#     def _run(self, subnet_id):
338
#         r = self.client.delete_subnet(subnet_id)
339
#         self._optional_output(r)
340

    
341
#     def main(self, subnet_id):
342
#         super(self.__class__, self)._run()
343
#         self._run(subnet_id=subnet_id)
344

    
345

    
346
@command(subnet_cmds)
347
class subnet_modify(_init_network, _optional_json):
348
    """Modify the attributes of a subnet"""
349

    
350
    arguments = dict(
351
        new_name=ValueArgument('New name of the subnet', '--name')
352
    )
353
    required = ['new_name']
354

    
355
    @errors.generic.all
356
    @errors.cyclades.connection
357
    def _run(self, subnet_id):
358
        r = self.client.get_subnet_details(subnet_id)
359
        r = self.client.update_subnet(
360
            subnet_id, r['network_id'], name=self['new_name'])
361
        self._print(r, self.print_dict)
362

    
363
    def main(self, subnet_id):
364
        super(self.__class__, self)._run()
365
        self._run(subnet_id=subnet_id)
366

    
367

    
368
@command(port_cmds)
369
class port_list(_init_network, _optional_json):
370
    """List all ports"""
371

    
372
    @errors.generic.all
373
    @errors.cyclades.connection
374
    def _run(self):
375
        net = self.client.list_ports()
376
        self._print(net)
377

    
378
    def main(self):
379
        super(self.__class__, self)._run()
380
        self._run()
381

    
382

    
383
@command(port_cmds)
384
class port_info(_init_network, _optional_json):
385
    """Get details about a port"""
386

    
387
    @errors.generic.all
388
    @errors.cyclades.connection
389
    def _run(self, port_id):
390
        net = self.client.get_port_details(port_id)
391
        self._print(net, self.print_dict)
392

    
393
    def main(self, port_id):
394
        super(self.__class__, self)._run()
395
        self._run(port_id=port_id)
396

    
397

    
398
@command(port_cmds)
399
class port_delete(_init_network, _optional_output_cmd):
400
    """Delete a port (== disconnect server from network)"""
401

    
402
    @errors.generic.all
403
    @errors.cyclades.connection
404
    def _run(self, port_id):
405
        r = self.client.delete_port(port_id)
406
        self._optional_output(r)
407

    
408
    def main(self, port_id):
409
        super(self.__class__, self)._run()
410
        self._run(port_id=port_id)
411

    
412

    
413
@command(port_cmds)
414
class port_modify(_init_network, _optional_json):
415
    """Modify the attributes of a port"""
416

    
417
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
418
    required = ['new_name', ]
419

    
420
    @errors.generic.all
421
    @errors.cyclades.connection
422
    def _run(self, port_id):
423
        r = self.client.get_port_details(port_id)
424
        r = self.client.update_port(
425
            port_id, r['network_id'], name=self['new_name'])
426
        self._print(r, self.print_dict)
427

    
428
    def main(self, port_id):
429
        super(self.__class__, self)._run()
430
        self._run(port_id=port_id)
431

    
432

    
433
@command(port_cmds)
434
class port_create(_init_network, _optional_json):
435
    """Create a new port (== connect server to network)"""
436

    
437
    arguments = dict(
438
        name=ValueArgument('A human readable name', '--name'),
439
        security_group_id=RepeatableArgument(
440
            'Add a security group id (can be repeated)',
441
            ('-g', '--security-group')),
442
        subnet_id=ValueArgument(
443
            'Subnet id for fixed ips (used with --ip-address)',
444
            '--subnet-id'),
445
        ip_address=ValueArgument(
446
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
447
        network_id=ValueArgument('Set the network ID', '--network-id'),
448
        device_id=ValueArgument(
449
            'The device is either a virtual server or a virtual router',
450
            '--device-id')
451
    )
452
    retuired = ('network_id', 'device_id')
453

    
454
    @errors.generic.all
455
    @errors.cyclades.connection
456
    @errors.cyclades.network_id
457
    def _run(self, network_id, device_id):
458
        fixed_ips = [dict(
459
            subnet_id=self['subnet_id'], ip_address=self['ip_address'])] if (
460
                self['subnet_id']) else None
461
        r = self.client.create_port(
462
            network_id, device_id,
463
            name=self['name'],
464
            security_groups=self['security_group_id'],
465
            fixed_ips=fixed_ips)
466
        self._print(r, self.print_dict)
467

    
468
    def main(self):
469
        super(self.__class__, self)._run()
470
        self._run(network_id=self['network_id'], device_id=self['device_id'])
471

    
472

    
473
#  Warn users for some importand changes
474

    
475

    
476
@command(network_cmds)
477
class network_connect(_init_network, _optional_output_cmd):
478
    """DEPRECATED, use /port create"""
479

    
480
    def main(self):
481
        raise CLISyntaxError('DEPRECATED, replaced by kamaki port create')
482

    
483

    
484
@command(network_cmds)
485
class network_disconnect(_init_network):
486
    """DEPRECATED, /use port delete"""
487

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