1 # Copyright 2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
34 from kamaki.cli import command
35 from kamaki.cli.command_tree import CommandTree
36 from kamaki.cli.utils import print_dict, print_items, print_list, bold
37 from kamaki.cli.errors import CLIError, raiseCLIError, CLISyntaxError
38 from kamaki.clients.cyclades import CycladesClient, ClientError
39 from kamaki.cli.argument import FlagArgument, ValueArgument
40 from kamaki.cli.argument import ProgressBarArgument
41 from kamaki.cli.commands import _command_init
43 from base64 import b64encode
44 from os.path import exists
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]
58 class _init_cyclades(_command_init):
59 def main(self, service='compute'):
60 token = self.config.get(service, 'token')\
61 or self.config.get('global', 'token')
62 base_url = self.config.get(service, 'url')\
63 or self.config.get('global', 'url')
64 self.client = CycladesClient(base_url=base_url, token=token)
68 class server_list(_init_cyclades):
71 def __init__(self, arguments={}):
72 super(server_list, self).__init__(arguments)
73 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
75 def _info_print(self, server):
77 if 'attachments' in server:
78 for addr in server['attachments']['values']:
79 ips = addr.pop('values', [])
81 addr['IPv%s' % ip['version']] = ip['addr']
82 if 'firewallProfile' in addr:
83 addr['firewall'] = addr.pop('firewallProfile')
84 addr_dict[addr.pop('id')] = addr
85 server['attachments'] = addr_dict if addr_dict is not {} else None
86 if 'metadata' in server:
87 server['metadata'] = server['metadata']['values']
88 print_dict(server, ident=2)
90 def _print(self, servers):
91 for server in servers:
92 sname = server.pop('name')
93 sid = server.pop('id')
94 print('%s (%s)' % (sid, bold(sname)))
95 if self.get_argument('detail'):
96 self._info_print(server)
100 super(self.__class__, self).main()
102 servers = self.client.list_servers(self.get_argument('detail'))
104 except ClientError as err:
108 @command(server_cmds)
109 class server_info(_init_cyclades):
110 """Get server details"""
113 def _print(self, server):
115 if 'attachments' in server:
116 atts = server.pop('attachments')
117 for addr in atts['values']:
118 ips = addr.pop('values', [])
120 addr['IPv%s' % ip['version']] = ip['addr']
121 if 'firewallProfile' in addr:
122 addr['firewall'] = addr.pop('firewallProfile')
123 addr_dict[addr.pop('id')] = addr
124 server['attachments'] = addr_dict if addr_dict else None
125 if 'metadata' in server:
126 server['metadata'] = server['metadata']['values']
127 print_dict(server, ident=2)
129 def main(self, server_id):
130 super(self.__class__, self).main()
132 server = self.client.get_server_details(int(server_id))
133 except ClientError as err:
135 except ValueError as err:
136 raise CLIError(message='Server id must be positive integer',
141 class PersonalityArgument(ValueArgument):
144 return [self._value] if hasattr(self, '_value') else []
147 def value(self, newvalue):
148 if newvalue == self.default:
150 termlist = newvalue.split(',')
151 if len(termlist) > 5:
152 raise CLISyntaxError(details='Wrong number of terms'\
153 + ' ("PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]"')
155 self._value = dict(path=path)
157 raise CLIError(message="File %s does not exist" % path,
159 with open(path) as f:
160 self._value['contents'] = b64encode(f.read())
162 self._value['owner'] = termlist[1]
163 self._value['group'] = termlist[2]
164 self._value['mode'] = termlist[3]
169 @command(server_cmds)
170 class server_create(_init_cyclades):
171 """Create a server"""
173 def __init__(self, arguments={}):
174 super(server_create, self).__init__(arguments)
175 self.arguments['personality'] = PersonalityArgument(\
176 'add a personality file ( ' +\
177 '"PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]" )',
178 parsed_name='--personality')
180 def main(self, name, flavor_id, image_id):
181 super(self.__class__, self).main()
183 print('PERSONLITY: ' % self.get_argument('personality'))
186 reply = self.client.create_server(name,
189 self.get_argument('personality'))
190 except ClientError as err:
192 except ValueError as err:
193 raise CLIError('Invalid flavor id %s ' % flavor_id,
194 details='Flavor id must be a positive integer',
196 except Exception as err:
197 raise CLIError('Syntax error: %s\n' % err, importance=1)
201 @command(server_cmds)
202 class server_rename(_init_cyclades):
203 """Update a server's name"""
205 def main(self, server_id, new_name):
206 super(self.__class__, self).main()
208 self.client.update_server_name(int(server_id), new_name)
209 except ClientError as err:
212 raise CLIError('Invalid server id %s ' % server_id,
213 details='Server id must be positive integer\n',
217 @command(server_cmds)
218 class server_delete(_init_cyclades):
219 """Delete a server"""
221 def main(self, server_id):
222 super(self.__class__, self).main()
224 self.client.delete_server(int(server_id))
225 except ClientError as err:
228 raise CLIError(message='Server id must be positive integer',
232 @command(server_cmds)
233 class server_reboot(_init_cyclades):
234 """Reboot a server"""
236 def __init__(self, arguments={}):
237 super(server_reboot, self).__init__(arguments)
238 self.arguments['hard'] = FlagArgument('perform a hard reboot', '-f')
240 def main(self, server_id):
241 super(self.__class__, self).main()
243 self.client.reboot_server(int(server_id),
244 self.get_argument('hard'))
245 except ClientError as err:
248 raise CLIError(message='Server id must be positive integer',
252 @command(server_cmds)
253 class server_start(_init_cyclades):
256 def main(self, server_id):
257 super(self.__class__, self).main()
259 self.client.start_server(int(server_id))
260 except ClientError as err:
263 raise CLIError(message='Server id must be positive integer',
267 @command(server_cmds)
268 class server_shutdown(_init_cyclades):
269 """Shutdown a server"""
271 def main(self, server_id):
272 super(self.__class__, self).main()
274 self.client.shutdown_server(int(server_id))
275 except ClientError as err:
278 raise CLIError(message='Server id must be positive integer',
282 @command(server_cmds)
283 class server_console(_init_cyclades):
284 """Get a VNC console"""
286 def main(self, server_id):
287 super(self.__class__, self).main()
289 reply = self.client.get_server_console(int(server_id))
290 except ClientError as err:
293 raise CLIError(message='Server id must be positive integer',
298 @command(server_cmds)
299 class server_firewall(_init_cyclades):
300 """Set the server's firewall profile"""
302 def main(self, server_id, profile):
303 super(self.__class__, self).main()
305 self.client.set_firewall_profile(int(server_id), profile)
306 except ClientError as err:
309 raise CLIError(message='Server id must be positive integer',
313 @command(server_cmds)
314 class server_addr(_init_cyclades):
315 """List a server's nic address"""
317 def main(self, server_id):
318 super(self.__class__, self).main()
320 reply = self.client.list_server_nics(int(server_id))
321 except ClientError as err:
324 raise CLIError(message='Server id must be positive integer',
329 @command(server_cmds)
330 class server_meta(_init_cyclades):
331 """Get a server's metadata"""
333 def main(self, server_id, key=''):
334 super(self.__class__, self).main()
336 reply = self.client.get_server_metadata(int(server_id), key)
338 raise CLIError(message='Server id must be positive integer',
340 except ClientError as err:
345 @command(server_cmds)
346 class server_addmeta(_init_cyclades):
347 """Add server metadata"""
349 def main(self, server_id, key, val):
350 super(self.__class__, self).main()
352 reply = self.client.create_server_metadata(\
353 int(server_id), key, val)
354 except ClientError as err:
357 raise CLIError(message='Server id must be positive integer',
362 @command(server_cmds)
363 class server_setmeta(_init_cyclades):
364 """Update server's metadata"""
366 def main(self, server_id, key, val):
367 super(self.__class__, self).main()
368 metadata = {key: val}
370 reply = self.client.update_server_metadata(int(server_id),
372 except ClientError as err:
375 raise CLIError(message='Server id must be positive integer',
380 @command(server_cmds)
381 class server_delmeta(_init_cyclades):
382 """Delete server metadata"""
384 def main(self, server_id, key):
385 super(self.__class__, self).main()
387 self.client.delete_server_metadata(int(server_id), key)
388 except ClientError as err:
391 raise CLIError(message='Server id must be positive integer',
395 @command(server_cmds)
396 class server_stats(_init_cyclades):
397 """Get server statistics"""
399 def main(self, server_id):
400 super(self.__class__, self).main()
402 reply = self.client.get_server_stats(int(server_id))
403 except ClientError as err:
406 raise CLIError(message='Server id must be positive integer',
408 print_dict(reply, exclude=('serverRef',))
411 @command(server_cmds)
412 class server_wait(_init_cyclades):
413 """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
415 def __init__(self, arguments={}):
416 super(self.__class__, self).__init__(arguments)
417 self.arguments['progress_bar'] = ProgressBarArgument(\
418 'do not show progress bar', '--no-progress-bar', False)
420 def main(self, server_id, currect_status='BUILD'):
421 super(self.__class__, self).main()
423 progress_bar = self.arguments['progress_bar']
424 wait_cb = progress_bar.get_generator(\
425 'Server %s still in %s mode' % (server_id, currect_status))
429 new_mode = self.client.wait_server(server_id,
432 progress_bar.finish()
433 except KeyboardInterrupt:
435 progress_bar.finish()
437 except ClientError as err:
438 progress_bar.finish()
441 print('\nServer %s is now in %s mode' % (server_id, new_mode))
446 @command(flavor_cmds)
447 class flavor_list(_init_cyclades):
450 def __init__(self, arguments={}):
451 super(flavor_list, self).__init__(arguments)
452 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
455 def _print(self, flist):
456 for i, flavor in enumerate(flist):
457 print(bold('%s. %s' % (i, flavor['name'])))
458 print_dict(flavor, exclude=('name'), ident=2)
462 super(self.__class__, self).main()
464 flavors = self.client.list_flavors(self.get_argument('detail'))
465 except ClientError as err:
471 @command(flavor_cmds)
472 class flavor_info(_init_cyclades):
473 """Get flavor details"""
475 def main(self, flavor_id):
476 super(self.__class__, self).main()
478 flavor = self.client.get_flavor_details(int(flavor_id))
479 except ClientError as err:
482 raise CLIError(message='Server id must be positive integer',
487 @command(network_cmds)
488 class network_list(_init_cyclades):
491 def __init__(self, arguments={}):
492 super(network_list, self).__init__(arguments)
493 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
495 def print_networks(self, nets):
497 netname = bold(net.pop('name'))
498 netid = bold(unicode(net.pop('id')))
499 print('%s (%s)' % (netid, netname))
500 if self.get_argument('detail'):
501 network_info.print_network(net)
504 super(self.__class__, self).main()
506 networks = self.client.list_networks(self.get_argument('detail'))
507 except ClientError as err:
509 self.print_networks(networks)
512 @command(network_cmds)
513 class network_create(_init_cyclades):
514 """Create a network"""
516 def __init__(self, arguments={}):
517 super(network_create, self).__init__(arguments)
518 self.arguments['cidr'] =\
519 ValueArgument('specific cidr for new network', '--with-cidr')
520 self.arguments['gateway'] =\
521 ValueArgument('specific gateway for new network', '--with-gateway')
522 self.arguments['dhcp'] =\
523 ValueArgument('specific dhcp for new network', '--with-dhcp')
524 self.arguments['type'] =\
525 ValueArgument('specific type for new network', '--with-type')
527 def main(self, name):
528 super(self.__class__, self).main()
530 reply = self.client.create_network(name,
531 cidr=self.get_argument('cidr'),
532 gateway=self.get_argument('gateway'),
533 dhcp=self.get_argument('dhcp'),
534 type=self.get_argument('type'))
535 except ClientError as err:
540 @command(network_cmds)
541 class network_info(_init_cyclades):
542 """Get network details"""
545 def print_network(self, net):
546 if 'attachments' in net:
547 att = net['attachments']['values']
548 net['attachments'] = att if len(att) > 0 else None
549 print_dict(net, ident=2)
551 def main(self, network_id):
552 super(self.__class__, self).main()
554 network = self.client.get_network_details(network_id)
555 except ClientError as err:
557 network_info.print_network(network)
560 @command(network_cmds)
561 class network_rename(_init_cyclades):
562 """Update network name"""
564 def main(self, network_id, new_name):
565 super(self.__class__, self).main()
567 self.client.update_network_name(network_id, new_name)
568 except ClientError as err:
572 @command(network_cmds)
573 class network_delete(_init_cyclades):
574 """Delete a network"""
576 def main(self, network_id):
577 super(self.__class__, self).main()
579 self.client.delete_network(network_id)
580 except ClientError as err:
584 @command(network_cmds)
585 class network_connect(_init_cyclades):
586 """Connect a server to a network"""
588 def main(self, server_id, network_id):
589 super(self.__class__, self).main()
591 self.client.connect_server(server_id, network_id)
592 except ClientError as err:
596 @command(network_cmds)
597 class network_disconnect(_init_cyclades):
598 """Disconnect a nic that connects a server to a network"""
600 def main(self, nic_id):
601 super(self.__class__, self).main()
603 server_id = nic_id.split('-')[1]
604 self.client.disconnect_server(server_id, nic_id)
606 raise CLIError(message='Incorrect nic format', importance=1,
607 details='nid_id format: nic-<server_id>-<nic_index>')
608 except ClientError as err: