Revision 38d247df snf-app/synnefo/tools/cloud.py
b/snf-app/synnefo/tools/cloud.py | ||
---|---|---|
1 | 1 |
#!/usr/bin/env python |
2 | 2 |
|
3 | 3 |
# Copyright 2011 GRNET S.A. All rights reserved. |
4 |
#
|
|
4 |
# |
|
5 | 5 |
# Redistribution and use in source and binary forms, with or |
6 | 6 |
# without modification, are permitted provided that the following |
7 | 7 |
# conditions are met: |
8 |
#
|
|
8 |
# |
|
9 | 9 |
# 1. Redistributions of source code must retain the above |
10 | 10 |
# copyright notice, this list of conditions and the following |
11 | 11 |
# disclaimer. |
12 |
#
|
|
12 |
# |
|
13 | 13 |
# 2. Redistributions in binary form must reproduce the above |
14 | 14 |
# copyright notice, this list of conditions and the following |
15 | 15 |
# disclaimer in the documentation and/or other materials |
16 | 16 |
# provided with the distribution. |
17 |
#
|
|
17 |
# |
|
18 | 18 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
19 | 19 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
20 | 20 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
... | ... | |
27 | 27 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
28 | 28 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | 29 |
# POSSIBILITY OF SUCH DAMAGE. |
30 |
#
|
|
30 |
# |
|
31 | 31 |
# The views and conclusions contained in the software and |
32 | 32 |
# documentation are those of the authors and should not be |
33 | 33 |
# interpreted as representing official policies, either expressed |
... | ... | |
43 | 43 |
|
44 | 44 |
|
45 | 45 |
DEFAULT_API_URL = 'http://127.0.0.1:8000/api/v1.1' |
46 |
DEFAULT_TOKEN = '46e427d657b20defe352804f0eb6f8a2' |
|
47 | 46 |
|
48 | 47 |
MARGIN = 14 |
49 | 48 |
|
... | ... | |
62 | 61 |
addr = '' |
63 | 62 |
if 'values' in net: |
64 | 63 |
addr = '[%s]' % ' '.join(ip['addr'] for ip in net['values']) |
65 |
|
|
64 |
|
|
66 | 65 |
val = '%s/%s %s %s' % (net['id'], net['name'], net['mac'], addr) |
67 | 66 |
if 'firewallProfile' in net: |
68 | 67 |
val += ' - %s' % net['firewallProfile'] |
... | ... | |
92 | 91 |
parser.add_option('--token', |
93 | 92 |
dest='token', |
94 | 93 |
metavar='TOKEN', |
95 |
default=DEFAULT_TOKEN, |
|
96 | 94 |
help='use user token TOKEN') |
97 | 95 |
parser.add_option('-v', |
98 | 96 |
action='store_true', |
... | ... | |
101 | 99 |
help='use verbose output') |
102 | 100 |
self.add_options(parser) |
103 | 101 |
options, args = parser.parse_args(argv) |
104 |
|
|
102 |
|
|
105 | 103 |
# Add options to self |
106 | 104 |
for opt in parser.option_list: |
107 | 105 |
key = opt.dest |
108 | 106 |
if key: |
109 | 107 |
val = getattr(options, key) |
110 | 108 |
setattr(self, key, val) |
111 |
|
|
109 |
|
|
112 | 110 |
self.execute(*args) |
113 |
|
|
111 |
|
|
114 | 112 |
def add_options(self, parser): |
115 | 113 |
pass |
116 |
|
|
114 |
|
|
117 | 115 |
def execute(self, *args): |
118 | 116 |
pass |
119 |
|
|
117 |
|
|
120 | 118 |
def http_cmd(self, method, path, body=None, expected_status=200): |
121 | 119 |
p = urlparse(self.apiurl) |
122 | 120 |
if p.scheme == 'https': |
... | ... | |
165 | 163 |
|
166 | 164 |
def http_get(self, path, expected_status=200): |
167 | 165 |
return self.http_cmd('GET', path, None, expected_status) |
168 |
|
|
166 |
|
|
169 | 167 |
def http_post(self, path, body, expected_status=202): |
170 | 168 |
return self.http_cmd('POST', path, body, expected_status) |
171 | 169 |
|
... | ... | |
179 | 177 |
@command_name('ls') |
180 | 178 |
class ListServers(Command): |
181 | 179 |
description = 'list servers' |
182 |
|
|
180 |
|
|
183 | 181 |
def add_options(self, parser): |
184 | 182 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
185 | 183 |
help='show detailed output') |
186 | 184 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
187 | 185 |
help='include empty values') |
188 |
|
|
186 |
|
|
189 | 187 |
def execute(self): |
190 | 188 |
path = '/servers/detail' if self.detail else '/servers' |
191 | 189 |
reply = self.http_get(path) |
... | ... | |
205 | 203 |
class GetServerDetails(Command): |
206 | 204 |
description = 'get server details' |
207 | 205 |
syntax = '<server id>' |
208 |
|
|
206 |
|
|
209 | 207 |
def add_options(self, parser): |
210 | 208 |
parser.add_option('-a', action='store_true', dest='show_empty', default=False, |
211 | 209 |
help='include empty values') |
212 |
|
|
210 |
|
|
213 | 211 |
def execute(self, server_id): |
214 | 212 |
path = '/servers/%d' % int(server_id) |
215 | 213 |
reply = self.http_get(path) |
216 | 214 |
server = reply['server'] |
217 | 215 |
server.pop('id') |
218 | 216 |
print_dict(server, self.show_empty) |
219 |
|
|
217 |
|
|
220 | 218 |
|
221 | 219 |
@command_name('create') |
222 | 220 |
class CreateServer(Command): |
223 | 221 |
description = 'create server' |
224 | 222 |
syntax = '<server name>' |
225 |
|
|
223 |
|
|
226 | 224 |
def add_options(self, parser): |
227 | 225 |
parser.add_option('-f', dest='flavor', metavar='FLAVOR_ID', default=1, |
228 | 226 |
help='use flavor FLAVOR_ID') |
229 | 227 |
parser.add_option('-i', dest='image', metavar='IMAGE_ID', default=1, |
230 | 228 |
help='use image IMAGE_ID') |
231 |
|
|
229 |
|
|
232 | 230 |
def execute(self, name): |
233 | 231 |
server = { |
234 | 232 |
'name': name, |
... | ... | |
244 | 242 |
class UpdateServerName(Command): |
245 | 243 |
description = 'update server name' |
246 | 244 |
syntax = '<server id> <new name>' |
247 |
|
|
245 |
|
|
248 | 246 |
def execute(self, server_id, name): |
249 | 247 |
path = '/servers/%d' % int(server_id) |
250 | 248 |
body = json.dumps({'server': {'name': name}}) |
... | ... | |
255 | 253 |
class DeleteServer(Command): |
256 | 254 |
description = 'delete server' |
257 | 255 |
syntax = '<server id>' |
258 |
|
|
256 |
|
|
259 | 257 |
def execute(self, server_id): |
260 | 258 |
path = '/servers/%d' % int(server_id) |
261 | 259 |
self.http_delete(path) |
... | ... | |
265 | 263 |
class RebootServer(Command): |
266 | 264 |
description = 'reboot server' |
267 | 265 |
syntax = '<server id>' |
268 |
|
|
266 |
|
|
269 | 267 |
def add_options(self, parser): |
270 | 268 |
parser.add_option('-f', action='store_true', dest='hard', default=False, |
271 | 269 |
help='perform a hard reboot') |
272 |
|
|
270 |
|
|
273 | 271 |
def execute(self, server_id): |
274 | 272 |
path = '/servers/%d/action' % int(server_id) |
275 | 273 |
type = 'HARD' if self.hard else 'SOFT' |
276 | 274 |
body = json.dumps({'reboot': {'type': type}}) |
277 | 275 |
self.http_post(path, body) |
278 |
|
|
276 |
|
|
279 | 277 |
|
280 | 278 |
@command_name('start') |
281 | 279 |
class StartServer(Command): |
282 | 280 |
description = 'start server' |
283 | 281 |
syntax = '<server id>' |
284 |
|
|
282 |
|
|
285 | 283 |
def execute(self, server_id): |
286 | 284 |
path = '/servers/%d/action' % int(server_id) |
287 | 285 |
body = json.dumps({'start': {}}) |
... | ... | |
292 | 290 |
class StartServer(Command): |
293 | 291 |
description = 'shutdown server' |
294 | 292 |
syntax = '<server id>' |
295 |
|
|
293 |
|
|
296 | 294 |
def execute(self, server_id): |
297 | 295 |
path = '/servers/%d/action' % int(server_id) |
298 | 296 |
body = json.dumps({'shutdown': {}}) |
... | ... | |
303 | 301 |
class ServerConsole(Command): |
304 | 302 |
description = 'get VNC console' |
305 | 303 |
syntax = '<server id>' |
306 |
|
|
304 |
|
|
307 | 305 |
def execute(self, server_id): |
308 | 306 |
path = '/servers/%d/action' % int(server_id) |
309 | 307 |
body = json.dumps({'console': {'type': 'vnc'}}) |
... | ... | |
315 | 313 |
class SetFirewallProfile(Command): |
316 | 314 |
description = 'set the firewall profile' |
317 | 315 |
syntax = '<server id> <profile>' |
318 |
|
|
316 |
|
|
319 | 317 |
def execute(self, server_id, profile): |
320 | 318 |
path = '/servers/%d/action' % int(server_id) |
321 | 319 |
body = json.dumps({'firewallProfile': {'profile': profile}}) |
... | ... | |
326 | 324 |
class ListAddresses(Command): |
327 | 325 |
description = 'list server addresses' |
328 | 326 |
syntax = '<server id> [network]' |
329 |
|
|
327 |
|
|
330 | 328 |
def execute(self, server_id, network=None): |
331 | 329 |
path = '/servers/%d/ips' % int(server_id) |
332 | 330 |
if network: |
333 | 331 |
path += '/%s' % network |
334 | 332 |
reply = self.http_get(path) |
335 |
|
|
333 |
|
|
336 | 334 |
addresses = [reply['network']] if network else reply['addresses']['values'] |
337 | 335 |
print_addresses(addresses) |
338 | 336 |
|
... | ... | |
340 | 338 |
@command_name('lsflv') |
341 | 339 |
class ListFlavors(Command): |
342 | 340 |
description = 'list flavors' |
343 |
|
|
341 |
|
|
344 | 342 |
def add_options(self, parser): |
345 | 343 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
346 | 344 |
help='show detailed output') |
347 |
|
|
345 |
|
|
348 | 346 |
def execute(self): |
349 | 347 |
path = '/flavors/detail' if self.detail else '/flavors' |
350 | 348 |
reply = self.http_get(path) |
351 |
|
|
349 |
|
|
352 | 350 |
for flavor in reply['flavors']['values']: |
353 | 351 |
id = flavor.pop('id') |
354 | 352 |
name = flavor.pop('name') |
... | ... | |
360 | 358 |
class GetFlavorDetails(Command): |
361 | 359 |
description = 'get flavor details' |
362 | 360 |
syntax = '<flavor id>' |
363 |
|
|
361 |
|
|
364 | 362 |
def execute(self, flavor_id): |
365 | 363 |
path = '/flavors/%d' % int(flavor_id) |
366 | 364 |
reply = self.http_get(path) |
367 |
|
|
365 |
|
|
368 | 366 |
flavor = reply['flavor'] |
369 | 367 |
id = flavor.pop('id') |
370 | 368 |
name = flavor.pop('name') |
... | ... | |
375 | 373 |
@command_name('lsimg') |
376 | 374 |
class ListImages(Command): |
377 | 375 |
description = 'list images' |
378 |
|
|
376 |
|
|
379 | 377 |
def add_options(self, parser): |
380 | 378 |
parser.add_option('-l', action='store_true', dest='detail', default=False, |
381 | 379 |
help='show detailed output') |
382 |
|
|
380 |
|
|
383 | 381 |
def execute(self): |
384 | 382 |
path = '/images/detail' if self.detail else '/images' |
385 | 383 |
reply = self.http_get(path) |
386 |
|
|
384 |
|
|
387 | 385 |
for image in reply['images']['values']: |
388 | 386 |
id = image.pop('id') |
389 | 387 |
name = image.pop('name') |
... | ... | |
399 | 397 |
class GetImageDetails(Command): |
400 | 398 |
description = 'get image details' |
401 | 399 |
syntax = '<image id>' |
402 |
|
|
400 |
|
|
403 | 401 |
def execute(self, image_id): |
404 | 402 |
path = '/images/%d' % int(image_id) |
405 | 403 |
reply = self.http_get(path) |
... | ... | |
412 | 410 |
class CreateImage(Command): |
413 | 411 |
description = 'create image' |
414 | 412 |
syntax = '<server id> <image name>' |
415 |
|
|
413 |
|
|
416 | 414 |
def execute(self, server_id, name): |
417 | 415 |
image = {'name': name, 'serverRef': int(server_id)} |
418 | 416 |
body = json.dumps({'image': image}) |
... | ... | |
423 | 421 |
class DeleteImage(Command): |
424 | 422 |
description = 'delete image' |
425 | 423 |
syntax = '<image id>' |
426 |
|
|
424 |
|
|
427 | 425 |
def execute(self, image_id): |
428 | 426 |
path = '/images/%d' % int(image_id) |
429 | 427 |
self.http_delete(path) |
... | ... | |
638 | 636 |
|
639 | 637 |
def main(): |
640 | 638 |
try: |
641 |
name = argv[1]
|
|
639 |
name = argv[1] |
|
642 | 640 |
cls = commands[name] |
643 | 641 |
except (IndexError, KeyError): |
644 | 642 |
print_usage() |
645 | 643 |
exit(1) |
646 |
|
|
644 |
|
|
647 | 645 |
try: |
648 | 646 |
cls(argv[2:]) |
649 | 647 |
except TypeError: |
Also available in: Unified diff