Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 439826ec

History | View | Annotate | Download (32.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 kamaki.cli import command
35
from kamaki.cli.command_tree import CommandTree
36
from kamaki.cli.utils import print_dict, print_list, print_items
37
from kamaki.cli.errors import raiseCLIError, CLISyntaxError
38
from kamaki.clients.cyclades import CycladesClient, ClientError
39
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
40
from kamaki.cli.argument import ProgressBarArgument, DateArgument, IntArgument
41
from kamaki.cli.commands import _command_init
42

    
43
from base64 import b64encode
44
from os.path import exists
45

    
46

    
47
server_cmds = CommandTree('server',
48
    'Compute/Cyclades API server commands')
49
flavor_cmds = CommandTree('flavor',
50
    'Compute/Cyclades API flavor commands')
51
image_cmds = CommandTree('image',
52
    'Compute/Cyclades or Glance API image commands')
53
network_cmds = CommandTree('network',
54
    'Compute/Cyclades API network commands')
55
_commands = [server_cmds, flavor_cmds, image_cmds, network_cmds]
56

    
57

    
58
about_authentication = '\n  User Authentication:\
59
    \n    to check authentication: /astakos authenticate\
60
    \n    to set authentication token: /config set token <token>'
61

    
62
howto_personality = [
63
    'Defines a file to be injected to VMs personality.',
64
    'Personality value syntax: PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]',
65
    '  PATH: of local file to be injected',
66
    '  SERVER_PATH: destination location inside server Image',
67
    '  OWNER: user id of destination file owner',
68
    '  GROUP: group id or name to own destination file',
69
    '  MODEL: permition in octal (e.g. 0777 or o+rwx)']
70

    
71

    
72
def raise_if_connection_error(err, base_url='compute.url'):
73
    if err.status == 401:
74
        raiseCLIError(err, 'Authorization failed', details=[
75
            'Make sure a valid token is provided:',
76
            '  to check if the token is valid: /astakos authenticate',
77
            '  to set a token: /config set [.server.]token <token>',
78
            '  to get current token: /config get [server.]token'])
79
    elif err.status in range(-12, 200) + [403, 500]:
80
        raiseCLIError(err, details=[
81
            'Check if service is up or set to %s' % base_url,
82
            '  to get service url: /config get %s' % base_url,
83
            '  to set service url: /config set %s <URL>' % base_url]
84
        )
85

    
86

    
87
class _init_cyclades(_command_init):
88
    def main(self, service='compute'):
89
        token = self.config.get(service, 'token')\
90
            or self.config.get('global', 'token')
91
        base_url = self.config.get(service, 'url')\
92
            or self.config.get('global', 'url')
93
        self.client = CycladesClient(base_url=base_url, token=token)
94

    
95

    
96
@command(server_cmds)
97
class server_list(_init_cyclades):
98
    """List Virtual Machines accessible by user
99
    """
100

    
101
    __doc__ += about_authentication
102

    
103
    arguments = dict(
104
        detail=FlagArgument('show detailed output', '-l'),
105
        since=DateArgument(
106
            'show only items since date (\' d/m/Y H:M:S \')',
107
            '--since'),
108
        limit=IntArgument('limit the number of VMs to list', '-n'),
109
        more=FlagArgument(
110
            'output results in pages (-n to set items per page, default 10)',
111
            '--more')
112
    )
113

    
114
    def _make_results_pretty(self, servers):
115
        for server in servers:
116
            addr_dict = {}
117
            if 'attachments' in server:
118
                for addr in server['attachments']['values']:
119
                    ips = addr.pop('values', [])
120
                    for ip in ips:
121
                        addr['IPv%s' % ip['version']] = ip['addr']
122
                    if 'firewallProfile' in addr:
123
                        addr['firewall'] = addr.pop('firewallProfile')
124
                    addr_dict[addr.pop('id')] = addr
125
                server['attachments'] = addr_dict if addr_dict else None
126
            if 'metadata' in server:
127
                server['metadata'] = server['metadata']['values']
128

    
129
    def main(self):
130
        super(self.__class__, self).main()
131
        try:
132
            servers = self.client.list_servers(self['detail'], self['since'])
133
            if self['detail']:
134
                self._make_results_pretty(servers)
135
        except ClientError as ce:
136
            if ce.status == 400 and 'changes-since' in ('%s' % ce):
137
                raiseCLIError(None,
138
                    'Incorrect date format for --since',
139
                    details=['Accepted date format: d/m/y'])
140
            raise_if_connection_error(ce)
141
            raiseCLIError(ce)
142
        except Exception as err:
143
            raiseCLIError(err)
144
        if self['more']:
145
            print_items(
146
                servers,
147
                page_size=self['limit'] if self['limit'] else 10)
148
        else:
149
            print_items(
150
                servers[:self['limit'] if self['limit'] else len(servers)])
151

    
152

    
153
@command(server_cmds)
154
class server_info(_init_cyclades):
155
    """Detailed information on a Virtual Machine
156
    Contains:
157
    - name, id, status, create/update dates
158
    - network interfaces
159
    - metadata (e.g. os, superuser) and diagnostics
160
    - hardware flavor and os image ids
161
    """
162

    
163
    @classmethod
164
    def _print(self, server):
165
        addr_dict = {}
166
        if 'attachments' in server:
167
            atts = server.pop('attachments')
168
            for addr in atts['values']:
169
                ips = addr.pop('values', [])
170
                for ip in ips:
171
                    addr['IPv%s' % ip['version']] = ip['addr']
172
                if 'firewallProfile' in addr:
173
                    addr['firewall'] = addr.pop('firewallProfile')
174
                addr_dict[addr.pop('id')] = addr
175
            server['attachments'] = addr_dict if addr_dict else None
176
        if 'metadata' in server:
177
            server['metadata'] = server['metadata']['values']
178
        print_dict(server, ident=1)
179

    
180
    def main(self, server_id):
181
        super(self.__class__, self).main()
182
        try:
183
            server = self.client.get_server_details(int(server_id))
184
        except ValueError as err:
185
            raiseCLIError(err, 'Server id must be a positive integer', 1)
186
        except ClientError as ce:
187
            if ce.status == 404:
188
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
189
            raise_if_connection_error(ce)
190
            raiseCLIError(ce)
191
        except Exception as err:
192
            raiseCLIError(err)
193
        self._print(server)
194

    
195

    
196
class PersonalityArgument(KeyValueArgument):
197
    @property
198
    def value(self):
199
        return self._value if hasattr(self, '_value') else []
200

    
201
    @value.setter
202
    def value(self, newvalue):
203
        if newvalue == self.default:
204
            return self.value
205
        self._value = []
206
        for i, terms in enumerate(newvalue):
207
            termlist = terms.split(',')
208
            if len(termlist) > 5:
209
                raiseCLIError(
210
                CLISyntaxError('Wrong number of terms (should be 1 to 5)'),
211
                details=howto_personality)
212
            path = termlist[0]
213
            if not exists(path):
214
                raiseCLIError(None,
215
                    '--personality: File %s does not exist' % path,
216
                    importance=1,
217
                    details=howto_personality)
218
            self._value.append(dict(path=path))
219
            with open(path) as f:
220
                self._value[i]['contents'] = b64encode(f.read())
221
            try:
222
                self._value[i]['path'] = termlist[1]
223
                self._value[i]['owner'] = termlist[2]
224
                self._value[i]['group'] = termlist[3]
225
                self._value[i]['mode'] = termlist[4]
226
            except IndexError:
227
                pass
228

    
229

    
230
@command(server_cmds)
231
class server_create(_init_cyclades):
232
    """Create a server (aka Virtual Machine)
233
    Parameters:
234
    - name: (single quoted text)
235
    - flavor id: Hardware flavor. Pick one from: /flavor list
236
    - image id: OS images. Pick one from: /image list
237
    """
238

    
239
    arguments = dict(
240
        personality=PersonalityArgument(
241
            ' _ _ _ '.join(howto_personality),
242
            parsed_name='--personality')
243
    )
244

    
245
    def main(self, name, flavor_id, image_id):
246
        super(self.__class__, self).main()
247

    
248
        try:
249
            reply = self.client.create_server(
250
                        name,
251
                        int(flavor_id),
252
                        image_id,
253
                        self['personality']
254
                    )
255
        except ClientError as ce:
256
            if ce.status == 404:
257
                msg = ('%s' % ce).lower()
258
                if 'flavor' in msg:
259
                    raiseCLIError(ce,
260
                        'Flavor id %s not found' % flavor_id,
261
                        details=['How to pick a valid flavor id:',
262
                        '  - get a list of flavor ids: /flavor list',
263
                        '  - details on a flavor: /flavor info <flavor id>'])
264
                elif 'image' in msg:
265
                    raiseCLIError(ce,
266
                        'Image id %s not found' % image_id,
267
                        details=['How to pick a valid image id:',
268
                        '  - get a list of image ids: /image list',
269
                        '  - details on an image: /image info <image id>'])
270
            raise_if_connection_error(ce)
271
            raiseCLIError(ce)
272
        except ValueError as err:
273
            raiseCLIError(err, 'Invalid flavor id %s ' % flavor_id,
274
                details='Flavor id must be a positive integer',
275
                importance=1)
276
        except Exception as err:
277
            raiseCLIError(err, 'Syntax error: %s\n' % err, importance=1)
278
        print_dict(reply)
279

    
280

    
281
@command(server_cmds)
282
class server_rename(_init_cyclades):
283
    """Set/update a server (VM) name
284
    VM names are not unique, therefore multiple server may share the same name
285
    """
286

    
287
    def main(self, server_id, new_name):
288
        super(self.__class__, self).main()
289
        try:
290
            self.client.update_server_name(int(server_id), new_name)
291
        except ClientError as ce:
292
            if ce.status == 404:
293
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
294
            raise_if_connection_error(ce)
295
            raiseCLIError(ce)
296
        except ValueError as err:
297
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
298
                details=['Server id must be positive integer\n'],
299
                importance=1)
300

    
301

    
302
@command(server_cmds)
303
class server_delete(_init_cyclades):
304
    """Delete a server (VM)"""
305

    
306
    def main(self, server_id):
307
        super(self.__class__, self).main()
308
        try:
309
            self.client.delete_server(int(server_id))
310
        except ClientError as ce:
311
            if ce.status == 404:
312
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
313
            raise_if_connection_error(ce)
314
            raiseCLIError(ce)
315
        except ValueError as err:
316
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
317
                details=['Server id must be positive integer\n'],
318
                importance=1)
319
        except Exception as err:
320
            raiseCLIError(err)
321

    
322

    
323
@command(server_cmds)
324
class server_reboot(_init_cyclades):
325
    """Reboot a server (VM)"""
326

    
327
    arguments = dict(
328
        hard=FlagArgument('perform a hard reboot', '-f')
329
    )
330

    
331
    def main(self, server_id):
332
        super(self.__class__, self).main()
333
        try:
334
            self.client.reboot_server(int(server_id), self['hard'])
335
        except ClientError as ce:
336
            if ce.status == 404:
337
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
338
            raise_if_connection_error(ce)
339
            raiseCLIError(ce)
340
        except ValueError as err:
341
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
342
                details=['Server id must be positive integer\n'],
343
                importance=1)
