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_list, bold
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
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=1)
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 Exception 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=1)
129 def main(self, server_id):
130 super(self.__class__, self).main()
132 server = self.client.get_server_details(int(server_id))
133 except ValueError as err:
134 raiseCLIError(err, 'Server id must be positive integer', 1)
135 except Exception as err:
140 class PersonalityArgument(KeyValueArgument):
143 return self._value if hasattr(self, '_value') else []
146 def value(self, newvalue):
147 if newvalue == self.default:
150 for i, terms in enumerate(newvalue):
151 termlist = terms.split(',')
152 if len(termlist) > 5:
153 raiseCLIError(CLISyntaxError(details='Wrong number of terms'\
154 + ' ("PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]"'))
157 raiseCLIError(None, "File %s does not exist" % path, 1)
158 self._value.append(dict(path=path))
159 with open(path) as f:
160 self._value[i]['contents'] = b64encode(f.read())
162 self._value[i]['path'] = termlist[1]
163 self._value[i]['owner'] = termlist[2]
164 self._value[i]['group'] = termlist[3]
165 self._value[i]['mode'] = termlist[4]
170 @command(server_cmds)
171 class server_create(_init_cyclades):
172 """Create a server"""
174 def __init__(self, arguments={}):
175 super(server_create, self).__init__(arguments)
176 self.arguments['personality'] = PersonalityArgument(\
177 'add one or more personality files ( ' +\
178 '"PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]" )',
179 parsed_name='--personality')
181 def main(self, name, flavor_id, image_id):
182 super(self.__class__, self).main()
185 reply = self.client.create_server(name,
188 self.get_argument('personality'))
189 except ClientError as err:
191 except ValueError as err:
192 raiseCLIError(err, 'Invalid flavor id %s ' % flavor_id,
193 details='Flavor id must be a positive integer',
195 except Exception as err:
196 raiseCLIError(err, 'Syntax error: %s\n' % err, importance=1)
200 @command(server_cmds)
201 class server_rename(_init_cyclades):
202 """Update a server's name"""
204 def main(self, server_id, new_name):
205 super(self.__class__, self).main()
207 self.client.update_server_name(int(server_id), new_name)
208 except ClientError as err:
210 except ValueError as err:
211 raiseCLIError(err, 'Invalid server id %s ' % server_id,
212 details='Server id must be positive integer\n',
216 @command(server_cmds)
217 class server_delete(_init_cyclades):
218 """Delete a server"""
220 def main(self, server_id):
221 super(self.__class__, self).main()
223 self.client.delete_server(int(server_id))
224 except ValueError as err:
225 raiseCLIError(err, 'Server id must be positive integer', 1)
226 except Exception as err:
230 @command(server_cmds)
231 class server_reboot(_init_cyclades):
232 """Reboot a server"""
234 def __init__(self, arguments={}):
235 super(server_reboot, self).__init__(arguments)
236 self.arguments['hard'] = FlagArgument('perform a hard reboot', '-f')
238 def main(self, server_id):
239 super(self.__class__, self).main()
241 self.client.reboot_server(int(server_id),
242 self.get_argument('hard'))
243 except ValueError as err:
244 raiseCLIError(err, 'Server id must be positive integer', 1)
245 except Exception as err:
249 @command(server_cmds)
250 class server_start(_init_cyclades):
253 def main(self, server_id):
254 super(self.__class__, self).main()
256 self.client.start_server(int(server_id))
257 except ValueError as err:
258 raiseCLIError(err, 'Server id must be positive integer', 1)
259 except Exception as err:
263 @command(server_cmds)
264 class server_shutdown(_init_cyclades):
265 """Shutdown a server"""
267 def main(self, server_id):
268 super(self.__class__, self).main()
270 self.client.shutdown_server(int(server_id))
271 except ValueError as err:
272 raiseCLIError(err, 'Server id must be positive integer', 1)
273 except Exception as err:
277 @command(server_cmds)
278 class server_console(_init_cyclades):
279 """Get a VNC console"""
281 def main(self, server_id):
282 super(self.__class__, self).main()
284 reply = self.client.get_server_console(int(server_id))
285 except ValueError as err:
286 raiseCLIError(err, 'Server id must be positive integer', 1)
287 except Exception as err:
292 @command(server_cmds)
293 class server_firewall(_init_cyclades):
294 """Set the server's firewall profile"""
296 def main(self, server_id, profile):
297 super(self.__class__, self).main()
299 self.client.set_firewall_profile(int(server_id), profile)
300 except ValueError as err:
301 raiseCLIError(err, 'Server id must be positive integer', 1)
302 except Exception as err:
306 @command(server_cmds)
307 class server_addr(_init_cyclades):
308 """List a server's nic address"""
310 def main(self, server_id):
311 super(self.__class__, self).main()
313 reply = self.client.list_server_nics(int(server_id))
314 except ValueError as err:
315 raiseCLIError(err, 'Server id must be positive integer', 1)
316 except Exception as err:
318 print_list(reply, with_enumeration=True)
321 @command(server_cmds)
322 class server_meta(_init_cyclades):
323 """Get a server's metadata"""
325 def main(self, server_id, key=''):
326 super(self.__class__, self).main()
328 reply = self.client.get_server_metadata(int(server_id), key)
329 except ValueError as err:
330 raiseCLIError(err, 'Server id must be positive integer', 1)
331 except Exception as err:
336 @command(server_cmds)
337 class server_addmeta(_init_cyclades):
338 """Add server metadata"""
340 def main(self, server_id, key, val):
341 super(self.__class__, self).main()
343 reply = self.client.create_server_metadata(\
344 int(server_id), key, val)
345 except ValueError as err:
346 raiseCLIError(err, 'Server id must be positive integer', 1)
347 except Exception as err:
352 @command(server_cmds)
353 class server_setmeta(_init_cyclades):
354 """Update server's metadata"""
356 def main(self, server_id, key, val):
357 super(self.__class__, self).main()
358 metadata = {key: val}
360 reply = self.client.update_server_metadata(int(server_id),
362 except ValueError as err:
363 raiseCLIError(err, 'Server id must be positive integer', 1)
364 except Exception as err:
369 @command(server_cmds)
370 class server_delmeta(_init_cyclades):
371 """Delete server metadata"""
373 def main(self, server_id, key):
374 super(self.__class__, self).main()
376 self.client.delete_server_metadata(int(server_id), key)
377 except ValueError as err:
378 raiseCLIError(err, 'Server id must be positive integer', 1)
379 except Exception as err:
383 @command(server_cmds)
384 class server_stats(_init_cyclades):
385 """Get server statistics"""
387 def main(self, server_id):
388 super(self.__class__, self).main()
390 reply = self.client.get_server_stats(int(server_id))
391 except ValueError as err:
392 raiseCLIError(err, 'Server id must be positive integer', 1)
393 except Exception as err:
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 progress_bar.finish()
420 except KeyboardInterrupt:
422 progress_bar.finish()
424 except ClientError as err:
425 progress_bar.finish()
428 print('Server %s is now in %s mode' % (server_id, new_mode))
430 raiseCLIError(None, 'Time out')
433 @command(flavor_cmds)
434 class flavor_list(_init_cyclades):
437 def __init__(self, arguments={}):
438 super(flavor_list, self).__init__(arguments)
439 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
442 def _print(self, flist):
443 for i, flavor in enumerate(flist):
444 print(bold('%s. %s' % (i, flavor['name'])))
445 print_dict(flavor, exclude=('name'), ident=1)
449 super(self.__class__, self).main()
451 flavors = self.client.list_flavors(self.get_argument('detail'))
452 except Exception as err:
457 @command(flavor_cmds)
458 class flavor_info(_init_cyclades):
459 """Get flavor details"""
461 def main(self, flavor_id):
462 super(self.__class__, self).main()
464 flavor = self.client.get_flavor_details(int(flavor_id))
465 except ValueError as err:
466 raiseCLIError(err, 'Server id must be positive integer', 1)
467 except Exception as err:
472 @command(network_cmds)
473 class network_list(_init_cyclades):
476 def __init__(self, arguments={}):
477 super(network_list, self).__init__(arguments)
478 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
480 def print_networks(self, nets):
482 netname = bold(net.pop('name'))
483 netid = bold(unicode(net.pop('id')))
484 print('%s (%s)' % (netid, netname))
485 if self.get_argument('detail'):
486 network_info.print_network(net)
489 super(self.__class__, self).main()
491 networks = self.client.list_networks(self.get_argument('detail'))
492 except Exception as err:
494 self.print_networks(networks)
497 @command(network_cmds)
498 class network_create(_init_cyclades):
499 """Create a network"""
501 def __init__(self, arguments={}):
502 super(network_create, self).__init__(arguments)
503 self.arguments['cidr'] =\
504 ValueArgument('specific cidr for new network', '--with-cidr')
505 self.arguments['gateway'] =\
506 ValueArgument('specific gateway for new network', '--with-gateway')
507 self.arguments['dhcp'] =\
508 ValueArgument('specific dhcp for new network', '--with-dhcp')
509 self.arguments['type'] =\
510 ValueArgument('specific type for new network', '--with-type')
512 def main(self, name):
513 super(self.__class__, self).main()
515 reply = self.client.create_network(name,
516 cidr=self.get_argument('cidr'),
517 gateway=self.get_argument('gateway'),
518 dhcp=self.get_argument('dhcp'),
519 type=self.get_argument('type'))
520 except Exception as err:
525 @command(network_cmds)
526 class network_info(_init_cyclades):
527 """Get network details"""
530 def print_network(self, net):
531 if 'attachments' in net:
532 att = net['attachments']['values']
533 net['attachments'] = att if len(att) > 0 else None
534 print_dict(net, ident=1)
536 def main(self, network_id):
537 super(self.__class__, self).main()
539 network = self.client.get_network_details(network_id)
540 except Exception as err:
542 network_info.print_network(network)
545 @command(network_cmds)
546 class network_rename(_init_cyclades):
547 """Update network name"""
549 def main(self, network_id, new_name):
550 super(self.__class__, self).main()
552 self.client.update_network_name(network_id, new_name)
553 except Exception as err:
557 @command(network_cmds)
558 class network_delete(_init_cyclades):
559 """Delete a network"""
561 def main(self, network_id):
562 super(self.__class__, self).main()
564 self.client.delete_network(network_id)
565 except Exception as err:
569 @command(network_cmds)
570 class network_connect(_init_cyclades):
571 """Connect a server to a network"""
573 def main(self, server_id, network_id):
574 super(self.__class__, self).main()
576 self.client.connect_server(server_id, network_id)
577 except Exception as err:
581 @command(network_cmds)
582 class network_disconnect(_init_cyclades):
583 """Disconnect a nic that connects a server to a network"""
585 def main(self, nic_id):
586 super(self.__class__, self).main()
588 server_id = nic_id.split('-')[1]
589 self.client.disconnect_server(server_id, nic_id)
590 except IndexError as err:
591 raiseCLIError(err, 'Incorrect nic format', importance=1,
592 details='nid_id format: nic-<server_id>-<nic_index>')
593 except Exception as err: