root / tools / cloud @ 09471611
History | View | Annotate | Download (11.5 kB)
1 |
#!/usr/bin/env python |
---|---|
2 |
|
3 |
from functools import wraps |
4 |
from httplib import HTTPConnection |
5 |
from optparse import OptionParser |
6 |
from os.path import basename |
7 |
from sys import argv, exit |
8 |
|
9 |
import json |
10 |
|
11 |
DEFAULT_HOST = '127.0.0.1:8000' |
12 |
DEFAULT_API = 'v1.1redux' |
13 |
|
14 |
|
15 |
commands = {} |
16 |
|
17 |
def command_name(name): |
18 |
def decorator(cls): |
19 |
commands[name] = cls |
20 |
return cls |
21 |
return decorator |
22 |
|
23 |
|
24 |
def address_to_string(address): |
25 |
key = address['id'] |
26 |
val = ' '.join(ip['addr'] for ip in address['values']) |
27 |
return '%s: %s' % (key, val) |
28 |
|
29 |
def print_dict(d, show_empty=True): |
30 |
for key, val in sorted(d.items()): |
31 |
if key == 'metadata': |
32 |
val = ', '.join('%s="%s"' % x for x in val['values'].items()) |
33 |
if key == 'addresses': |
34 |
val = ', '.join(address_to_string(address) for address in val['values']) |
35 |
if val or show_empty: |
36 |
print '%s: %s' % (key.rjust(12), val) |
37 |
|
38 |
|
39 |
class Command(object): |
40 |
def __init__(self, argv): |
41 |
parser = OptionParser() |
42 |
parser.add_option('--host', dest='host', metavar='HOST', default=DEFAULT_HOST, |
43 |
help='use server HOST') |
44 |
parser.add_option('--api', dest='api', metavar='API', default=DEFAULT_API, |
45 |
help='use api API') |
46 |
parser.add_option('-v', action='store_true', dest='verbose', default=False, |
47 |
help='use verbose output') |
48 |
self.add_options(parser) |
49 |
options, args = parser.parse_args(argv) |
50 |
|
51 |
# Add options to self |
52 |
for opt in parser.option_list: |
53 |
key = opt.dest |
54 |
if key: |
55 |
val = getattr(options, key) |
56 |
setattr(self, key, val) |
57 |
|
58 |
self.execute(*args) |
59 |
|
60 |
def add_options(self, parser): |
61 |
pass |
62 |
|
63 |
def execute(self, *args): |
64 |
pass |
65 |
|
66 |
def http_cmd(self, method, path, body=None, expected_status=200): |
67 |
conn = HTTPConnection(self.host) |
68 |
|
69 |
kwargs = {} |
70 |
if body: |
71 |
kwargs['headers'] = {'Content-Type': 'application/json'} |
72 |
kwargs['body'] = body |
73 |
conn.request(method, path, **kwargs) |
74 |
|
75 |
resp = conn.getresponse() |
76 |
if self.verbose: |
77 |
print '%d %s' % (resp.status, resp.reason) |
78 |
for key, val in resp.getheaders(): |
79 |
print '%s: %s' % (key.capitalize(), val) |
80 |
|
81 |
|
82 |
buf = resp.read() or '{}' |
83 |
reply = json.loads(buf) |
84 |
|
85 |
if resp.status != expected_status: |
86 |
if len(reply) == 1: |
87 |
key = reply.keys()[0] |
88 |
val = reply[key] |
89 |
print '%s %s' % (key, val.get('message', '')) |
90 |
if self.verbose: |
91 |
print val.get('details', '') |
92 |
exit(1) |
93 |
|
94 |
return reply |
95 |
|
96 |
def http_get(self, path, expected_status=200): |
97 |
return self.http_cmd('GET', path, None, expected_status) |
98 |
|
99 |
def http_post(self, path, body, expected_status=202): |
100 |
return self.http_cmd('POST', path, body, expected_status) |
101 |
|
102 |
def http_put(self, path, body, expected_status=204): |
103 |
return self.http_cmd('PUT', path, body, expected_status) |
104 |
|
105 |
def http_delete(self, path, expected_status=204): |
106 |
return self.http_cmd('DELETE', path, None, expected_status) |
107 |
|
108 |
|
109 |
@command_name('ls') |
110 |
class ListServers(Command): |
111 |
description = 'list servers' |
112 |
|
113 |
def add_options(self, parser): |
114 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
115 |
help='show detailed output') |
116 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
117 |
help='include empty values') |
118 |
|
119 |
def execute(self): |
120 |
path = '/api/%s/servers' % self.api |
121 |
if self.detail: |
122 |
path += '/detail' |
123 |
|
124 |
reply = self.http_get(path) |
125 |
|
126 |
for server in reply['servers']['values']: |
127 |
id = server.pop('id') |
128 |
name = server.pop('name') |
129 |
if self.detail: |
130 |
print '%d %s' % (id, name) |
131 |
print_dict(server, self.show_empty) |
132 |
|
133 |
else: |
134 |
print '%3d %s' % (id, name) |
135 |
|
136 |
|
137 |
@command_name('info') |
138 |
class GetServerDetails(Command): |
139 |
description = 'get server details' |
140 |
syntax = '<server id>' |
141 |
|
142 |
def add_options(self, parser): |
143 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
144 |
help='include empty values') |
145 |
|
146 |
def execute(self, server_id): |
147 |
path = '/api/%s/servers/%d' % (self.api, int(server_id)) |
148 |
reply = self.http_get(path) |
149 |
server = reply['server'] |
150 |
server.pop('id') |
151 |
print_dict(server, self.show_empty) |
152 |
|
153 |
|
154 |
@command_name('create') |
155 |
class CreateServer(Command): |
156 |
description = 'create server' |
157 |
syntax = '<server name>' |
158 |
|
159 |
def add_options(self, parser): |
160 |
parser.add_option('-f', dest='flavor', metavar='FLAVOR_ID', default=1, |
161 |
help='use flavor FLAVOR_ID') |
162 |
parser.add_option('-i', dest='image', metavar='IMAGE_ID', default=1, |
163 |
help='use image IMAGE_ID') |
164 |
|
165 |
def execute(self, name): |
166 |
path = '/api/%s/servers' % self.api |
167 |
server = {'name': name, 'flavorRef': self.flavor, 'imageRef': self.image} |
168 |
body = json.dumps({'server': server}) |
169 |
reply = self.http_post(path, body) |
170 |
server = reply['server'] |
171 |
server.pop('id') |
172 |
print_dict(server) |
173 |
|
174 |
|
175 |
@command_name('rename') |
176 |
class UpdateServerName(Command): |
177 |
description = 'update server name' |
178 |
syntax = '<server id> <new name>' |
179 |
|
180 |
def execute(self, server_id, name): |
181 |
path = '/api/%s/servers/%d' % (self.api, int(server_id)) |
182 |
body = json.dumps({'server': {'name': name}}) |
183 |
self.http_put(path, body) |
184 |
|
185 |
|
186 |
@command_name('delete') |
187 |
class DeleteServer(Command): |
188 |
description = 'delete server' |
189 |
syntax = '<server id>' |
190 |
|
191 |
def execute(self, server_id): |
192 |
path = '/api/%s/servers/%d' % (self.api, int(server_id)) |
193 |
self.http_delete(path) |
194 |
|
195 |
|
196 |
@command_name('reboot') |
197 |
class RebootServer(Command): |
198 |
description = 'reboot server' |
199 |
syntax = '<server id>' |
200 |
|
201 |
def add_options(self, parser): |
202 |
parser.add_option('-f', action='store_true', dest='hard', default=False, |
203 |
help='perform a hard reboot') |
204 |
|
205 |
def execute(self, server_id): |
206 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
207 |
type = 'HARD' if self.hard else 'SOFT' |
208 |
body = json.dumps({'reboot': {'type': type}}) |
209 |
self.http_post(path, body) |
210 |
|
211 |
|
212 |
@command_name('start') |
213 |
class StartServer(Command): |
214 |
description = 'start server' |
215 |
syntax = '<server id>' |
216 |
|
217 |
def execute(self, server_id): |
218 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
219 |
body = json.dumps({'start': {}}) |
220 |
self.http_post(path, body) |
221 |
|
222 |
|
223 |
@command_name('shutdown') |
224 |
class StartServer(Command): |
225 |
description = 'shutdown server' |
226 |
syntax = '<server id>' |
227 |
|
228 |
def execute(self, server_id): |
229 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
230 |
body = json.dumps({'shutdown': {}}) |
231 |
self.http_post(path, body) |
232 |
|
233 |
|
234 |
@command_name('console') |
235 |
class ServerConsole(Command): |
236 |
description = 'get VNC console' |
237 |
syntax = '<server id>' |
238 |
|
239 |
def add_options(self, parser): |
240 |
pass |
241 |
|
242 |
def execute(self, server_id): |
243 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
244 |
body = json.dumps({'console':{'type':'VNC'}}) |
245 |
reply = self.http_post(path, body) |
246 |
print_dict(reply) |
247 |
|
248 |
|
249 |
@command_name('lsaddr') |
250 |
class ListAddresses(Command): |
251 |
description = 'list server addresses' |
252 |
syntax = '<server id> [network]' |
253 |
|
254 |
def execute(self, server_id, network=None): |
255 |
path = '/api/%s/servers/%d/ips' % (self.api, int(server_id)) |
256 |
if network: |
257 |
path += '/%s' % network |
258 |
reply = self.http_get(path) |
259 |
|
260 |
addresses = [reply['network']] if network else reply['addresses']['values'] |
261 |
for address in addresses: |
262 |
print address_to_string(address) |
263 |
|
264 |
|
265 |
@command_name('lsflv') |
266 |
class ListFlavors(Command): |
267 |
description = 'list flavors' |
268 |
|
269 |
def add_options(self, parser): |
270 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
271 |
help='show detailed output') |
272 |
|
273 |
def execute(self): |
274 |
path = '/api/%s/flavors' % self.api |
275 |
if self.detail: |
276 |
path += '/detail' |
277 |
reply = self.http_get(path) |
278 |
|
279 |
for flavor in reply['flavors']['values']: |
280 |
id = flavor.pop('id') |
281 |
name = flavor.pop('name') |
282 |
details = ' '.join('%s=%s' % item for item in sorted(flavor.items())) |
283 |
print '%3d %s %s' % (id, name, details) |
284 |
|
285 |
|
286 |
@command_name('infoflv') |
287 |
class GetFlavorDetails(Command): |
288 |
description = 'get flavor details' |
289 |
syntax = '<flavor id>' |
290 |
|
291 |
def execute(self, flavor_id): |
292 |
path = '/api/%s/flavors/%d' % (self.api, int(flavor_id)) |
293 |
reply = self.http_get(path) |
294 |
|
295 |
flavor = reply['flavor'] |
296 |
id = flavor.pop('id') |
297 |
name = flavor.pop('name') |
298 |
details = ' '.join('%s=%s' % item for item in sorted(flavor.items())) |
299 |
print '%3d %s %s' % (id, name, details) |
300 |
|
301 |
|
302 |
@command_name('lsimg') |
303 |
class ListImages(Command): |
304 |
description = 'list images' |
305 |
|
306 |
def add_options(self, parser): |
307 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
308 |
help='show detailed output') |
309 |
|
310 |
def execute(self): |
311 |
path = '/api/%s/images' % self.api |
312 |
if self.detail: |
313 |
path += '/detail' |
314 |
reply = self.http_get(path) |
315 |
|
316 |
for image in reply['images']['values']: |
317 |
id = image.pop('id') |
318 |
name = image.pop('name') |
319 |
if self.detail: |
320 |
print '%d %s' % (id, name) |
321 |
print_dict(image) |
322 |
|
323 |
else: |
324 |
print '%3d %s' % (id, name) |
325 |
|
326 |
|
327 |
@command_name('imginfo') |
328 |
class GetImageDetails(Command): |
329 |
description = 'get image details' |
330 |
syntax = '<image id>' |
331 |
|
332 |
def execute(self, image_id): |
333 |
path = '/api/%s/images/%d' % (self.api, int(image_id)) |
334 |
reply = self.http_get(path) |
335 |
image = reply['image'] |
336 |
image.pop('id') |
337 |
print_dict(image) |
338 |
|
339 |
|
340 |
@command_name('createimg') |
341 |
class CreateImage(Command): |
342 |
description = 'create image' |
343 |
syntax = '<server id> <image name>' |
344 |
|
345 |
def execute(self, server_id, name): |
346 |
path = '/api/%s/images' % self.api |
347 |
image = {'name': name, 'serverRef': int(server_id)} |
348 |
body = json.dumps({'image': image}) |
349 |
reply = self.http_post(path, body) |
350 |
print_dict(reply['image']) |
351 |
|
352 |
|
353 |
@command_name('deleteimg') |
354 |
class DeleteImage(Command): |
355 |
description = 'delete image' |
356 |
syntax = '<image id>' |
357 |
|
358 |
def execute(self, image_id): |
359 |
path = '/api/%s/images/%d' % (self.api, int(image_id)) |
360 |
self.http_delete(path) |
361 |
|
362 |
|
363 |
def main(): |
364 |
try: |
365 |
name = argv[1] |
366 |
cls = commands[name] |
367 |
except (IndexError, KeyError): |
368 |
print 'Usage: %s <command>' % basename(argv[0]) |
369 |
|
370 |
print 'Commands:' |
371 |
for name, cls in sorted(commands.items()): |
372 |
description = getattr(cls, 'description', '') |
373 |
print ' %s %s' % (name.ljust(12), description) |
374 |
exit(1) |
375 |
|
376 |
try: |
377 |
commands[name](argv[2:]) |
378 |
except TypeError: |
379 |
syntax = getattr(cls, 'syntax', '') |
380 |
if syntax: |
381 |
print 'Syntax: %s %s' % (name, syntax) |
382 |
else: |
383 |
print 'Invalid syntax' |
384 |
exit(1) |
385 |
|
386 |
|
387 |
if __name__ == '__main__': |
388 |
main() |