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 _print(self, servers):
76 for server in servers:
77 sname = server.pop('name')
78 sid = server.pop('id')
79 print('%s (%s)' % (bold(sname), bold(unicode(sid))))
80 if self.get_argument('detail'):
81 server_info._print(server)
85 super(self.__class__, self).main()
87 servers = self.client.list_servers(self.get_argument('detail'))
90 except ClientError as err:
95 class server_info(_init_cyclades):
96 """Get server details"""
99 def _print(self, server):
101 if 'attachments' in server:
102 for addr in server['attachments']['values']:
103 ips = addr.pop('values', [])
105 addr['IPv%s' % ip['version']] = ip['addr']
106 if 'firewallProfile' in addr:
107 addr['firewall'] = addr.pop('firewallProfile')
108 addr_dict[addr.pop('id')] = addr
109 server['attachments'] = addr_dict if addr_dict is not {} else None
110 if 'metadata' in server:
111 server['metadata'] = server['metadata']['values']
112 print_dict(server, ident=14)
114 def main(self, server_id):
115 super(self.__class__, self).main()
117 server = self.client.get_server_details(int(server_id))
118 except ClientError as err:
120 except ValueError as err:
121 raise CLIError(message='Server id must be positive integer',
126 class PersonalityArgument(ValueArgument):
129 return [self._value] if hasattr(self, '_value') else []
132 def value(self, newvalue):
133 if newvalue == self.default:
135 termlist = newvalue.split()
136 if len(termlist) > 4:
137 raise CLISyntaxError(details='Wrong number of terms'\
138 + ' ("PATH [OWNER [GROUP [MODE]]]"')
140 self._value = dict(path=path)
142 raise CLIError(message="File %s does not exist" % path,
144 with open(path) as f:
145 self._value['contents'] = b64encode(f.read())
147 self._value['owner'] = termlist[1]
148 self._value['group'] = termlist[2]
149 self._value['mode'] = termlist[3]
154 @command(server_cmds)
155 class server_create(_init_cyclades):
156 """Create a server"""
158 def __init__(self, arguments={}):
159 super(server_create, self).__init__(arguments)
160 self.arguments['personality'] = PersonalityArgument(\
161 help='add a personality file ( "PATH [OWNER [GROUP [MODE]]]" )',
162 parsed_name='--personality')
164 def update_parser(self, parser):
165 parser.add_argument('--personality', dest='personalities',
166 action='append', default=[],
167 metavar='PATH[,SERVER PATH[,OWNER[,GROUP,[MODE]]]]',
168 help='add a personality file')
170 def main(self, name, flavor_id, image_id):
171 super(self.__class__, self).main()
173 reply = self.client.create_server(name,
176 self.get_argument('personality'))
177 except ClientError as err:
179 except ValueError as err:
180 raise CLIError('Invalid flavor id %s ' % flavor_id,
181 details='Flavor id must be a positive integer',
183 except Exception as err:
184 raise CLIError('Syntax error: %s\n' % err, importance=1)
188 @command(server_cmds)
189 class server_rename(_init_cyclades):
190 """Update a server's name"""
192 def main(self, server_id, new_name):
193 super(self.__class__, self).main()
195 self.client.update_server_name(int(server_id), new_name)
196 except ClientError as err:
199 raise CLIError('Invalid server id %s ' % server_id,
200 details='Server id must be positive integer\n',
204 @command(server_cmds)
205 class server_delete(_init_cyclades):
206 """Delete a server"""
208 def main(self, server_id):
209 super(self.__class__, self).main()
211 self.client.delete_server(int(server_id))
212 except ClientError as err:
215 raise CLIError(message='Server id must be positive integer',
219 @command(server_cmds)
220 class server_reboot(_init_cyclades):
221 """Reboot a server"""
223 def __init__(self, arguments={}):
224 super(server_reboot, self).__init__(arguments)
225 self.arguments['hard'] = FlagArgument('perform a hard reboot', '-f')
227 def main(self, server_id):
228 super(self.__class__, self).main()
230 self.client.reboot_server(int(server_id),
231 self.get_argument('hard'))
232 except ClientError as err:
235 raise CLIError(message='Server id must be positive integer',
239 @command(server_cmds)
240 class server_start(_init_cyclades):
243 def main(self, server_id):
244 super(self.__class__, self).main()
246 self.client.start_server(int(server_id))
247 except ClientError as err:
250 raise CLIError(message='Server id must be positive integer',
254 @command(server_cmds)
255 class server_shutdown(_init_cyclades):
256 """Shutdown a server"""
258 def main(self, server_id):
259 super(self.__class__, self).main()
261 self.client.shutdown_server(int(server_id))
262 except ClientError as err:
265 raise CLIError(message='Server id must be positive integer',
269 @command(server_cmds)
270 class server_console(_init_cyclades):
271 """Get a VNC console"""
273 def main(self, server_id):
274 super(self.__class__, self).main()
276 reply = self.client.get_server_console(int(server_id))
277 except ClientError as err:
280 raise CLIError(message='Server id must be positive integer',
285 @command(server_cmds)
286 class server_firewall(_init_cyclades):
287 """Set the server's firewall profile"""
289 def main(self, server_id, profile):
290 super(self.__class__, self).main()
292 self.client.set_firewall_profile(int(server_id), profile)
293 except ClientError as err:
296 raise CLIError(message='Server id must be positive integer',
300 @command(server_cmds)
301 class server_addr(_init_cyclades):
302 """List a server's nic address"""
304 def main(self, server_id):
305 super(self.__class__, self).main()
307 reply = self.client.list_server_nics(int(server_id))
308 except ClientError as err:
311 raise CLIError(message='Server id must be positive integer',
316 @command(server_cmds)
317 class server_meta(_init_cyclades):
318 """Get a server's metadata"""
320 def main(self, server_id, key=''):
321 super(self.__class__, self).main()
323 reply = self.client.get_server_metadata(int(server_id), key)
325 raise CLIError(message='Server id must be positive integer',
327 except ClientError as err:
332 @command(server_cmds)
333 class server_addmeta(_init_cyclades):
334 """Add server metadata"""
336 def main(self, server_id, key, val):
337 super(self.__class__, self).main()
339 reply = self.client.create_server_metadata(\
340 int(server_id), key, val)
341 except ClientError as err:
344 raise CLIError(message='Server id must be positive integer',
349 @command(server_cmds)
350 class server_setmeta(_init_cyclades):
351 """Update server's metadata"""
353 def main(self, server_id, key, val):
354 super(self.__class__, self).main()
355 metadata = {key: val}
357 reply = self.client.update_server_metadata(int(server_id),
359 except ClientError as err:
362 raise CLIError(message='Server id must be positive integer',
367 @command(server_cmds)
368 class server_delmeta(_init_cyclades):
369 """Delete server metadata"""
371 def main(self, server_id, key):
372 super(self.__class__, self).main()
374 self.client.delete_server_metadata(int(server_id), key)
375 except ClientError as err:
378 raise CLIError(message='Server id must be positive integer',
382 @command(server_cmds)
383 class server_stats(_init_cyclades):
384 """Get server statistics"""
386 def main(self, server_id):
387 super(self.__class__, self).main()
389 reply = self.client.get_server_stats(int(server_id))
390 except ClientError as err:
393 raise CLIError(message='Server id must be positive integer',
395 print_dict(reply, exclude=('serverRef',))
398 @command(server_cmds)
399 class server_wait(_init_cyclades):
400 """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
402 def __init__(self, arguments={}):
403 super(self.__class__, self).__init__(arguments)
404 self.arguments['progress_bar'] = ProgressBarArgument(\
405 'do not show progress bar', '--no-progress-bar', False)
407 def main(self, server_id, currect_status='BUILD'):
408 super(self.__class__, self).main()
410 progress_bar = self.arguments['progress_bar']
411 wait_cb = progress_bar.get_generator(\
412 'Server %s still in %s mode' % (server_id, currect_status))
416 new_mode = self.client.wait_server(server_id,
419 except KeyboardInterrupt:
422 except ClientError as err:
425 print('\nServer %s is now in %s mode' % (server_id, new_mode))
430 @command(flavor_cmds)
431 class flavor_list(_init_cyclades):
434 def __init__(self, arguments={}):
435 super(flavor_list, self).__init__(arguments)
436 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
439 super(self.__class__, self).main()
441 flavors = self.client.list_flavors(self.get_argument('detail'))
442 except ClientError as err:
447 @command(flavor_cmds)
448 class flavor_info(_init_cyclades):
449 """Get flavor details"""
451 def main(self, flavor_id):
452 super(self.__class__, self).main()
454 flavor = self.client.get_flavor_details(int(flavor_id))
455 except ClientError as err:
458 raise CLIError(message='Server id must be positive integer',
463 @command(network_cmds)
464 class network_list(_init_cyclades):
467 def __init__(self, arguments={}):
468 super(network_list, self).__init__(arguments)
469 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
471 def print_networks(self, nets):
473 netname = bold(net.pop('name'))
474 netid = bold(unicode(net.pop('id')))
475 print('%s (%s)' % (netname, netid))
476 if self.get_argument('detail'):
477 network_info.print_network(net)
480 super(self.__class__, self).main()
482 networks = self.client.list_networks(self.get_argument('detail'))
483 except ClientError as err:
485 self.print_networks(networks)
488 @command(network_cmds)
489 class network_create(_init_cyclades):
490 """Create a network"""
492 def __init__(self, arguments={}):
493 super(network_create, self).__init__(arguments)
494 self.arguments['cidr'] =\
495 ValueArgument('specific cidr for new network', '--with-cidr')
496 self.arguments['gateway'] =\
497 ValueArgument('specific gateway for new network', '--with-gateway')
498 self.arguments['dhcp'] =\
499 ValueArgument('specific dhcp for new network', '--with-dhcp')
500 self.arguments['type'] =\
501 ValueArgument('specific type for new network', '--with-type')
503 def main(self, name):
504 super(self.__class__, self).main()
506 reply = self.client.create_network(name,
507 cidr=self.get_argument('cidr'),
508 gateway=self.get_argument('gateway'),
509 dhcp=self.get_argument('dhcp'),
510 type=self.get_argument('type'))
511 except ClientError as err:
516 @command(network_cmds)
517 class network_info(_init_cyclades):
518 """Get network details"""
521 def print_network(self, net):
522 if 'attachments' in net:
523 att = net['attachments']['values']
524 net['attachments'] = att if len(att) > 0 else None
525 print_dict(net, ident=14)
527 def main(self, network_id):
528 super(self.__class__, self).main()
530 network = self.client.get_network_details(network_id)
531 except ClientError as err:
533 network_info.print_network(network)
536 @command(network_cmds)
537 class network_rename(_init_cyclades):
538 """Update network name"""
540 def main(self, network_id, new_name):
541 super(self.__class__, self).main()
543 self.client.update_network_name(network_id, new_name)
544 except ClientError as err:
548 @command(network_cmds)
549 class network_delete(_init_cyclades):
550 """Delete a network"""
552 def main(self, network_id):
553 super(self.__class__, self).main()
555 self.client.delete_network(network_id)
556 except ClientError as err:
560 @command(network_cmds)
561 class network_connect(_init_cyclades):
562 """Connect a server to a network"""
564 def main(self, server_id, network_id):
565 super(self.__class__, self).main()
567 self.client.connect_server(server_id, network_id)
568 except ClientError as err:
572 @command(network_cmds)
573 class network_disconnect(_init_cyclades):
574 """Disconnect a nic that connects a server to a network"""
576 def main(self, nic_id):
577 super(self.__class__, self).main()
579 server_id = nic_id.split('-')[1]
580 self.client.disconnect_server(server_id, nic_id)
582 raise CLIError(message='Incorrect nic format', importance=1,
583 details='nid_id format: nic-<server_id>-<nic_index>')
584 except ClientError as err: