Statistics
| Branch: | Tag: | Revision:

root / tools / cloud @ 40777cc8

History | View | Annotate | Download (14.8 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
TOKEN = '46e427d657b20defe352804f0eb6f8a2'
15

    
16

    
17
commands = {}
18

    
19
def command_name(name):
20
    def decorator(cls):
21
        commands[name] = cls
22
        return cls
23
    return decorator
24

    
25

    
26
def address_to_string(address):
27
    key = address['id']
28
    val = ' '.join(ip['addr'] for ip in address['values'])
29
    return '%s: %s' % (key, val)
30

    
31
def print_dict(d, show_empty=True):
32
    for key, val in sorted(d.items()):
33
        if key == 'metadata':
34
            val = ', '.join('%s="%s"' % x for x in val['values'].items())
35
        if key == 'addresses':
36
            val = ', '.join(address_to_string(address) for address in val['values'])
37
        if val or show_empty:
38
            print '%s: %s' % (key.rjust(12), val)
39

    
40

    
41
class Command(object):
42
    def __init__(self, argv):
43
        parser = OptionParser()
44
        parser.add_option('--host', dest='host', metavar='HOST', default=DEFAULT_HOST,
45
                            help='use server HOST')
46
        parser.add_option('--api', dest='api', metavar='API', default=DEFAULT_API,
47
                            help='use api API')
48
        parser.add_option('-v', action='store_true', dest='verbose', default=False,
49
                            help='use verbose output')
50
        self.add_options(parser)
51
        options, args = parser.parse_args(argv)
52
        
53
        # Add options to self
54
        for opt in parser.option_list:
55
            key = opt.dest
56
            if key:
57
                val = getattr(options, key)
58
                setattr(self, key, val)
59
        
60
        self.execute(*args)
61
    
62
    def add_options(self, parser):
63
        pass
64
    
65
    def execute(self, *args):
66
        pass
67
    
68
    def http_cmd(self, method, path, body=None, expected_status=200):
69
        conn = HTTPConnection(self.host)
70

    
71
        kwargs = {}
72
        kwargs['headers'] = {'X-Auth-Token': TOKEN}
73
        if body:
74
            kwargs['headers']['Content-Type'] = 'application/json'
75
            kwargs['body'] = body
76
        conn.request(method, path, **kwargs)
77

    
78
        resp = conn.getresponse()
79
        if self.verbose:
80
            print '%d %s' % (resp.status, resp.reason)
81
            for key, val in resp.getheaders():
82
                print '%s: %s' % (key.capitalize(), val)
83
            print
84

    
85
        buf = resp.read() or '{}'
86
        reply = json.loads(buf)
87

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

    
100
        return reply
101

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

    
108
    def http_put(self, path, body, expected_status=204):
109
        return self.http_cmd('PUT', path, body, expected_status)
110

    
111
    def http_delete(self, path, expected_status=204):
112
        return self.http_cmd('DELETE', path, None, expected_status)
113

    
114

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

    
130
        reply = self.http_get(path)
131

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

    
142

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

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

    
179

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

    
190

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

    
200

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

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

    
227

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

    
238

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

    
253

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

    
269

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

    
290

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

    
306

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

    
331

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

    
344

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
462

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

    
486

    
487
if __name__ == '__main__':
488
    main()