Statistics
| Branch: | Tag: | Revision:

root / tools / cloud @ 4cf8adf8

History | View | Annotate | Download (14.7 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.1'
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
            print
81

    
82
        buf = resp.read() or '{}'
83
        reply = json.loads(buf)
84

    
85
        # If the response status is not the expected one,
86
        # assume an error has occured and treat the body
87
        # as a cloudfault.
88
        if resp.status != expected_status:
89
            if len(reply) == 1:
90
                key = reply.keys()[0]
91
                val = reply[key]
92
                print '%s: %s' % (key, val.get('message', ''))
93
                if self.verbose:
94
                    print val.get('details', '')
95
                exit(1)
96

    
97
        return reply
98

    
99
    def http_get(self, path, expected_status=200):
100
        return self.http_cmd('GET', path, None, expected_status)
101
    
102
    def http_post(self, path, body, expected_status=202):
103
        return self.http_cmd('POST', path, body, expected_status)
104

    
105
    def http_put(self, path, body, expected_status=204):
106
        return self.http_cmd('PUT', path, body, expected_status)
107

    
108
    def http_delete(self, path, expected_status=204):
109
        return self.http_cmd('DELETE', path, None, expected_status)
110

    
111

    
112
@command_name('ls')
113
class ListServers(Command):
114
    description = 'list servers'
115
    
116
    def add_options(self, parser):
117
        parser.add_option('-l', action='store_true', dest='detail', default=False,
118
                            help='show detailed output')
119
        parser.add_option('-a', action='store_true', dest='show_empty', default=False,
120
                            help='include empty values')
121
    
122
    def execute(self):
123
        path = '/api/%s/servers' % self.api
124
        if self.detail:
125
            path += '/detail'
126

    
127
        reply = self.http_get(path)
128

    
129
        for server in reply['servers']['values']:
130
            id = server.pop('id')
131
            name = server.pop('name')
132
            if self.detail:
133
                print '%d %s' % (id, name)
134
                print_dict(server, self.show_empty)
135
                print
136
            else:
137
                print '%3d %s' % (id, name)
138

    
139

    
140
@command_name('info')
141
class GetServerDetails(Command):
142
    description = 'get server details'
143
    syntax = '<server id>'
144
    
145
    def add_options(self, parser):
146
        parser.add_option('-a', action='store_true', dest='show_empty', default=False,
147
                            help='include empty values')
148
    
149
    def execute(self, server_id):
150
        path = '/api/%s/servers/%d' % (self.api, int(server_id))
151
        reply = self.http_get(path)
152
        server = reply['server']
153
        server.pop('id')
154
        print_dict(server, self.show_empty)
155
        
156

    
157
@command_name('create')
158
class CreateServer(Command):
159
    description = 'create server'
160
    syntax = '<server name>'
161
    
162
    def add_options(self, parser):
163
        parser.add_option('-f', dest='flavor', metavar='FLAVOR_ID', default=1,
164
                            help='use flavor FLAVOR_ID')
165
        parser.add_option('-i', dest='image', metavar='IMAGE_ID', default=1,
166
                            help='use image IMAGE_ID')
167
    
168
    def execute(self, name):
169
        path = '/api/%s/servers' % self.api
170
        server = {'name': name, 'flavorRef': self.flavor, 'imageRef': self.image}
171
        body = json.dumps({'server': server})
172
        reply = self.http_post(path, body)
173
        server = reply['server']
174
        server.pop('id')
175
        print_dict(server)
176

    
177

    
178
@command_name('rename')
179
class UpdateServerName(Command):
180
    description = 'update server name'
181
    syntax = '<server id> <new name>'
182
    
183
    def execute(self, server_id, name):
184
        path = '/api/%s/servers/%d' % (self.api, int(server_id))
185
        body = json.dumps({'server': {'name': name}})
186
        self.http_put(path, body)
187

    
188

    
189
@command_name('delete')
190
class DeleteServer(Command):
191
    description = 'delete server'
192
    syntax = '<server id>'
193
    
194
    def execute(self, server_id):
195
        path = '/api/%s/servers/%d' % (self.api, int(server_id))
196
        self.http_delete(path)
197

    
198

    
199
@command_name('reboot')
200
class RebootServer(Command):
201
    description = 'reboot server'
202
    syntax = '<server id>'
203
    
204
    def add_options(self, parser):
205
        parser.add_option('-f', action='store_true', dest='hard', default=False,
206
                            help='perform a hard reboot')
207
    
208
    def execute(self, server_id):
209
        path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
210
        type = 'HARD' if self.hard else 'SOFT'
211
        body = json.dumps({'reboot': {'type': type}})
212
        self.http_post(path, body)
213
    
214

    
215
@command_name('start')
216
class StartServer(Command):
217
    description = 'start server'
218
    syntax = '<server id>'
219
    
220
    def execute(self, server_id):
221
        path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
222
        body = json.dumps({'start': {}})
223
        self.http_post(path, body)
224

    
225

    
226
@command_name('shutdown')
227
class StartServer(Command):
228
    description = 'shutdown server'
229
    syntax = '<server id>'
230
    
231
    def execute(self, server_id):
232
        path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
233
        body = json.dumps({'shutdown': {}})
234
        self.http_post(path, body)
235

    
236

    
237
@command_name('console')
238
class ServerConsole(Command):
239
    description = 'get VNC console'
240
    syntax = '<server id>'
241
    
242
    def add_options(self, parser):
243
    	pass
244
    
245
    def execute(self, server_id):
246
        path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
247
        body = json.dumps({'console':{'type':'vnc'}})
248
        reply = self.http_cmd('POST', path, body, 200)
249
        print_dict(reply['vnc'])
250

    
251

    
252
@command_name('lsaddr')
253
class ListAddresses(Command):
254
    description = 'list server addresses'
255
    syntax = '<server id> [network]'
256
    
257
    def execute(self, server_id, network=None):
258
        path = '/api/%s/servers/%d/ips' % (self.api, int(server_id))
259
        if network:
260
            path += '/%s' % network
261
        reply = self.http_get(path)
262
        
263
        addresses = [reply['network']] if network else reply['addresses']['values']
264
        for address in addresses:
265
            print address_to_string(address)
266

    
267

    
268
@command_name('lsflv')
269
class ListFlavors(Command):
270
    description = 'list flavors'
271
    
272
    def add_options(self, parser):
273
        parser.add_option('-l', action='store_true', dest='detail', default=False,
274
                            help='show detailed output')
275
    
276
    def execute(self):
277
        path = '/api/%s/flavors' % self.api
278
        if self.detail:
279
            path += '/detail'
280
        reply = self.http_get(path)
281
        
282
        for flavor in reply['flavors']['values']:
283
            id = flavor.pop('id')
284
            name = flavor.pop('name')
285
            details = ' '.join('%s=%s' % item for item in sorted(flavor.items()))
286
            print '%3d %s %s' % (id, name, details)
287

    
288

    
289
@command_name('flvinfo')
290
class GetFlavorDetails(Command):
291
    description = 'get flavor details'
292
    syntax = '<flavor id>'
293
    
294
    def execute(self, flavor_id):
295
        path = '/api/%s/flavors/%d' % (self.api, int(flavor_id))
296
        reply = self.http_get(path)
297
        
298
        flavor = reply['flavor']
299
        id = flavor.pop('id')
300
        name = flavor.pop('name')
301
        details = ' '.join('%s=%s' % item for item in sorted(flavor.items()))
302
        print '%3d %s %s' % (id, name, details)
303

    
304

    
305
@command_name('lsimg')
306
class ListImages(Command):
307
    description = 'list images'
308
    
309
    def add_options(self, parser):
310
        parser.add_option('-l', action='store_true', dest='detail', default=False,
311
                            help='show detailed output')
312
    
313
    def execute(self):
314
        path = '/api/%s/images' % self.api
315
        if self.detail:
316
            path += '/detail'
317
        reply = self.http_get(path)
318
        
319
        for image in reply['images']['values']:
320
            id = image.pop('id')
321
            name = image.pop('name')
322
            if self.detail:
323
                print '%d %s' % (id, name)
324
                print_dict(image)
325
                print
326
            else:
327
                print '%3d %s' % (id, name)        
328

    
329

    
330
@command_name('imginfo')
331
class GetImageDetails(Command):
332
    description = 'get image details'
333
    syntax = '<image id>'
334
    
335
    def execute(self, image_id):
336
        path = '/api/%s/images/%d' % (self.api, int(image_id))
337
        reply = self.http_get(path)
338
        image = reply['image']
339
        image.pop('id')
340
        print_dict(image)
341

    
342

    
343
@command_name('createimg')
344
class CreateImage(Command):
345
    description = 'create image'
346
    syntax = '<server id> <image name>'
347
    
348
    def execute(self, server_id, name):
349
        path = '/api/%s/images' % self.api
350
        image = {'name': name, 'serverRef': int(server_id)}
351
        body = json.dumps({'image': image})
352
        reply = self.http_post(path, body)
353
        print_dict(reply['image'])
354

    
355
@command_name('deleteimg')
356
class DeleteImage(Command):
357
    description = 'delete image'
358
    syntax = '<image id>'
359
    
360
    def execute(self, image_id):
361
        path = '/api/%s/images/%d' % (self.api, int(image_id))
362
        self.http_delete(path)
363

    
364
@command_name('lsmeta')
365
class ListServerMeta(Command):
366
    description = 'list server meta'
367
    syntax = '<server id> [key]'
368

    
369
    def execute(self, server_id, key=None):
370
        path = '/api/%s/servers/%d/meta' % (self.api, int(server_id))
371
        if key:
372
            path += '/' + key
373
        reply = self.http_get(path)
374
        if key:
375
            print_dict(reply['meta'])
376
        else:
377
            print_dict(reply['metadata']['values'])
378

    
379
@command_name('setmeta')
380
class UpdateServerMeta(Command):
381
    description = 'update server meta'
382
    syntax = '<server id> <key> <val>'
383

    
384
    def execute(self, server_id, key, val):
385
        path = '/api/%s/servers/%d/meta' % (self.api, int(server_id))
386
        metadata = {key: val}
387
        body = json.dumps({'metadata': metadata})
388
        reply = self.http_post(path, body, expected_status=201)
389
        print_dict(reply['metadata'])
390

    
391
@command_name('addmeta')
392
class CreateServerMeta(Command):
393
    description = 'add server meta'
394
    syntax = '<server id> <key> <val>'
395

    
396
    def execute(self, server_id, key, val):
397
        path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key)