344
        except Exception as err:
345
            raiseCLIError(err)
346

    
347

    
348
@command(server_cmds)
349
class server_start(_init_cyclades):
350
    """Start an existing server (VM)"""
351

    
352
    def main(self, server_id):
353
        super(self.__class__, self).main()
354
        try:
355
            self.client.start_server(int(server_id))
356
        except ClientError as ce:
357
            if ce.status == 404:
358
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
359
            raise_if_connection_error(ce)
360
            raiseCLIError(ce)
361
        except ValueError as err:
362
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
363
                details=['Server id must be positive integer\n'],
364
                importance=1)
365
        except Exception as err:
366
            raiseCLIError(err)
367

    
368

    
369
@command(server_cmds)
370
class server_shutdown(_init_cyclades):
371
    """Shutdown an active server (VM)"""
372

    
373
    def main(self, server_id):
374
        super(self.__class__, self).main()
375
        try:
376
            self.client.shutdown_server(int(server_id))
377
        except ClientError as ce:
378
            if ce.status == 404:
379
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
380
            raise_if_connection_error(ce)
381
            raiseCLIError(ce)
382
        except ValueError as err:
383
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
384
                details=['Server id must be positive integer\n'],
385
                importance=1)
386
        except Exception as err:
387
            raiseCLIError(err)
388

    
389

    
390
@command(server_cmds)
391
class server_console(_init_cyclades):
392
    """Get a VNC console to access an existing server (VM)
393
    Console connection information provided (at least):
394
    - host: (url or address) a VNC host
395
    - port: (int) the gateway to enter VM on host
396
    - password: for VNC authorization
397
    """
398

    
399
    def main(self, server_id):
