root / snf-cyclades-app / synnefo / tools / cloud.py @ 9c0ac5af
History | View | Annotate | Download (19.8 kB)
1 |
#!/usr/bin/env python
|
---|---|
2 |
|
3 |
# Copyright 2011 GRNET S.A. All rights reserved.
|
4 |
#
|
5 |
# Redistribution and use in source and binary forms, with or
|
6 |
# without modification, are permitted provided that the following
|
7 |
# conditions are met:
|
8 |
#
|
9 |
# 1. Redistributions of source code must retain the above
|
10 |
# copyright notice, this list of conditions and the following
|
11 |
# disclaimer.
|
12 |
#
|
13 |
# 2. Redistributions in binary form must reproduce the above
|
14 |
# copyright notice, this list of conditions and the following
|
15 |
# disclaimer in the documentation and/or other materials
|
16 |
# provided with the distribution.
|
17 |
#
|
18 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
19 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
21 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
22 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
25 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
26 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
27 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
28 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29 |
# POSSIBILITY OF SUCH DAMAGE.
|
30 |
#
|
31 |
# The views and conclusions contained in the software and
|
32 |
# documentation are those of the authors and should not be
|
33 |
# interpreted as representing official policies, either expressed
|
34 |
# or implied, of GRNET S.A.
|
35 |
|
36 |
from httplib import HTTPConnection, HTTPSConnection |
37 |
from optparse import OptionParser |
38 |
from os.path import basename |
39 |
from sys import argv, exit |
40 |
from urlparse import urlparse |
41 |
|
42 |
import json |
43 |
|
44 |
|
45 |
DEFAULT_API_URL = 'http://127.0.0.1:8000/api/v1.1'
|
46 |
|
47 |
MARGIN = 14
|
48 |
|
49 |
commands = {} |
50 |
|
51 |
def command_name(name): |
52 |
def decorator(cls): |
53 |
commands[name] = cls |
54 |
return cls
|
55 |
return decorator
|
56 |
|
57 |
|
58 |
def print_addresses(networks): |
59 |
for i, net in enumerate(networks): |
60 |
key = 'addresses:'.rjust(MARGIN + 1) if i == 0 else ' ' * (MARGIN + 1) |
61 |
addr = ''
|
62 |
if 'values' in net: |
63 |
addr = '[%s]' % ' '.join(ip['addr'] for ip in net['values']) |
64 |
|
65 |
val = '%s/%s %s %s' % (net['id'], net['name'], net['mac'], addr) |
66 |
if 'firewallProfile' in net: |
67 |
val += ' - %s' % net['firewallProfile'] |
68 |
print '%s %s' % (key, val) |
69 |
|
70 |
def print_dict(d, show_empty=True): |
71 |
for key, val in sorted(d.items()): |
72 |
if key == 'metadata': |
73 |
val = ', '.join('%s="%s"' % x for x in val['values'].items()) |
74 |
elif key == 'addresses': |
75 |
print_addresses(val['values'])
|
76 |
continue
|
77 |
elif key == 'servers': |
78 |
val = ', '.join(str(server_id) for server_id in val['values']) |
79 |
if val or show_empty: |
80 |
print '%s: %s' % (key.rjust(MARGIN), val) |
81 |
|
82 |
|
83 |
class Command(object): |
84 |
def __init__(self, argv): |
85 |
parser = OptionParser() |
86 |
parser.add_option('--apiurl',
|
87 |
dest='apiurl',
|
88 |
metavar='URL',
|
89 |
default=DEFAULT_API_URL, |
90 |
help='use api API')
|
91 |
parser.add_option('--token',
|
92 |
dest='token',
|
93 |
metavar='TOKEN',
|
94 |
help='use user token TOKEN')
|
95 |
parser.add_option('-v',
|
96 |
action='store_true',
|
97 |
dest='verbose',
|
98 |
default=False,
|
99 |
help='use verbose output')
|
100 |
self.add_options(parser)
|
101 |
options, args = parser.parse_args(argv) |
102 |
|
103 |
# Add options to self
|
104 |
for opt in parser.option_list: |
105 |
key = opt.dest |
106 |
if key:
|
107 |
val = getattr(options, key)
|
108 |
setattr(self, key, val) |
109 |
|
110 |
self.execute(*args)
|
111 |
|
112 |
def add_options(self, parser): |
113 |
pass
|
114 |
|
115 |
def execute(self, *args): |
116 |
pass
|
117 |
|
118 |
def http_cmd(self, method, path, body=None, expected_status=200): |
119 |
p = urlparse(self.apiurl)
|
120 |
if p.scheme == 'https': |
121 |
conn = HTTPSConnection(p.netloc) |
122 |
else:
|
123 |
conn = HTTPConnection(p.netloc) |
124 |
|
125 |
kwargs = {} |
126 |
kwargs['headers'] = {'X-Auth-Token': self.token} |
127 |
if body:
|
128 |
kwargs['headers']['Content-Type'] = 'application/json' |
129 |
kwargs['body'] = body
|
130 |
conn.request(method, p.path + path, **kwargs) |
131 |
|
132 |
resp = conn.getresponse() |
133 |
if self.verbose: |
134 |
print '%d %s' % (resp.status, resp.reason) |
135 |
for key, val in resp.getheaders(): |
136 |
print '%s: %s' % (key.capitalize(), val) |
137 |
print
|
138 |
|
139 |
buf = resp.read() or '{}' |
140 |
try:
|
141 |
reply = json.loads(buf) |
142 |
except ValueError: |
143 |
print 'Invalid response from the server.' |
144 |
if self.verbose: |
145 |
print buf
|
146 |
exit(1) |
147 |
|
148 |
# If the response status is not the expected one,
|
149 |
# assume an error has occured and treat the body
|
150 |
# as a cloudfault.
|
151 |
if resp.status != expected_status:
|
152 |
if len(reply) == 1: |
153 |
key = reply.keys()[0]
|
154 |
val = reply[key] |
155 |
print '%s: %s' % (key, val.get('message', '')) |
156 |
if self.verbose: |
157 |
print val.get('details', '') |
158 |
else:
|
159 |
print 'Invalid response from the server.' |
160 |
exit(1) |
161 |
|
162 |
return reply
|
163 |
|
164 |
def http_get(self, path, expected_status=200): |
165 |
return self.http_cmd('GET', path, None, expected_status) |
166 |
|
167 |
def http_post(self, path, body, expected_status=202): |
168 |
return self.http_cmd('POST', path, body, expected_status) |
169 |
|
170 |
def http_put(self, path, body, expected_status=204): |
171 |
return self.http_cmd('PUT', path, body, expected_status) |
172 |
|
173 |
def http_delete(self, path, expected_status=204): |
174 |
return self.http_cmd('DELETE', path, None, expected_status) |
175 |
|
176 |
|
177 |
@command_name('ls') |
178 |
class ListServers(Command): |
179 |
description = 'list servers'
|
180 |
|
181 |
def add_options(self, parser): |
182 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
183 |
help='show detailed output')
|
184 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
185 |
help='include empty values')
|
186 |
|
187 |
def execute(self): |
188 |
path = '/servers/detail' if self.detail else '/servers' |
189 |
reply = self.http_get(path)
|
190 |
|
191 |
for server in reply['servers']['values']: |
192 |
id = server.pop('id')
|
193 |
name = server.pop('name')
|
194 |
if self.detail: |
195 |
print '%d %s' % (id, name) |
196 |
print_dict(server, self.show_empty)
|
197 |
print
|
198 |
else:
|
199 |
print '%3d %s' % (id, name) |
200 |
|
201 |
|
202 |
@command_name('info') |
203 |
class GetServerDetails(Command): |
204 |
description = 'get server details'
|
205 |
syntax = '<server id>'
|
206 |
|
207 |
def add_options(self, parser): |
208 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
209 |
help='include empty values')
|
210 |
|
211 |
def execute(self, server_id): |
212 |
path = '/servers/%d' % int(server_id) |
213 |
reply = self.http_get(path)
|
214 |
server = reply['server']
|
215 |
server.pop('id')
|
216 |
print_dict(server, self.show_empty)
|
217 |
|
218 |
|
219 |
@command_name('create') |
220 |
class CreateServer(Command): |
221 |
description = 'create server'
|
222 |
syntax = '<server name>'
|
223 |
|
224 |
def add_options(self, parser): |
225 |
parser.add_option('-f', dest='flavor', metavar='FLAVOR_ID', default=1, |
226 |
help='use flavor FLAVOR_ID')
|
227 |
parser.add_option('-i', dest='image', metavar='IMAGE_ID', default=1, |
228 |
help='use image IMAGE_ID')
|
229 |
|
230 |
def execute(self, name): |
231 |
server = { |
232 |
'name': name,
|
233 |
'flavorRef': self.flavor, |
234 |
'imageRef': self.image} |
235 |
body = json.dumps({'server': server})
|
236 |
reply = self.http_post('/servers', body) |
237 |
server = reply['server']
|
238 |
print_dict(server) |
239 |
|
240 |
|
241 |
@command_name('rename') |
242 |
class UpdateServerName(Command): |
243 |
description = 'update server name'
|
244 |
syntax = '<server id> <new name>'
|
245 |
|
246 |
def execute(self, server_id, name): |
247 |
path = '/servers/%d' % int(server_id) |
248 |
body = json.dumps({'server': {'name': name}}) |
249 |
self.http_put(path, body)
|
250 |
|
251 |
|
252 |
@command_name('delete') |
253 |
class DeleteServer(Command): |
254 |
description = 'delete server'
|
255 |
syntax = '<server id>'
|
256 |
|
257 |
def execute(self, server_id): |
258 |
path = '/servers/%d' % int(server_id) |
259 |
self.http_delete(path)
|
260 |
|
261 |
|
262 |
@command_name('reboot') |
263 |
class RebootServer(Command): |
264 |
description = 'reboot server'
|
265 |
syntax = '<server id>'
|
266 |
|
267 |
def add_options(self, parser): |
268 |
parser.add_option('-f', action='store_true', dest='hard', default=False, |
269 |
help='perform a hard reboot')
|
270 |
|
271 |
def execute(self, server_id): |
272 |
path = '/servers/%d/action' % int(server_id) |
273 |
type = 'HARD' if self.hard else 'SOFT' |
274 |
body = json.dumps({'reboot': {'type': type}}) |
275 |
self.http_post(path, body)
|
276 |
|
277 |
|
278 |
@command_name('start') |
279 |
class StartServer(Command): |
280 |
description = 'start server'
|
281 |
syntax = '<server id>'
|
282 |
|
283 |
def execute(self, server_id): |
284 |
path = '/servers/%d/action' % int(server_id) |
285 |
body = json.dumps({'start': {}})
|
286 |
self.http_post(path, body)
|
287 |
|
288 |
|
289 |
@command_name('shutdown') |
290 |
class StartServer(Command): |
291 |
description = 'shutdown server'
|
292 |
syntax = '<server id>'
|
293 |
|
294 |
def execute(self, server_id): |
295 |
path = '/servers/%d/action' % int(server_id) |
296 |
body = json.dumps({'shutdown': {}})
|
297 |
self.http_post(path, body)
|
298 |
|
299 |
|
300 |
@command_name('console') |
301 |
class ServerConsole(Command): |
302 |
description = 'get VNC console'
|
303 |
syntax = '<server id>'
|
304 |
|
305 |
def execute(self, server_id): |
306 |
path = '/servers/%d/action' % int(server_id) |
307 |
body = json.dumps({'console': {'type': 'vnc'}}) |
308 |
reply = self.http_cmd('POST', path, body, 200) |
309 |
print_dict(reply['console'])
|
310 |
|
311 |
|
312 |
@command_name('profile') |
313 |
class SetFirewallProfile(Command): |
314 |
description = 'set the firewall profile'
|
315 |
syntax = '<server id> <profile>'
|
316 |
|
317 |
def execute(self, server_id, profile): |
318 |
path = '/servers/%d/action' % int(server_id) |
319 |
body = json.dumps({'firewallProfile': {'profile': profile}}) |
320 |
self.http_cmd('POST', path, body, 202) |
321 |
|
322 |
|
323 |
@command_name('lsaddr') |
324 |
class ListAddresses(Command): |
325 |
description = 'list server addresses'
|
326 |
syntax = '<server id> [network]'
|
327 |
|
328 |
def execute(self, server_id, network=None): |
329 |
path = '/servers/%d/ips' % int(server_id) |
330 |
if network:
|
331 |
path += '/%s' % network
|
332 |
reply = self.http_get(path)
|
333 |
|
334 |
addresses = [reply['network']] if network else reply['addresses']['values'] |
335 |
print_addresses(addresses) |
336 |
|
337 |
|
338 |
@command_name('lsflv') |
339 |
class ListFlavors(Command): |
340 |
description = 'list flavors'
|
341 |
|
342 |
def add_options(self, parser): |
343 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
344 |
help='show detailed output')
|
345 |
|
346 |
def execute(self): |
347 |
path = '/flavors/detail' if self.detail else '/flavors' |
348 |
reply = self.http_get(path)
|
349 |
|
350 |
for flavor in reply['flavors']['values']: |
351 |
id = flavor.pop('id')
|
352 |
name = flavor.pop('name')
|
353 |
details = ' '.join('%s=%s' % item for item in sorted(flavor.items())) |
354 |
print '%3d %s %s' % (id, name, details) |
355 |
|
356 |
|
357 |
@command_name('flvinfo') |
358 |
class GetFlavorDetails(Command): |
359 |
description = 'get flavor details'
|
360 |
syntax = '<flavor id>'
|
361 |
|
362 |
def execute(self, flavor_id): |
363 |
path = '/flavors/%d' % int(flavor_id) |
364 |
reply = self.http_get(path)
|
365 |
|
366 |
flavor = reply['flavor']
|
367 |
id = flavor.pop('id')
|
368 |
name = flavor.pop('name')
|
369 |
details = ' '.join('%s=%s' % item for item in sorted(flavor.items())) |
370 |
print '%3d %s %s' % (id, name, details) |
371 |
|
372 |
|
373 |
@command_name('lsimg') |
374 |
class ListImages(Command): |
375 |
description = 'list images'
|
376 |
|
377 |
def add_options(self, parser): |
378 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
379 |
help='show detailed output')
|
380 |
|
381 |
def execute(self): |
382 |
path = '/images/detail' if self.detail else '/images' |
383 |
reply = self.http_get(path)
|
384 |
|
385 |
for image in reply['images']['values']: |
386 |
id = image.pop('id')
|
387 |
name = image.pop('name')
|
388 |
if self.detail: |
389 |
print '%d %s' % (id, name) |
390 |
print_dict(image) |
391 |
print
|
392 |
else:
|
393 |
print '%3d %s' % (id, name) |
394 |
|
395 |
|
396 |
@command_name('imginfo') |
397 |
class GetImageDetails(Command): |
398 |
description = 'get image details'
|
399 |
syntax = '<image id>'
|
400 |
|
401 |
def execute(self, image_id): |
402 |
path = '/images/%d' % int(image_id) |
403 |
reply = self.http_get(path)
|
404 |
image = reply['image']
|
405 |
image.pop('id')
|
406 |
print_dict(image) |
407 |
|
408 |
|
409 |
@command_name('createimg') |
410 |
class CreateImage(Command): |
411 |
description = 'create image'
|
412 |
syntax = '<server id> <image name>'
|
413 |
|
414 |
def execute(self, server_id, name): |
415 |
image = {'name': name, 'serverRef': int(server_id)} |
416 |
body = json.dumps({'image': image})
|
417 |
reply = self.http_post('/images', body) |
418 |
print_dict(reply['image'])
|
419 |
|
420 |
@command_name('deleteimg') |
421 |
class DeleteImage(Command): |
422 |
description = 'delete image'
|
423 |
syntax = '<image id>'
|
424 |
|
425 |
def execute(self, image_id): |
426 |
path = '/images/%d' % int(image_id) |
427 |
self.http_delete(path)
|
428 |
|
429 |
@command_name('lsmeta') |
430 |
class ListServerMeta(Command): |
431 |
description = 'list server meta'
|
432 |
syntax = '<server id> [key]'
|
433 |
|
434 |
def execute(self, server_id, key=None): |
435 |
path = '/servers/%d/meta' % int(server_id) |
436 |
if key:
|
437 |
path += '/' + key
|
438 |
reply = self.http_get(path)
|
439 |
if key:
|
440 |
print_dict(reply['meta'])
|
441 |
else:
|
442 |
print_dict(reply['metadata']['values']) |
443 |
|
444 |
@command_name('setmeta') |
445 |
class UpdateServerMeta(Command): |
446 |
description = 'update server meta'
|
447 |
syntax = '<server id> <key> <val>'
|
448 |
|
449 |
def execute(self, server_id, key, val): |
450 |
path = '/servers/%d/meta' % int(server_id) |
451 |
metadata = {key: val} |
452 |
body = json.dumps({'metadata': metadata})
|
453 |
reply = self.http_post(path, body, expected_status=201) |
454 |
print_dict(reply['metadata'])
|
455 |
|
456 |
@command_name('addmeta') |
457 |
class CreateServerMeta(Command): |
458 |
description = 'add server meta'
|
459 |
syntax = '<server id> <key> <val>'
|
460 |
|
461 |
def execute(self, server_id, key, val): |
462 |
path = '/servers/%d/meta/%s' % (int(server_id), key) |
463 |
meta = {key: val} |
464 |
body = json.dumps({'meta': meta})
|
465 |
reply = self.http_put(path, body, expected_status=201) |
466 |
print_dict(reply['meta'])
|
467 |
|
468 |
@command_name('delmeta') |
469 |
class DeleteServerMeta(Command): |
470 |
description = 'delete server meta'
|
471 |
syntax = '<server id> <key>'
|
472 |
|
473 |
def execute(self, server_id, key): |
474 |
path = '/servers/%d/meta/%s' % (int(server_id), key) |
475 |
reply = self.http_delete(path)
|
476 |
|
477 |
@command_name('lsimgmeta') |
478 |
class ListImageMeta(Command): |
479 |
description = 'list image meta'
|
480 |
syntax = '<image id> [key]'
|
481 |
|
482 |
def execute(self, image_id, key=None): |
483 |
path = '/images/%d/meta' % int(image_id) |
484 |
if key:
|
485 |
path += '/' + key
|
486 |
reply = self.http_get(path)
|
487 |
if key:
|
488 |
print_dict(reply['meta'])
|
489 |
else:
|
490 |
print_dict(reply['metadata']['values']) |
491 |
|
492 |
@command_name('setimgmeta') |
493 |
class UpdateImageMeta(Command): |
494 |
description = 'update image meta'
|
495 |
syntax = '<image id> <key> <val>'
|
496 |
|
497 |
def execute(self, image_id, key, val): |
498 |
path = '/images/%d/meta' % int(image_id) |
499 |
metadata = {key: val} |
500 |
body = json.dumps({'metadata': metadata})
|
501 |
reply = self.http_post(path, body, expected_status=201) |
502 |
print_dict(reply['metadata'])
|
503 |
|
504 |
@command_name('addimgmeta') |
505 |
class CreateImageMeta(Command): |
506 |
description = 'add image meta'
|
507 |
syntax = '<image id> <key> <val>'
|
508 |
|
509 |
def execute(self, image_id, key, val): |
510 |
path = '/images/%d/meta/%s' % (int(image_id), key) |
511 |
meta = {key: val} |
512 |
body = json.dumps({'meta': meta})
|
513 |
reply = self.http_put(path, body, expected_status=201) |
514 |
print_dict(reply['meta'])
|
515 |
|
516 |
@command_name('delimgmeta') |
517 |
class DeleteImageMeta(Command): |
518 |
description = 'delete image meta'
|
519 |
syntax = '<image id> <key>'
|
520 |
|
521 |
def execute(self, image_id, key): |
522 |
path = '/images/%d/meta/%s' % (int(image_id), key) |
523 |
reply = self.http_delete(path)
|
524 |
|
525 |
|
526 |
@command_name('lsnet') |
527 |
class ListNetworks(Command): |
528 |
description = 'list networks'
|
529 |
|
530 |
def add_options(self, parser): |
531 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
532 |
help='show detailed output')
|
533 |
|
534 |
def execute(self): |
535 |
path = '/networks/detail' if self.detail else '/networks' |
536 |
reply = self.http_get(path)
|
537 |
|
538 |
for network in reply['networks']['values']: |
539 |
id = network.pop('id')
|
540 |
name = network.pop('name')
|
541 |
if self.detail: |
542 |
print '%s %s' % (id, name) |
543 |
print_dict(network) |
544 |
print
|
545 |
else:
|
546 |
print '%3s %s' % (id, name) |
547 |
|
548 |
|
549 |
@command_name('createnet') |
550 |
class CreateNetwork(Command): |
551 |
description = 'create network'
|
552 |
syntax = '<network name>'
|
553 |
|
554 |
def execute(self, name): |
555 |
network = {'name': name}
|
556 |
body = json.dumps({'network': network})
|
557 |
reply = self.http_post('/networks', body) |
558 |
print_dict(reply['network'])
|
559 |
|
560 |
|
561 |
@command_name('netinfo') |
562 |
class GetNetworkDetails(Command): |
563 |
description = 'get network details'
|
564 |
syntax = '<network id>'
|
565 |
|
566 |
def execute(self, network_id): |
567 |
path = '/networks/%d' % int(network_id) |
568 |
reply = self.http_get(path)
|
569 |
net = reply['network']
|
570 |
name = net.pop('id')
|
571 |
print_dict(net) |
572 |
|
573 |
|
574 |
@command_name('renamenet') |
575 |
class UpdateNetworkName(Command): |
576 |
description = 'update network name'
|
577 |
syntax = '<network_id> <new name>'
|
578 |
|
579 |
def execute(self, network_id, name): |
580 |
path = '/networks/%d' % int(network_id) |
581 |
body = json.dumps({'network': {'name': name}}) |
582 |
self.http_put(path, body)
|
583 |
|
584 |
|
585 |
@command_name('deletenet') |
586 |
class DeleteNetwork(Command): |
587 |
description = 'delete network'
|
588 |
syntax = '<network id>'
|
589 |
|
590 |
def execute(self, network_id): |
591 |
path = '/networks/%d' % int(network_id) |
592 |
self.http_delete(path)
|
593 |
|
594 |
|
595 |
@command_name('connect') |
596 |
class AddNetwork(Command): |
597 |
description = 'connect a server to a network'
|
598 |
syntax = '<server id> <network id>'
|
599 |
|
600 |
def execute(self, server_id, network_id): |
601 |
path = '/networks/%d/action' % int(network_id) |
602 |
body = json.dumps({'add': {'serverRef': server_id}}) |
603 |
self.http_post(path, body, expected_status=202) |
604 |
|
605 |
|
606 |
@command_name('disconnect') |
607 |
class RemoveNetwork(Command): |
608 |
description = 'disconnect a server from a network'
|
609 |
syntax = '<server id> <network id>'
|
610 |
|
611 |
def execute(self, server_id, network_id): |
612 |
path = '/networks/%s/action' % int(network_id) |
613 |
body = json.dumps({'remove': {'serverRef': server_id}}) |
614 |
self.http_post(path, body, expected_status=202) |
615 |
|
616 |
@command_name('stats') |
617 |
class ServerStats(Command): |
618 |
description = 'get server stats'
|
619 |
syntax = '<server id>'
|
620 |
|
621 |
def execute(self, server_id): |
622 |
path = '/servers/%d/stats' % int(server_id) |
623 |
reply = self.http_get(path)
|
624 |
stats = reply['stats']
|
625 |
stats.pop('serverRef')
|
626 |
print_dict(stats) |
627 |
|
628 |
|
629 |
def print_usage(): |
630 |
print 'Usage: %s <command>' % basename(argv[0]) |
631 |
print
|
632 |
print 'Commands:' |
633 |
for name, cls in sorted(commands.items()): |
634 |
description = getattr(cls, 'description', '') |
635 |
print ' %s %s' % (name.ljust(12), description) |
636 |
|
637 |
def main(): |
638 |
try:
|
639 |
name = argv[1]
|
640 |
cls = commands[name] |
641 |
except (IndexError, KeyError): |
642 |
print_usage() |
643 |
exit(1) |
644 |
|
645 |
try:
|
646 |
cls(argv[2:])
|
647 |
except TypeError: |
648 |
syntax = getattr(cls, 'syntax', '') |
649 |
if syntax:
|
650 |
print 'Syntax: %s %s' % (name, syntax) |
651 |
else:
|
652 |
print 'Invalid syntax' |
653 |
exit(1) |
654 |
|
655 |
|
656 |
if __name__ == '__main__': |
657 |
main() |