398
        meta = {key: val}
399
        body = json.dumps({'meta': meta})
400
        reply = self.http_put(path, body, expected_status=201)
401
        print_dict(reply['meta'])
402

    
403
@command_name('delmeta')
404
class DeleteServerMeta(Command):
405
    description = 'delete server meta'
406
    syntax = '<server id> <key>'
407

    
408
    def execute(self, server_id, key):
409
        path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key)
410
        reply = self.http_delete(path)
411

    
412
@command_name('lsimgmeta')
413
class ListImageMeta(Command):
414
    description = 'list image meta'
415
    syntax = '<image id> [key]'
416

    
417
    def execute(self, image_id, key=None):
418
        path = '/api/%s/images/%d/meta' % (self.api, int(image_id))
419
        if key:
420
            path += '/' + key
421
        reply = self.http_get(path)
422
        if key:
423
            print_dict(reply['meta'])
424
        else:
425
            print_dict(reply['metadata']['values'])
426

    
427
@command_name('setimgmeta')
428
class UpdateImageMeta(Command):
429
    description = 'update image meta'
430
    syntax = '<image id> <key> <val>'
431

    
432
    def execute(self, image_id, key, val):
433
        path = '/api/%s/images/%d/meta' % (self.api, int(image_id))
434
        metadata = {key: val}
435
        body = json.dumps({'metadata': metadata})
436
        reply = self.http_post(path, body, expected_status=201)
437
        print_dict(reply['metadata'])
438

    
439
@command_name('addimgmeta')
440
class CreateImageMeta(Command):
441
    description = 'add image meta'
442
    syntax = '<image id> <key> <val>'
443

    
444
    def execute(self, image_id, key, val):
445
        path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key)
446
        meta = {key: val}
447
        body = json.dumps({'meta': meta})
448
        reply = self.http_put(path, body, expected_status=201)
449
        print_dict(reply['meta'])
450

    
451
@command_name('delimgmeta')
452
class DeleteImageMeta(Command):
453
    description = 'delete image meta'
454
    syntax = '<image id> <key>'
455

    
456
    def execute(self, image_id, key):
457
        path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key)
458
        reply = self.http_delete(path)
459

    
460

    
461
def main():
462
    try:
463
        name = argv[1]    
464
        cls = commands[name]
465
    except (IndexError, KeyError):
466
        print 'Usage: %s <command>' % basename(argv[0])
467
        print
468
        print 'Commands:'
469
        for name, cls in sorted(commands.items()):
470
            description = getattr(cls, 'description', '')
471
            print '  %s %s' % (name.ljust(12), description)
472
        exit(1)
473
    
474
    try:
475
        commands[name](argv[2:])
476
    except TypeError:
477
        syntax = getattr(cls, 'syntax', '')
478
        if syntax:
479
            print 'Syntax: %s %s' % (name, syntax)
480
        else:
481
            print 'Invalid syntax'
482
        exit(1)
483

    
484

    
485
if __name__ == '__main__':
486
    main()