root / tools / cloud @ c738c935
History | View | Annotate | Download (20.3 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 |
37 |
from optparse import OptionParser |
38 |
from os.path import basename |
39 |
from sys import argv, exit |
40 |
|
41 |
import json |
42 |
|
43 |
DEFAULT_HOST = '127.0.0.1:8000' |
44 |
DEFAULT_API = 'v1.1' |
45 |
|
46 |
TOKEN = '46e427d657b20defe352804f0eb6f8a2' |
47 |
|
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(13) if i == 0 else ' ' * 13 |
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(12), val) |
81 |
|
82 |
|
83 |
class Command(object): |
84 |
def __init__(self, argv): |
85 |
parser = OptionParser() |
86 |
parser.add_option('--host', dest='host', metavar='HOST', default=DEFAULT_HOST, |
87 |
help='use server HOST') |
88 |
parser.add_option('--api', dest='api', metavar='API', default=DEFAULT_API, |
89 |
help='use api API') |
90 |
parser.add_option('-v', action='store_true', dest='verbose', default=False, |
91 |
help='use verbose output') |
92 |
self.add_options(parser) |
93 |
options, args = parser.parse_args(argv) |
94 |
|
95 |
# Add options to self |
96 |
for opt in parser.option_list: |
97 |
key = opt.dest |
98 |
if key: |
99 |
val = getattr(options, key) |
100 |
setattr(self, key, val) |
101 |
|
102 |
self.execute(*args) |
103 |
|
104 |
def add_options(self, parser): |
105 |
pass |
106 |
|
107 |
def execute(self, *args): |
108 |
pass |
109 |
|
110 |
def http_cmd(self, method, path, body=None, expected_status=200): |
111 |
conn = HTTPConnection(self.host) |
112 |
|
113 |
kwargs = {} |
114 |
kwargs['headers'] = {'X-Auth-Token': TOKEN} |
115 |
if body: |
116 |
kwargs['headers']['Content-Type'] = 'application/json' |
117 |
kwargs['body'] = body |
118 |
conn.request(method, path, **kwargs) |
119 |
|
120 |
resp = conn.getresponse() |
121 |
if self.verbose: |
122 |
print '%d %s' % (resp.status, resp.reason) |
123 |
for key, val in resp.getheaders(): |
124 |
print '%s: %s' % (key.capitalize(), val) |
125 |
|
126 |
|
127 |
buf = resp.read() or '{}' |
128 |
try: |
129 |
reply = json.loads(buf) |
130 |
except ValueError: |
131 |
print 'Invalid response from the server.' |
132 |
if self.verbose: |
133 |
print buf |
134 |
exit(1) |
135 |
|
136 |
# If the response status is not the expected one, |
137 |
# assume an error has occured and treat the body |
138 |
# as a cloudfault. |
139 |
if resp.status != expected_status: |
140 |
if len(reply) == 1: |
141 |
key = reply.keys()[0] |
142 |
val = reply[key] |
143 |
print '%s: %s' % (key, val.get('message', '')) |
144 |
if self.verbose: |
145 |
print val.get('details', '') |
146 |
else: |
147 |
print 'Invalid response from the server.' |
148 |
exit(1) |
149 |
|
150 |
return reply |
151 |
|
152 |
def http_get(self, path, expected_status=200): |
153 |
return self.http_cmd('GET', path, None, expected_status) |
154 |
|
155 |
def http_post(self, path, body, expected_status=202): |
156 |
return self.http_cmd('POST', path, body, expected_status) |
157 |
|
158 |
def http_put(self, path, body, expected_status=204): |
159 |
return self.http_cmd('PUT', path, body, expected_status) |
160 |
|
161 |
def http_delete(self, path, expected_status=204): |
162 |
return self.http_cmd('DELETE', path, None, expected_status) |
163 |
|
164 |
|
165 |
@command_name('ls') |
166 |
class ListServers(Command): |
167 |
description = 'list servers' |
168 |
|
169 |
def add_options(self, parser): |
170 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
171 |
help='show detailed output') |
172 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
173 |
help='include empty values') |
174 |
|
175 |
def execute(self): |
176 |
path = '/api/%s/servers' % self.api |
177 |
if self.detail: |
178 |
path += '/detail' |
179 |
|
180 |
reply = self.http_get(path) |
181 |
|
182 |
for server in reply['servers']['values']: |
183 |
id = server.pop('id') |
184 |
name = server.pop('name') |
185 |
if self.detail: |
186 |
print '%d %s' % (id, name) |
187 |
print_dict(server, self.show_empty) |
188 |
|
189 |
else: |
190 |
print '%3d %s' % (id, name) |
191 |
|
192 |
|
193 |
@command_name('info') |
194 |
class GetServerDetails(Command): |
195 |
description = 'get server details' |
196 |
syntax = '<server id>' |
197 |
|
198 |
def add_options(self, parser): |
199 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
200 |
help='include empty values') |
201 |
|
202 |
def execute(self, server_id): |
203 |
path = '/api/%s/servers/%d' % (self.api, int(server_id)) |
204 |
reply = self.http_get(path) |
205 |
server = reply['server'] |
206 |
server.pop('id') |
207 |
print_dict(server, self.show_empty) |
208 |
|
209 |
|
210 |
@command_name('create') |
211 |
class CreateServer(Command): |
212 |
description = 'create server' |
213 |
syntax = '<server name>' |
214 |
|
215 |
def add_options(self, parser): |
216 |
parser.add_option('-f', dest='flavor', metavar='FLAVOR_ID', default=1, |
217 |
help='use flavor FLAVOR_ID') |
218 |
parser.add_option('-i', dest='image', metavar='IMAGE_ID', default=1, |
219 |
help='use image IMAGE_ID') |
220 |
|
221 |
def execute(self, name): |
222 |
path = '/api/%s/servers' % self.api |
223 |
server = {'name': name, 'flavorRef': self.flavor, 'imageRef': self.image} |
224 |
body = json.dumps({'server': server}) |
225 |
reply = self.http_post(path, body) |
226 |
server = reply['server'] |
227 |
print_dict(server) |
228 |
|
229 |
|
230 |
@command_name('rename') |
231 |
class UpdateServerName(Command): |
232 |
description = 'update server name' |
233 |
syntax = '<server id> <new name>' |
234 |
|
235 |
def execute(self, server_id, name): |
236 |
path = '/api/%s/servers/%d' % (self.api, int(server_id)) |
237 |
body = json.dumps({'server': {'name': name}}) |
238 |
self.http_put(path, body) |
239 |
|
240 |
|
241 |
@command_name('delete') |
242 |
class DeleteServer(Command): |
243 |
description = 'delete server' |
244 |
syntax = '<server id>' |
245 |
|
246 |
def execute(self, server_id): |
247 |
path = '/api/%s/servers/%d' % (self.api, int(server_id)) |
248 |
self.http_delete(path) |
249 |
|
250 |
|
251 |
@command_name('reboot') |
252 |
class RebootServer(Command): |
253 |
description = 'reboot server' |
254 |
syntax = '<server id>' |
255 |
|
256 |
def add_options(self, parser): |
257 |
parser.add_option('-f', action='store_true', dest='hard', default=False, |
258 |
help='perform a hard reboot') |
259 |
|
260 |
def execute(self, server_id): |
261 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
262 |
type = 'HARD' if self.hard else 'SOFT' |
263 |
body = json.dumps({'reboot': {'type': type}}) |
264 |
self.http_post(path, body) |
265 |
|
266 |
|
267 |
@command_name('start') |
268 |
class StartServer(Command): |
269 |
description = 'start server' |
270 |
syntax = '<server id>' |
271 |
|
272 |
def execute(self, server_id): |
273 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
274 |
body = json.dumps({'start': {}}) |
275 |
self.http_post(path, body) |
276 |
|
277 |
|
278 |
@command_name('shutdown') |
279 |
class StartServer(Command): |
280 |
description = 'shutdown server' |
281 |
syntax = '<server id>' |
282 |
|
283 |
def execute(self, server_id): |
284 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
285 |
body = json.dumps({'shutdown': {}}) |
286 |
self.http_post(path, body) |
287 |
|
288 |
|
289 |
@command_name('console') |
290 |
class ServerConsole(Command): |
291 |
description = 'get VNC console' |
292 |
syntax = '<server id>' |
293 |
|
294 |
def execute(self, server_id): |
295 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
296 |
body = json.dumps({'console': {'type': 'vnc'}}) |
297 |
reply = self.http_cmd('POST', path, body, 200) |
298 |
print_dict(reply['console']) |
299 |
|
300 |
|
301 |
@command_name('profile') |
302 |
class SetFirewallProfile(Command): |
303 |
description = 'set the firewall profile' |
304 |
syntax = '<server id> <profile>' |
305 |
|
306 |
def execute(self, server_id, profile): |
307 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
308 |
body = json.dumps({'firewallProfile': {'profile': profile}}) |
309 |
self.http_cmd('POST', path, body, 202) |
310 |
|
311 |
|
312 |
@command_name('lsaddr') |
313 |
class ListAddresses(Command): |
314 |
description = 'list server addresses' |
315 |
syntax = '<server id> [network]' |
316 |
|
317 |
def execute(self, server_id, network=None): |
318 |
path = '/api/%s/servers/%d/ips' % (self.api, int(server_id)) |
319 |
if network: |
320 |
path += '/%s' % network |
321 |
reply = self.http_get(path) |
322 |
|
323 |
addresses = [reply['network']] if network else reply['addresses']['values'] |
324 |
print_addresses(addresses) |
325 |
|
326 |
|
327 |
@command_name('lsflv') |
328 |
class ListFlavors(Command): |
329 |
description = 'list flavors' |
330 |
|
331 |
def add_options(self, parser): |
332 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
333 |
help='show detailed output') |
334 |
|
335 |
def execute(self): |
336 |
path = '/api/%s/flavors' % self.api |
337 |
if self.detail: |
338 |
path += '/detail' |
339 |
reply = self.http_get(path) |
340 |
|
341 |
for flavor in reply['flavors']['values']: |
342 |
id = flavor.pop('id') |
343 |
name = flavor.pop('name') |
344 |
details = ' '.join('%s=%s' % item for item in sorted(flavor.items())) |
345 |
print '%3d %s %s' % (id, name, details) |
346 |
|
347 |
|
348 |
@command_name('flvinfo') |
349 |
class GetFlavorDetails(Command): |
350 |
description = 'get flavor details' |
351 |
syntax = '<flavor id>' |
352 |
|
353 |
def execute(self, flavor_id): |
354 |
path = '/api/%s/flavors/%d' % (self.api, int(flavor_id)) |
355 |
reply = self.http_get(path) |
356 |
|
357 |
flavor = reply['flavor'] |
358 |
id = flavor.pop('id') |
359 |
name = flavor.pop('name') |
360 |
details = ' '.join('%s=%s' % item for item in sorted(flavor.items())) |
361 |
print '%3d %s %s' % (id, name, details) |
362 |
|
363 |
|
364 |
@command_name('lsimg') |
365 |
class ListImages(Command): |
366 |
description = 'list images' |
367 |
|
368 |
def add_options(self, parser): |
369 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
370 |
help='show detailed output') |
371 |
|
372 |
def execute(self): |
373 |
path = '/api/%s/images' % self.api |
374 |
if self.detail: |
375 |
path += '/detail' |
376 |
reply = self.http_get(path) |
377 |
|
378 |
for image in reply['images']['values']: |
379 |
id = image.pop('id') |
380 |
name = image.pop('name') |
381 |
if self.detail: |
382 |
print '%d %s' % (id, name) |
383 |
print_dict(image) |
384 |
|
385 |
else: |
386 |
print '%3d %s' % (id, name) |
387 |
|
388 |
|
389 |
@command_name('imginfo') |
390 |
class GetImageDetails(Command): |
391 |
description = 'get image details' |
392 |
syntax = '<image id>' |
393 |
|
394 |
def execute(self, image_id): |
395 |
path = '/api/%s/images/%d' % (self.api, int(image_id)) |
396 |
reply = self.http_get(path) |
397 |
image = reply['image'] |
398 |
image.pop('id') |
399 |
print_dict(image) |
400 |
|
401 |
|
402 |
@command_name('createimg') |
403 |
class CreateImage(Command): |
404 |
description = 'create image' |
405 |
syntax = '<server id> <image name>' |
406 |
|
407 |
def execute(self, server_id, name): |
408 |
path = '/api/%s/images' % self.api |
409 |
image = {'name': name, 'serverRef': int(server_id)} |
410 |
body = json.dumps({'image': image}) |
411 |
reply = self.http_post(path, body) |
412 |
print_dict(reply['image']) |
413 |
|
414 |
@command_name('deleteimg') |
415 |
class DeleteImage(Command): |
416 |
description = 'delete image' |
417 |
syntax = '<image id>' |
418 |
|
419 |
def execute(self, image_id): |
420 |
path = '/api/%s/images/%d' % (self.api, int(image_id)) |
421 |
self.http_delete(path) |
422 |
|
423 |
@command_name('lsmeta') |
424 |
class ListServerMeta(Command): |
425 |
description = 'list server meta' |
426 |
syntax = '<server id> [key]' |
427 |
|
428 |
def execute(self, server_id, key=None): |
429 |
path = '/api/%s/servers/%d/meta' % (self.api, int(server_id)) |
430 |
if key: |
431 |
path += '/' + key |
432 |
reply = self.http_get(path) |
433 |
if key: |
434 |
print_dict(reply['meta']) |
435 |
else: |
436 |
print_dict(reply['metadata']['values']) |
437 |
|
438 |
@command_name('setmeta') |
439 |
class UpdateServerMeta(Command): |
440 |
description = 'update server meta' |
441 |
syntax = '<server id> <key> <val>' |
442 |
|
443 |
def execute(self, server_id, key, val): |
444 |
path = '/api/%s/servers/%d/meta' % (self.api, int(server_id)) |
445 |
metadata = {key: val} |
446 |
body = json.dumps({'metadata': metadata}) |
447 |
reply = self.http_post(path, body, expected_status=201) |
448 |
print_dict(reply['metadata']) |
449 |
|
450 |
@command_name('addmeta') |
451 |
class CreateServerMeta(Command): |
452 |
description = 'add server meta' |
453 |
syntax = '<server id> <key> <val>' |
454 |
|
455 |
def execute(self, server_id, key, val): |
456 |
path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key) |
457 |
meta = {key: val} |
458 |
body = json.dumps({'meta': meta}) |
459 |
reply = self.http_put(path, body, expected_status=201) |
460 |
print_dict(reply['meta']) |
461 |
|
462 |
@command_name('delmeta') |
463 |
class DeleteServerMeta(Command): |
464 |
description = 'delete server meta' |
465 |
syntax = '<server id> <key>' |
466 |
|
467 |
def execute(self, server_id, key): |
468 |
path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key) |
469 |
reply = self.http_delete(path) |
470 |
|
471 |
@command_name('lsimgmeta') |
472 |
class ListImageMeta(Command): |
473 |
description = 'list image meta' |
474 |
syntax = '<image id> [key]' |
475 |
|
476 |
def execute(self, image_id, key=None): |
477 |
path = '/api/%s/images/%d/meta' % (self.api, int(image_id)) |
478 |
if key: |
479 |
path += '/' + key |
480 |
reply = self.http_get(path) |
481 |
if key: |
482 |
print_dict(reply['meta']) |
483 |
else: |
484 |
print_dict(reply['metadata']['values']) |
485 |
|
486 |
@command_name('setimgmeta') |
487 |
class UpdateImageMeta(Command): |
488 |
description = 'update image meta' |
489 |
syntax = '<image id> <key> <val>' |
490 |
|
491 |
def execute(self, image_id, key, val): |
492 |
path = '/api/%s/images/%d/meta' % (self.api, int(image_id)) |
493 |
metadata = {key: val} |
494 |
body = json.dumps({'metadata': metadata}) |
495 |
reply = self.http_post(path, body, expected_status=201) |
496 |
print_dict(reply['metadata']) |
497 |
|
498 |
@command_name('addimgmeta') |
499 |
class CreateImageMeta(Command): |
500 |
description = 'add image meta' |
501 |
syntax = '<image id> <key> <val>' |
502 |
|
503 |
def execute(self, image_id, key, val): |
504 |
path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key) |
505 |
meta = {key: val} |
506 |
body = json.dumps({'meta': meta}) |
507 |
reply = self.http_put(path, body, expected_status=201) |
508 |
print_dict(reply['meta']) |
509 |
|
510 |
@command_name('delimgmeta') |
511 |
class DeleteImageMeta(Command): |
512 |
description = 'delete image meta' |
513 |
syntax = '<image id> <key>' |
514 |
|
515 |
def execute(self, image_id, key): |
516 |
path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key) |
517 |
reply = self.http_delete(path) |
518 |
|
519 |
|
520 |
@command_name('lsnet') |
521 |
class ListNetworks(Command): |
522 |
description = 'list networks' |
523 |
|
524 |
def add_options(self, parser): |
525 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
526 |
help='show detailed output') |
527 |
|
528 |
def execute(self): |
529 |
path = '/api/%s/networks' % self.api |
530 |
if self.detail: |
531 |
path += '/detail' |
532 |
reply = self.http_get(path) |
533 |
|
534 |
for network in reply['networks']['values']: |
535 |
id = network.pop('id') |
536 |
name = network.pop('name') |
537 |
if self.detail: |
538 |
print '%s %s' % (id, name) |
539 |
print_dict(network) |
540 |
|
541 |
else: |
542 |
print '%3s %s' % (id, name) |
543 |
|
544 |
|
545 |
@command_name('createnet') |
546 |
class CreateNetwork(Command): |
547 |
description = 'create network' |
548 |
syntax = '<network name>' |
549 |
|
550 |
def execute(self, name): |
551 |
path = '/api/%s/networks' % self.api |
552 |
network = {'name': name} |
553 |
body = json.dumps({'network': network}) |
554 |
reply = self.http_post(path, body) |
555 |
print_dict(reply['network']) |
556 |
|
557 |
|
558 |
@command_name('netinfo') |
559 |
class GetNetworkDetails(Command): |
560 |
description = 'get network details' |
561 |
syntax = '<network id>' |
562 |
|
563 |
def execute(self, network_id): |
564 |
path = '/api/%s/networks/%d' % (self.api, int(network_id)) |
565 |
reply = self.http_get(path) |
566 |
net = reply['network'] |
567 |
name = net.pop('id') |
568 |
print_dict(net) |
569 |
|
570 |
|
571 |
@command_name('renamenet') |
572 |
class UpdateNetworkName(Command): |
573 |
description = 'update network name' |
574 |
syntax = '<network_id> <new name>' |
575 |
|
576 |
def execute(self, network_id, name): |
577 |
path = '/api/%s/networks/%d' % (self.api, int(network_id)) |
578 |
body = json.dumps({'network': {'name': name}}) |
579 |
self.http_put(path, body) |
580 |
|
581 |
|
582 |
@command_name('deletenet') |
583 |
class DeleteNetwork(Command): |
584 |
description = 'delete network' |
585 |
syntax = '<network id>' |
586 |
|
587 |
def execute(self, network_id): |
588 |
path = '/api/%s/networks/%d' % (self.api, int(network_id)) |
589 |
self.http_delete(path) |
590 |
|
591 |
|
592 |
@command_name('connect') |
593 |
class AddNetwork(Command): |
594 |
description = 'connect a server to a network' |
595 |
syntax = '<server id> <network id>' |
596 |
|
597 |
def execute(self, server_id, network_id): |
598 |
path = '/api/%s/networks/%d/action' % (self.api, int(network_id)) |
599 |
body = json.dumps({'add': {'serverRef': server_id}}) |
600 |
self.http_post(path, body, expected_status=202) |
601 |
|
602 |
|
603 |
@command_name('disconnect') |
604 |
class RemoveNetwork(Command): |
605 |
description = 'disconnect a server from a network' |
606 |
syntax = '<server id> <network id>' |
607 |
|
608 |
def execute(self, server_id, network_id): |
609 |
path = '/api/%s/networks/%s/action' % (self.api, int(network_id)) |
610 |
body = json.dumps({'remove': {'serverRef': server_id}}) |
611 |
self.http_post(path, body, expected_status=202) |
612 |
|
613 |
@command_name('stats') |
614 |
class ServerStats(Command): |
615 |
description = 'get server stats' |
616 |
syntax = '<server id>' |
617 |
|
618 |
def execute(self, server_id): |
619 |
path = '/api/%s/servers/%d/stats' % (self.api, int(server_id)) |
620 |
reply = self.http_get(path) |
621 |
stats = reply['stats'] |
622 |
stats.pop('serverRef') |
623 |
print_dict(stats) |
624 |
|
625 |
|
626 |
def print_usage(): |
627 |
print 'Usage: %s <command>' % basename(argv[0]) |
628 |
|
629 |
print 'Commands:' |
630 |
for name, cls in sorted(commands.items()): |
631 |
description = getattr(cls, 'description', '') |
632 |
print ' %s %s' % (name.ljust(12), description) |
633 |
|
634 |
def main(): |
635 |
try: |
636 |
name = argv[1] |
637 |
cls = commands[name] |
638 |
except (IndexError, KeyError): |
639 |
print_usage() |
640 |
exit(1) |
641 |
|
642 |
try: |
643 |
cls(argv[2:]) |
644 |
except TypeError: |
645 |
syntax = getattr(cls, 'syntax', '') |
646 |
if syntax: |
647 |
print 'Syntax: %s %s' % (name, syntax) |
648 |
else: |
649 |
print 'Invalid syntax' |
650 |
exit(1) |
651 |
|
652 |
|
653 |
if __name__ == '__main__': |
654 |
main() |