Statistics
| Branch: | Tag: | Revision:

root / tools / cloud @ 432fc8c3

History | View | Annotate | Download (14.6 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 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
                print
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('flvinfo')
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
                print
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
@command_name('deleteimg')
353
class DeleteImage(Command):
354
    description = 'delete image'
355
    syntax = '<image id>'
356
    
357
    def execute(self, image_id):
358
        path = '/api/%s/images/%d' % (self.api, int(image_id))
359
        self.http_delete(path)
360

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

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

    
376
@command_name('setmeta')
377
class UpdateServerMeta(Command):
378
    description = 'update server meta'
379
    syntax = '<server id> <key> <val>'
380

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

    
388
@command_name('addmeta')
389
class CreateServerMeta(Command):
390
    description = 'add server meta'
391
    syntax = '<server id> <key> <val>'
392

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

    
400
@command_name('delmeta')
401
class DeleteServerMeta(Command):
402
    description = 'delete server meta'
403
    syntax = '<server id> <key>'
404

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

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

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

    
424
@command_name('setimgmeta')
425
class UpdateImageMeta(Command):
426
    description = 'update image meta'
427
    syntax = '<image id> <key> <val>'
428

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

    
436
@command_name('addimgmeta')
437
class CreateImageMeta(Command):
438
    description = 'add image meta'
439
    syntax = '<image id> <key> <val>'
440

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

    
448
@command_name('delimgmeta')
449
class DeleteImageMeta(Command):
450
    description = 'delete image meta'
451
    syntax = '<image id> <key>'
452

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

    
457

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

    
481

    
482
if __name__ == '__main__':
483
    main()