Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 236e7d08

History | View | Annotate | Download (33 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 _run(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
    def main(self, service='compute'):
96
        self._run(service)
97

    
98

    
99
@command(server_cmds)
100
class server_list(_init_cyclades):
101
    """List Virtual Machines accessible by user
102
    """
103

    
104
    __doc__ += about_authentication
105

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

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

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

    
155

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

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

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

    
198

    
199
class PersonalityArgument(KeyValueArgument):
200
    @property
201
    def value(self):
202
        return self._value if hasattr(self, '_value') else []
203

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

    
232

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

    
242
    arguments = dict(
243
        personality=PersonalityArgument(
244
            ' _ _ _ '.join(howto_personality),
245
            parsed_name='--personality')
246
    )
247

    
248
    def main(self, name, flavor_id, image_id):
249
        super(self.__class__, self).main()
250

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

    
283

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

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

    
304

    
305
@command(server_cmds)
306
class server_delete(_init_cyclades):
307
    """Delete a server (VM)"""
308

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

    
325

    
326
@command(server_cmds)
327
class server_reboot(_init_cyclades):
328
    """Reboot a server (VM)"""
329

    
330
    arguments = dict(
331
        hard=FlagArgument('perform a hard reboot', '-f')
332
    )
333

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

    
350

    
351
@command(server_cmds)
352
class server_start(_init_cyclades):
353
    """Start an existing server (VM)"""
354

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

    
371

    
372
@command(server_cmds)
373
class server_shutdown(_init_cyclades):
374
    """Shutdown an active server (VM)"""
375

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

    
392

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

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

    
419

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

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

    
450

    
451
@command(server_cmds)
452
class server_addr(_init_cyclades):
453
    """List the addresses of all network interfaces on a server (VM)"""
454

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

    
472

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

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

    
499

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

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

    
525

    
526
@command(server_cmds)
527
class server_delmeta(_init_cyclades):
528
    """Delete server (VM) metadata"""
529

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

    
549

    
550
@command(server_cmds)
551
class server_stats(_init_cyclades):
552
    """Get server (VM) statistics"""
553

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

    
571

    
572
@command(server_cmds)
573
class server_wait(_init_cyclades):
574
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
575

    
576
    arguments = dict(
577
        progress_bar=ProgressBarArgument(
578
            'do not show progress bar',
579
            '--no-progress-bar',
580
            False
581
        )
582
    )
583

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

    
616

    
617
@command(flavor_cmds)
618
class flavor_list(_init_cyclades):
619
    """List available hardware flavors"""
620

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

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

    
649

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

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

    
672

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

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

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

    
708

    
709
@command(network_cmds)
710
class network_list(_init_cyclades):
711
    """List networks"""
712

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

    
721
    def _make_results_pretty(self, nets):
722
        for net in nets:
723
            network_info._make_result_pretty(net)
724

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

    
751

    
752
@command(network_cmds)
753
class network_create(_init_cyclades):
754
    """Create an (unconnected) network"""
755

    
756
    arguments = dict(
757
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
758
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
759
        dhcp=ValueArgument('explicitly set dhcp', '--with-dhcp'),
760
        type=ValueArgument('explicitly set type', '--with-type')
761
    )
762

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

    
782

    
783
@command(network_cmds)
784
class network_rename(_init_cyclades):
785
    """Set the name of a network"""
786

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

    
807

    
808
@command(network_cmds)
809
class network_delete(_init_cyclades):
810
    """Delete a network"""
811

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

    
840

    
841
@command(network_cmds)
842
class network_connect(_init_cyclades):
843
    """Connect a server to a network"""
844

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

    
874

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

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