400
        super(self.__class__, self).main()
401
        try:
402
            reply = self.client.get_server_console(int(server_id))
403
        except ClientError as ce:
404
            if ce.status == 404:
405
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
406
            raise_if_connection_error(ce)
407
            raiseCLIError(ce)
408
        except ValueError as err:
409
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
410
                details=['Server id must be positive integer\n'],
411
                importance=1)
412
        except Exception as err:
413
            raiseCLIError(err)
414
        print_dict(reply)
415

    
416

    
417
@command(server_cmds)
418
class server_firewall(_init_cyclades):
419
    """Set the server (VM) firewall profile on VMs public network
420
    Values for profile:
421
    - DISABLED: Shutdown firewall
422
    - ENABLED: Firewall in normal mode
423
    - PROTECTED: Firewall in secure mode
424
    """
425

    
426
    def main(self, server_id, profile):
427
        super(self.__class__, self).main()
428
        try:
429
            self.client.set_firewall_profile(
430
                int(server_id),
431
                unicode(profile).upper())
432
        except ClientError as ce:
433
            if ce.status == 400 and 'firewall' in '%s' % ce:
434
                raiseCLIError(ce,
435
                    '%s is an unsupported firewall profile' % profile)
436
            elif ce.status == 404:
437
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
438
            raise_if_connection_error(ce)
439
            raiseCLIError(ce)
440
        except ValueError as err:
441
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
442
                details=['Server id must be positive integer\n'],
443
                importance=1)
444
        except Exception as err:
445
            raiseCLIError(err)
446

    
447

    
448
@command(server_cmds)
449
class server_addr(_init_cyclades):
450
    """List the addresses of all network interfaces on a server (VM)"""
451

    
452
    def main(self, server_id):
453
        super(self.__class__, self).main()
454
        try:
455
            reply = self.client.list_server_nics(int(server_id))
456
        except ClientError as ce:
457
            if ce.status == 404:
458
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
459
            raise_if_connection_error(ce)
460
            raiseCLIError(ce)
461
        except ValueError as err:
462
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
463
                details=['Server id must be positive integer\n'],
464
                importance=1)
465
        except Exception as err:
466
            raiseCLIError(err)
467
        print_list(reply, with_enumeration=len(reply) > 1)
468

    
469

    
470
@command(server_cmds)
471
class server_meta(_init_cyclades):
472
    """Get a server's metadatum
473
    Metadata are formed as key:value pairs where key is used to retrieve them
474
    """
475

    
476
    def main(self, server_id, key=''):
477
        super(self.__class__, self).main()
478
        try:
479
            reply = self.client.get_server_metadata(int(server_id), key)
480
        except ClientError as ce:
481
            if ce.status == 404:
482
                msg = 'No metadata with key %s' % key\
483
                if 'Metadata' in '%s' % ce\
484
                else 'Server with id %s not found' % server_id
485
                raiseCLIError(ce, msg)
486
            raise_if_connection_error(ce)
487
            raiseCLIError(ce)
488
        except ValueError as err:
489
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
490
                details=['Server id must be positive integer\n'],
491
                importance=1)
492
        except Exception as err:
493
            raiseCLIError(err)
494
        print_dict(reply)
495

    
496

    
497
@command(server_cmds)
498
class server_setmeta(_init_cyclades):
499
    """set server (VM) metadata
500
    Metadata are formed as key:value pairs, both needed to set one
501
    """
502

    
503
    def main(self, server_id, key, val):
504
        super(self.__class__, self).main()
505
        metadata = {key: val}
506
        try:
507
            reply = self.client.update_server_metadata(int(server_id),
508
                **metadata)
509
        except ClientError as ce:
510
            if ce.status == 404:
511
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
512
            raise_if_connection_error(ce)
513
            raiseCLIError(ce)
514
        except ValueError as err:
515
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
516
                details=['Server id must be positive integer\n'],
517
                importance=1)
518
        except Exception as err:
519
            raiseCLIError(err)
520
        print_dict(reply)
521

    
522

    
523
@command(server_cmds)
524
class server_delmeta(_init_cyclades):
525
    """Delete server (VM) metadata"""
526

    
527
    def main(self, server_id, key):
528
        super(self.__class__, self).main()
529
        try:
530
            self.client.delete_server_metadata(int(server_id), key)
531
        except ClientError as ce:
532
            if ce.status == 404:
533
                msg = 'No metadata with key %s' % key\
534
                if 'Metadata' in '%s' % ce\
535
                else 'Server with id %s not found' % server_id
536
                raiseCLIError(ce, msg)
537
            raise_if_connection_error(ce)
538
            raiseCLIError(ce)
539
        except ValueError as err:
540
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
541
                details=['Server id must be positive integer\n'],
542
                importance=1)
543
        except Exception as err:
544
            raiseCLIError(err)
545

    
546

    
547
@command(server_cmds)
548
class server_stats(_init_cyclades):
549
    """Get server (VM) statistics"""
550

    
551
    def main(self, server_id):
552
        super(self.__class__, self).main()
553
        try:
554
            reply = self.client.get_server_stats(int(server_id))
555
        except ClientError as ce:
556
            if ce.status == 404:
557
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
558
            raise_if_connection_error(ce)
559
            raiseCLIError(ce)
560
        except ValueError as err:
561
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
562
                details=['Server id must be positive integer\n'],
563
                importance=1)
564
        except Exception as err:
565
            raiseCLIError(err)
566
        print_dict(reply, exclude=('serverRef',))
567

    
568

    
569
@command(server_cmds)
570
class server_wait(_init_cyclades):
571
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
572

    
573
    arguments = dict(
574
        progress_bar=ProgressBarArgument(
575
            'do not show progress bar',
576
            '--no-progress-bar',
577
            False
578
        )
579
    )
580

    
581
    def main(self, server_id, currect_status='BUILD'):
582
        super(self.__class__, self).main()
583
        try:
584
            progress_bar = self.arguments['progress_bar']
585
            wait_cb = progress_bar.get_generator(\
586
                'Server %s still in %s mode' % (server_id, currect_status))
587
        except ValueError as err:
588
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
589
                details=['Server id must be positive integer\n'],
590
                importance=1)
591
        except Exception:
592
            wait_cb = None
593
        try:
594
            new_mode = self.client.wait_server(server_id,
595
                currect_status,
596
                wait_cb=wait_cb)
597
            progress_bar.finish()
598
        except KeyboardInterrupt:
599
            print('\nCanceled')
600
            progress_bar.finish()
601
            return
602
        except ClientError as ce:
603
            progress_bar.finish()
604
            if ce.status == 404:
605
                raiseCLIError(ce, 'Server with id %s not found' % server_id)
606
            raise_if_connection_error(ce)
607
            raiseCLIError(ce)
608
        if new_mode:
609
            print('Server %s is now in %s mode' % (server_id, new_mode))
610
        else:
611
            raiseCLIError(None, 'Time out')
612

    
613

    
614
@command(flavor_cmds)
615
class flavor_list(_init_cyclades):
616
    """List available hardware flavors"""
617

    
618
    arguments = dict(
619
        detail=FlagArgument('show detailed output', '-l'),
620
        limit=IntArgument('limit the number of flavors to list', '-n'),
621
        more=FlagArgument(
622
        'output results in pages (-n to set items per page, default 10)',
623
        '--more')
624
    )
625

    
626
    def main(self):
627
        super(self.__class__, self).main()
628
        try:
629
            flavors = self.client.list_flavors(self['detail'])
630
        except ClientError as ce:
631
            raise_if_connection_error(ce)
632
            raiseCLIError(ce)
633
        except Exception as err:
634
            raiseCLIError(err)
635
        if self['more']:
636
            print_items(
637
                flavors,
638
                with_redundancy=self['detail'],
639
                page_size=self['limit'] if self['limit'] else 10)
640
        else:
641
            print_items(
642
                flavors,
643
                with_redundancy=self['detail'],
644
                page_size=self['limit'])
645

    
646

    
647
@command(flavor_cmds)
648
class flavor_info(_init_cyclades):
649
    """Detailed information on a hardware flavor
650
    To get a list of available flavors and flavor ids, try /flavor list
651
    """
652

    
653
    def main(self, flavor_id):
654
        super(self.__class__, self).main()
655
        try:
656
            flavor = self.client.get_flavor_details(int(flavor_id))
657
        except ClientError as ce:
658
            raise_if_connection_error(ce)
659
            raiseCLIError(ce)
660
        except ValueError as err:
661
            raiseCLIError(err,
662
                'Invalid flavor id %s' % flavor_id,
663
                importance=1,
664
                details=['Flavor id must be possitive integer'])
665
        except Exception as err:
666
            raiseCLIError(err)
667
        print_dict(flavor)
668

    
669

    
670
@command(network_cmds)
671
class network_info(_init_cyclades):
672
    """Detailed information on a network
673
    To get a list of available networks and network ids, try /network list
674
    """
675

    
676
    @classmethod
677
    def _make_result_pretty(self, net):
678
        if 'attachments' in net:
679
            att = net['attachments']['values']
680
            count = len(att)
681
            net['attachments'] = att if count else None
682

    
683
    def main(self, network_id):
684
        super(self.__class__, self).main()
685
        try:
686
            network = self.client.get_network_details(int(network_id))
687
            self._make_result_pretty(network)
688
        except ClientError as ce:
689
            raise_if_connection_error(ce)
690
            if ce.status == 404:
691
                raiseCLIError(ce,
692
                    'No network found with id %s' % network_id,
693
                    details=['To see a detailed list of available network ids',
694
                    ' try /network list'])
695
            raiseCLIError(ce)
696
        except ValueError as ve:
697
            raiseCLIError(ve,
698
                'Invalid network_id %s' % network_id,
699
                importance=1,
700
                details=['Network id must be a possitive integer'])
701
        except Exception as err:
702
            raiseCLIError(err)
703
        print_dict(network)
704

    
705

    
706
@command(network_cmds)
707
class network_list(_init_cyclades):
708
    """List networks"""
709

    
710
    arguments = dict(
711
        detail=FlagArgument('show detailed output', '-l'),
712
        limit=IntArgument('limit the number of networks in list', '-n'),
713
        more=FlagArgument(
714
            'output results in pages (-n to set items per page, default 10)',
715
            '--more')
716
    )
717

    
718
    def _make_results_pretty(self, nets):
719
        for net in nets:
720
            network_info._make_result_pretty(net)
721

    
722
    def main(self):
723
        super(self.__class__, self).main()
724
        try:
725
            networks = self.client.list_networks(self['detail'])
726
            if self['detail']:
727
                self._make_results_pretty(networks)
728
        except ClientError as ce:
729
            raise_if_connection_error(ce)
730
            if ce.status == 404:
731
                raiseCLIError(ce,
732
                    'No networks found on server %s' % self.client.base_url,
733
                    details=[
734
                    'Please, check if service url is correctly set',
735
                    '  to get current service url: /config get compute.url',
736
                    '  to set service url: /config set compute.url <URL>'])
737
            raiseCLIError(ce)
738
        except Exception as err:
739
            raiseCLIError(err)
740
        if self['more']:
741
            print_items(networks,
742
                page_size=self['limit'] if self['limit'] else 10)
743
        elif self['limit']:
744
            print_items(networks[:self['limit']])
745
        else:
746
            print_items(networks)
747

    
748

    
749
@command(network_cmds)
750
class network_create(_init_cyclades):
751
    """Create an (unconnected) network"""
752

    
753
    arguments = dict(
754
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
755
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
756
        dhcp=ValueArgument('explicitly set dhcp', '--with-dhcp'),
757
        type=ValueArgument('explicitly set type', '--with-type')
758
    )
759

    
760
    def main(self, name):
761
        super(self.__class__, self).main()
762
        try:
763
            reply = self.client.create_network(name,
764
                cidr=self['cidr'],
765
                gateway=self['gateway'],
766
                dhcp=self['dhcp'],
767
                type=self['type'])
768
        except ClientError as ce:
769
            raise_if_connection_error(ce)
770
            if ce.status == 413:
771
                raiseCLIError(ce,
772
                    'Cannot create another network',
773
                    details=['Maximum number of networks reached'])
774
            raiseCLIError(ce)
775
        except Exception as err:
776
            raiseCLIError(err)
777
        print_items([reply])
778

    
779

    
780
@command(network_cmds)
781
class network_rename(_init_cyclades):
782
    """Set the name of a network"""
783

    
784
    def main(self, network_id, new_name):
785
        super(self.__class__, self).main()
786
        try:
787
            self.client.update_network_name(int(network_id), new_name)
788
        except ClientError as ce:
789
            raise_if_connection_error(ce)
790
            if ce.status == 404:
791
                raiseCLIError(ce,
792
                    'No network found with id %s' % network_id,
793
                    details=['To see a detailed list of available network ids',
794
                    ' try /network list'])
795
            raiseCLIError(ce)
