Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / network.py @ 11cc86af

History | View | Annotate | Download (14.5 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
                'compute')
67
            if base_url:
68
                token = self._custom_token(service) or self._custom_token(
69
                    'compute') 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('compute') or 'compute',
78
                self._custom_version('compute') 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
        detail = self['detail'] or self['user_id']
112
        nets = self.client.list_networks(detail=detail)
113
        nets = self._filter_by_user_id(nets)
114
        nets = self._filter_by_name(nets)
115
        nets = self._filter_by_id(nets)
116
        if detail and not self['detail']:
117
            nets = [dict(
118
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
119
        kwargs = dict()
120
        if self['more']:
121
            kwargs['out'] = StringIO()
122
            kwargs['title'] = ()
123
        self._print(nets, **kwargs)
124
        if self['more']:
125
            pager(kwargs['out'].getvalue())
126

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

    
131

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

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

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

    
147

    
148
@command(network_cmds)
149
class network_create(_init_network, _optional_json):
150
    """Create a new network
151
    Valid network types: CUSTOM MAC_FILTERED IP_LESS_ROUTED PHYSICAL_VLAN
152
    """
153

    
154
    arguments = dict(
155
        name=ValueArgument('Network name', '--name'),
156
        shared=FlagArgument(
157
            'Make network shared (special privileges required)', '--shared')
158
    )
159

    
160
    @errors.generic.all
161
    @errors.cyclades.connection
162
    @errors.cyclades.network_type
163
    def _run(self, network_type):
164
        net = self.client.create_network(
165
            network_type, name=self['name'], shared=self['shared'])
166
        self._print(net, self.print_dict)
167

    
168
    def main(self, network_type):
169
        super(self.__class__, self)._run()
170
        self._run(network_type=network_type)
171

    
172

    
173
@command(network_cmds)
174
class network_delete(_init_network, _optional_output_cmd):
175
    """Delete a network"""
176

    
177
    @errors.generic.all
178
    @errors.cyclades.connection
179
    @errors.cyclades.network_id
180
    def _run(self, network_id):
181
        r = self.client.delete_network(network_id)
182
        self._optional_output(r)
183

    
184
    def main(self, network_id):
185
        super(self.__class__, self)._run()
186
        self._run(network_id=network_id)
187

    
188

    
189
@command(network_cmds)
190
class network_set(_init_network, _optional_json):
191
    """Set an attribute of a network, leave the rest untouched (update)
192
    Only "--name" is supported for now
193
    """
194

    
195
    arguments = dict(name=ValueArgument('New name of the network', '--name'))
196

    
197
    @errors.generic.all
198
    @errors.cyclades.connection
199
    @errors.cyclades.network_id
200
    def _run(self, network_id):
201
        if self['name'] in (None, ):
202
            raise CLISyntaxError(
203
                'Missing network attributes to update',
204
                details=[
205
                    'At least one if the following is expected:',
206
                    '  --name=<new name>'])
207
        r = self.client.update_network(network_id, name=self['name'])
208
        self._print(r, self.print_dict)
209

    
210
    def main(self, network_id):
211
        super(self.__class__, self)._run()
212
        self._run(network_id=network_id)
213

    
214

    
215
@command(subnet_cmds)
216
class subnet_list(_init_network, _optional_json, _name_filter, _id_filter):
217
    """List subnets
218
    Use filtering arguments (e.g., --name-like) to manage long server lists
219
    """
220

    
221
    arguments = dict(
222
        detail=FlagArgument('show detailed output', ('-l', '--details')),
223
        more=FlagArgument(
224
            'output results in pages (-n to set items per page, default 10)',
225
            '--more'),
226
        user_id=ValueArgument(
227
            'show only subnets belonging to user with this id', '--user-id')
228
    )
229

    
230
    def _filter_by_user_id(self, nets):
231
        return filter_dicts_by_dict(nets, dict(user_id=self['user_id'])) if (
232
            self['user_id']) else nets
233

    
234
    @errors.generic.all
235
    @errors.cyclades.connection
236
    def _run(self):
237
        detail = self['detail'] or self['user_id']
238
        nets = self.client.list_subnets()
239
        nets = self._filter_by_user_id(nets)
240
        nets = self._filter_by_name(nets)
241
        nets = self._filter_by_id(nets)
242
        if detail and not self['detail']:
243
            nets = [dict(
244
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
245
        kwargs = dict()
246
        if self['more']:
247
            kwargs['out'] = StringIO()
248
            kwargs['title'] = ()
249
        self._print(nets, **kwargs)
250
        if self['more']:
251
            pager(kwargs['out'].getvalue())
252

    
253
    def main(self):
254
        super(self.__class__, self)._run()
255
        self._run()
256

    
257

    
258
@command(subnet_cmds)
259
class subnet_info(_init_network, _optional_json):
260
    """Get details about a subnet"""
261

    
262
    @errors.generic.all
263
    @errors.cyclades.connection
264
    def _run(self, subnet_id):
265
        net = self.client.get_subnet_details(subnet_id)
266
        self._print(net, self.print_dict)
267

    
268
    def main(self, subnet_id):
269
        super(self.__class__, self)._run()
270
        self._run(subnet_id=subnet_id)
271

    
272

    
273
class AllocationPoolArgument(RepeatableArgument):
274

    
275
    @property
276
    def value(self):
277
        return super(AllocationPoolArgument, self).value or []
278

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

    
292

    
293
@command(subnet_cmds)
294
class subnet_create(_init_network, _optional_json):
295
    """Create a new subnet
296
    """
297

    
298
    arguments = dict(
299
        name=ValueArgument('Subnet name', '--name'),
300
        allocation_pools=AllocationPoolArgument(
301
            'start_address,end_address of allocation pool (can be repeated)'
302
            ' e.g., --alloc-pool=123.45.67.1,123.45.67.8',
303
            '--alloc-pool'),
304
        gateway=ValueArgument('Gateway IP', '--gateway'),
305
        subnet_id=ValueArgument('The id for the subnet', '--id'),
306
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
307
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp')
308
    )
309

    
310
    @errors.generic.all
311
    @errors.cyclades.connection
312
    @errors.cyclades.network_id
313
    def _run(self, network_id, cidr):
314
        net = self.client.create_subnet(
315
            network_id, cidr,
316
            self['name'], self['allocation_pools'], self['gateway'],
317
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
318
        self._print(net, self.print_dict)
319

    
320
    def main(self, network_id, cidr):
321
        super(self.__class__, self)._run()
322
        self._run(network_id=network_id, cidr=cidr)
323

    
324

    
325
# @command(subnet_cmds)
326
# class subnet_delete(_init_network, _optional_output_cmd):
327
#     """Delete a subnet"""
328

    
329
#     @errors.generic.all
330
#     @errors.cyclades.connection
331
#     def _run(self, subnet_id):
332
#         r = self.client.delete_subnet(subnet_id)
333
#         self._optional_output(r)
334

    
335
#     def main(self, subnet_id):
336
#         super(self.__class__, self)._run()
337
#         self._run(subnet_id=subnet_id)
338

    
339

    
340
@command(subnet_cmds)
341
class subnet_set(_init_network, _optional_json):
342
    """Set an attribute of a subnet, leave the rest untouched (update)
343
    Only "--name" is supported for now
344
    """
345

    
346
    arguments = dict(name=ValueArgument('New name of the subnet', '--name'))
347

    
348
    @errors.generic.all
349
    @errors.cyclades.connection
350
    def _run(self, subnet_id):
351
        if self['name'] in (None, ):
352
            raise CLISyntaxError(
353
                'Missing subnet attributes to update',
354
                details=[
355
                    'At least one if the following is expected:',
356
                    '  --name=<new name>'])
357
        r = self.client.get_subnet_details(subnet_id)
358
        r = self.client.update_subnet(
359
            subnet_id, r['network_id'], name=self['name'])
360
        self._print(r, self.print_dict)
361

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

    
366

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

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

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

    
381

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

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

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

    
396

    
397
@command(port_cmds)
398
class port_delete(_init_network, _optional_output_cmd):
399
    """Delete a port"""
400

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

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

    
411

    
412
@command(port_cmds)
413
class port_set(_init_network, _optional_json):
414
    """Set an attribute of a port, leave the rest untouched (update)
415
    Only "--name" is supported for now
416
    """
417

    
418
    arguments = dict(name=ValueArgument('New name of the port', '--name'))
419

    
420
    @errors.generic.all
421
    @errors.cyclades.connection
422
    def _run(self, port_id):
423
        if self['name'] in (None, ):
424
            raise CLISyntaxError(
425
                'Missing port attributes to update',
426
                details=[
427
                    'At least one if the following is expected:',
428
                    '  --name=<new name>'])
429
        r = self.client.get_port_details(port_id)
430
        r = self.client.update_port(
431
            port_id, r['network_id'], name=self['name'])
432
        self._print(r, self.print_dict)
433

    
434
    def main(self, port_id):
435
        super(self.__class__, self)._run()
436
        self._run(port_id=port_id)
437

    
438

    
439
#@command(port_cmds)
440
#class port_create(_init_network, _optional_json):
441
#