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