Fully adjust cyclades_cli
[kamaki] / kamaki / cli / commands / cyclades_cli.py
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)