796
        except ValueError as ve:
797
            raiseCLIError(ve,
798
                'Invalid network_id %s' % network_id,
799
                importance=1,
800
                details=['Network id must be a possitive integer'])
801
        except Exception as err:
802
            raiseCLIError(err)
803

    
804

    
805
@command(network_cmds)
806
class network_delete(_init_cyclades):
807
    """Delete a network"""
808

    
809
    def main(self, network_id):
810
        super(self.__class__, self).main()
811
        try:
812
            self.client.delete_network(int(network_id))
813
        except ClientError as ce:
814
            raise_if_connection_error(ce)
815
            if ce.status == 421:
816
                raiseCLIError(ce,
817
                    'Network with id %s is in use' % network_id,
818
                    details=[
819
                        'Disconnect all nics/VMs of this network first',
820
                        '  to get nics: /network info %s' % network_id,
821
                        '    (under "attachments" section)',
822
                        '  to disconnect: /network disconnect <nic id>'])
823
            elif ce.status == 404:
824
                raiseCLIError(ce,
825
                    'No network found with id %s' % network_id,
826
                    details=['To see a detailed list of available network ids',
827
                    ' try /network list'])
828
            raiseCLIError(ce)
829
        except ValueError as ve:
830
            raiseCLIError(ve,
831
                'Invalid network_id %s' % network_id,
832
                importance=1,
833
                details=['Network id must be a possitive integer'])
834
        except Exception as err:
835
            raiseCLIError(err)
836

    
837

    
838
@command(network_cmds)
839
class network_connect(_init_cyclades):
840
    """Connect a server to a network"""
841

    
842
    def main(self, server_id, network_id):
843
        super(self.__class__, self).main()
844
        try:
845
            network_id = int(network_id)
846
            server_id = int(server_id)
847
            self.client.connect_server(server_id, network_id)
848
        except ClientError as ce:
849
            raise_if_connection_error(ce)
850
            if ce.status == 404:
851
                (thename, theid) = ('server', server_id)\
852
                    if 'server' in ('%s' % ce).lower()\
853
                    else ('network', network_id)
854
                raiseCLIError(ce,
855
                    'No %s found with id %s' % (thename, theid),
856
                    details=[
857
                    'To see a detailed list of available %s ids' % thename,
858
                    ' try /%s list' % thename])
859
            raiseCLIError(ce)
860
        except ValueError as ve:
861
            (thename, theid) = ('server', server_id)\
862
            if isinstance(network_id, int) else ('network', network_id)
863
            raiseCLIError(ve,
864
                'Invalid %s id %s' % (thename, theid),
865
                importance=1,
866
                details=['The %s id must be a possitive integer' % thename,
867
                '  to get available %s ids: /%s list' % (thename, thename)])
868
        except Exception as err:
869
            raiseCLIError(err)
870

    
871

    
872
@command(network_cmds)
873
class network_disconnect(_init_cyclades):
874
    """Disconnect a nic that connects a server to a network
875
    Nic ids are listed as "attachments" in detailed network information
876
    To get detailed network information: /network info <network id>
877
    """
878

    
879
    def main(self, nic_id):
880
        super(self.__class__, self).main()
881
        try:
882
            server_id = nic_id.split('-')[1]
883
            if not self.client.disconnect_server(server_id, nic_id):
884
                raise ClientError('Network Interface not found', status=404)
885
        except ClientError as ce:
886
            raise_if_connection_error(ce)
887
            if ce.status == 404:
888
                if 'server' in ('%s' % ce).lower():
889
                    raiseCLIError(ce,
890
                        'No server found with id %s' % (server_id),
891
                        details=[
892
                        'To see a detailed list of available server ids',
893
                        ' try /server list'])
894
                raiseCLIError(ce,
895
                    'No nic %s in server with id %s' % (nic_id, server_id),
896
                    details=[
897
                    'To see a list of nic ids for server %s try:' % server_id,
898
                    '  /server addr %s' % server_id])
899
            raiseCLIError(ce)
900
        except IndexError as err:
901
            raiseCLIError(err,
902
                'Nic %s is of incorrect format' % nic_id,
903
                importance=1,
904
                details=['nid_id format: nic-<server_id>-<nic_index>',
905
                    '  to get nic ids of a network: /network info <net_id>',
906
                    '  they are listed under the "attachments" section'])
907
        except Exception as err:
908
            raiseCLIError(err)