Statistics
| Branch: | Tag: | Revision:

root / tools / cloud @ b19653d1

History | View | Annotate | Download (17.9 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
        elif key == 'addresses':
36
            val = ', '.join(address_to_string(address) for address in val['values'])
37
        elif key == 'servers':
38
            val = ', '.join(str(server_id) for server_id in val['values'])
39
        if val or show_empty:
40
            print '%s: %s' % (key.rjust(12), val)
41

    
42

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

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

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

    
87
        buf = resp.read() or '{}'
88
        try:
89
            reply = json.loads(buf)
90
        except ValueError:
91
            print 'Invalid response from the server.'
92
            if self.verbose:
93
                print buf
94
            exit(1)
95

    
96
        # If the response status is not the expected one,
97
        # assume an error has occured and treat the body
98
        # as a cloudfault.
99
        if resp.status != expected_status:
100
            if len(reply) == 1:
101
                key = reply.keys()[0]
102
                val = reply[key]
103
                print '%s: %s' % (key, val.get('message', ''))
104
                if self.verbose:
105
                    print val.get('details', '')
106
            else:
107
                print 'Invalid response from the server.'
108
            exit(1)
109

    
110
        return reply
111

    
112
    def http_get(self, path, expected_status=200):
113
        return self.http_cmd('GET', path, None, expected_status)
114
    
115
    def http_post(self, path, body, expected_status=202):
116
        return self.http_cmd('POST', path, body, expected_status)
117

    
118
    def http_put(self, path, body, expected_status=204):
119
        return self.http_cmd('PUT', path, body, expected_status)
120

    
121
    def http_delete(self, path, expected_status=204):
122
        return self.http_cmd('DELETE', path, None, expected_status)
123

    
124

    
125
@command_name('ls')
126
class ListServers(Command):
127
    description = 'list servers'
128
    
129
    def add_options(self, parser):
130
        parser.add_option('-l', action='store_true', dest='detail', default=False,
131
                            help='show detailed output')
132
        parser.add_option('-a', action='store_true', dest='show_empty', default=False,
133
                            help='include empty values')
134
    
135
    def execute(self):
136
        path = '/api/%s/servers' % self.api
137
        if self.detail:
138
            path += '/detail'
139

    
140
        reply = self.http_get(path)
141

    
142
        for server in reply['servers']['values']:
143
            id = server.pop('id')
144
            name = server.pop('name')
145
            if self.detail:
146
                print '%d %s' % (id, name)
147
                print_dict(server, self.show_empty)
148
                print
149
            else:
150
                print '%3d %s' % (id, name)
151

    
152

    
153
@command_name('info')
154
class GetServerDetails(Command):
155
    description = 'get server details'
156
    syntax = '<server id>'
157
    
158
    def add_options(self, parser):
159
        parser.add_option('-a', action='store_true', dest='show_empty', default=False,
160
                            help='include empty values')
161
    
162
    def execute(self, server_id):
163
        path = '/api/%s/servers/%d' % (self.api, int(server_id))
164
        reply = self.http_get(path)
165
        server = reply['server']
166
        server.pop('id')
167
        print_dict(server, self.show_empty)
168
        
169

    
170
@command_name('create')
171
class CreateServer(Command):
172
    description = 'create server'
173
    syntax = '<server name>'
174
    
175
    def add_options(self, parser):
176
        parser.add_option('-f', dest='flavor', metavar='FLAVOR_ID', default=1,
177
                            help='use flavor FLAVOR_ID')
178
        parser.add_option('-i', dest='image', metavar='IMAGE_ID', default=1,
179
                            help='use image IMAGE_ID')
180
    
181
    def execute(self, name):
182
        path = '/api/%s/servers' % self.api
183
        server = {'name': name, 'flavorRef': self.flavor, 'imageRef': self.image}
184
        body = json.dumps({'server': server})
185
        reply = self.http_post(path, body)
186
        server = reply['server']
187
        print_dict(server)
188

    
189

    
190
@command_name('rename')
191
class UpdateServerName(Command):
192
    description = 'update server name'
193
    syntax = '<server id> <new name>'
194
    
195
    def execute(self, server_id, name):
196
        path = '/api/%s/servers/%d' % (self.api, int(server_id))
197
        body = json.dumps({'server': {'name': name}})
198
        self.http_put(path, body)
199

    
200

    
201
@command_name('delete')
202
class DeleteServer(Command):
203
    description = 'delete server'
204
    syntax = '<server id>'
205
    
206
    def execute(self, server_id):
207
        path = '/api/%s/servers/%d' % (self.api, int(server_id))
208
        self.http_delete(path)
209

    
210

    
211
@command_name('reboot')
212
class RebootServer(Command):
213
    description = 'reboot server'
214
    syntax = '<server id>'
215
    
216
    def add_options(self, parser):
217
        parser.add_option('-f', action='store_true', dest='hard', default=False,
218
                            help='perform a hard reboot')
219
    
220
    def execute(self, server_id):
221
        path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
222
        type = 'HARD' if self.hard else 'SOFT'
223
        body = json.dumps({'reboot': {'type': type}})
224
        self.http_post(path, body)
225
    
226

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

    
237

    
238
@command_name('shutdown')
239
class StartServer(Command):
240
    description = 'shutdown server'
241
    syntax = '<server id>'
242
    
243
    def execute(self, server_id):
244
        path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
245
        body = json.dumps({'shutdown': {}})
246
        self.http_post(path, body)
247

    
248

    
249
@command_name('console')
250
class ServerConsole(Command):
251
    description = 'get VNC console'
252
    syntax = '<server id>'
253
    
254
    def add_options(self, parser):
255
    	pass
256
    
257
    def execute(self, server_id):
258
        path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
259
        body = json.dumps({'console': {'type': 'vnc'}})
260
        reply = self.http_cmd('POST', path, body, 200)
261
        print_dict(reply['console'])
262

    
263

    
264
@command_name('lsaddr')
265
class ListAddresses(Command):
266
    description = 'list server addresses'
267
    syntax = '<server id> [network]'
268
    
269
    def execute(self, server_id, network=None):
270
        path = '/api/%s/servers/%d/ips' % (self.api, int(server_id))
271
        if network:
272
            path += '/%s' % network
273
        reply = self.http_get(path)
274
        
275
        addresses = [reply['network']] if network else reply['addresses']['values']
276
        for address in addresses:
277
            print address_to_string(address)
278

    
279

    
280
@command_name('lsflv')
281
class ListFlavors(Command):
282
    description = 'list flavors'
283
    
284
    def add_options(self, parser):
285
        parser.add_option('-l', action='store_true', dest='detail', default=False,
286
                            help='show detailed output')
287
    
288
    def execute(self):
289
        path = '/api/%s/flavors' % self.api
290
        if self.detail:
291
            path += '/detail'
292
        reply = self.http_get(path)
293
        
294
        for flavor in reply['flavors']['values']:
295
            id = flavor.pop('id')
296
            name = flavor.pop('name')
297
            details = ' '.join('%s=%s' % item for item in sorted(flavor.items()))
298
            print '%3d %s %s' % (id, name, details)
299

    
300

    
301
@command_name('flvinfo')
302
class GetFlavorDetails(Command):
303
    description = 'get flavor details'
304
    syntax = '<flavor id>'
305
    
306
    def execute(self, flavor_id):
307
        path = '/api/%s/flavors/%d' % (self.api, int(flavor_id))
308
        reply = self.http_get(path)
309
        
310
        flavor = reply['flavor']
311
        id = flavor.pop('id')
312
        name = flavor.pop('name')
313
        details = ' '.join('%s=%s' % item for item in sorted(flavor.items()))
314
        print '%3d %s %s' % (id, name, details)
315

    
316

    
317
@command_name('lsimg')
318
class ListImages(Command):
319
    description = 'list images'
320
    
321
    def add_options(self, parser):
322
        parser.add_option('-l', action='store_true', dest='detail', default=False,
323
                            help='show detailed output')
324
    
325
    def execute(self):
326
        path = '/api/%s/images' % self.api
327
        if self.detail:
328
            path += '/detail'
329
        reply = self.http_get(path)
330
        
331
        for image in reply['images']['values']:
332
            id = image.pop('id')
333
            name = image.pop('name')
334
            if self.detail:
335
                print '%d %s' % (id, name)
336
                print_dict(image)
337
                print
338
            else:
339
                print '%3d %s' % (id, name)
340

    
341

    
342
@command_name('imginfo')
343
class GetImageDetails(Command):
344
    description = 'get image details'
345
    syntax = '<image id>'
346
    
347
    def execute(self, image_id):
348
        path = '/api/%s/images/%d' % (self.api, int(image_id))
349
        reply = self.http_get(path)
350
        image = reply['image']
351
        image.pop('id')
352
        print_dict(image)
353

    
354

    
355
@command_name('createimg')
356
class CreateImage(Command):
357
    description = 'create image'
358
    syntax = '<server id> <image name>'
359
    
360
    def execute(self, server_id, name):
361
        path = '/api/%s/images' % self.api
362
        image = {'name': name, 'serverRef': int(server_id)}
363
        body = json.dumps({'image': image})
364
        reply = self.http_post(path, body)
365
        print_dict(reply['image'])
366

    
367
@command_name('deleteimg')
368
class DeleteImage(Command):
369
    description = 'delete image'
370
    syntax = '<image id>'
371
    
372
    def execute(self, image_id):
373
        path = '/api/%s/images/%d' % (self.api, int(image_id))
374
        self.http_delete(path)
375

    
376
@command_name('lsmeta')
377
class ListServerMeta(Command):
378
    description = 'list server meta'
379
    syntax = '<server id> [key]'
380

    
381
    def execute(self, server_id, key=None):
382
        path = '/api/%s/servers/%d/meta' % (self.api, int(server_id))
383
        if key:
384
            path += '/' + key
385
        reply = self.http_get(path)
386
        if key:
387
            print_dict(reply['meta'])
388
        else:
389
            print_dict(reply['metadata']['values'])
390

    
391
@command_name('setmeta')
392
class UpdateServerMeta(Command):
393
    description = 'update server meta'
394
    syntax = '<server id> <key> <val>'
395

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

    
403
@command_name('addmeta')
404
class CreateServerMeta(Command):
405
    description = 'add server meta'
406
    syntax = '<server id> <key> <val>'
407

    
408
    def execute(self, server_id, key, val):
409
        path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key)
410
        meta = {key: val}
411
        body = json.dumps({'meta': meta})
412
        reply = self.http_put(path, body, expected_status=201)
413
        print_dict(reply['meta'])
414

    
415
@command_name('delmeta')
416
class DeleteServerMeta(Command):
417
    description = 'delete server meta'
418
    syntax = '<server id> <key>'
419

    
420
    def execute(self, server_id, key):
421
        path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key)
422
        reply = self.http_delete(path)
423

    
424
@command_name('lsimgmeta')
425
class ListImageMeta(Command):
426
    description = 'list image meta'
427
    syntax = '<image id> [key]'
428

    
429
    def execute(self, image_id, key=None):
430
        path = '/api/%s/images/%d/meta' % (self.api, int(image_id))
431
        if key:
432
            path += '/' + key
433
        reply = self.http_get(path)
434
        if key:
435
            print_dict(reply['meta'])
436
        else:
437
            print_dict(reply['metadata']['values'])
438

    
439
@command_name('setimgmeta')
440
class UpdateImageMeta(Command):
441
    description = 'update image meta'
442
    syntax = '<image id> <key> <val>'
443

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

    
451
@command_name('addimgmeta')
452
class CreateImageMeta(Command):
453
    description = 'add image meta'
454
    syntax = '<image id> <key> <val>'
455

    
456
    def execute(self, image_id, key, val):
457
        path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key)
458
        meta = {key: val}
459
        body = json.dumps({'meta': meta})
460
        reply = self.http_put(path, body, expected_status=201)
461
        print_dict(reply['meta'])
462

    
463
@command_name('delimgmeta')
464
class DeleteImageMeta(Command):
465
    description = 'delete image meta'
466
    syntax = '<image id> <key>'
467

    
468
    def execute(self, image_id, key):
469
        path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key)
470
        reply = self.http_delete(path)
471

    
472

    
473
@command_name('lsnet')
474
class ListNetworks(Command):
475
    description = 'list networks'
476

    
477
    def add_options(self, parser):
478
        parser.add_option('-l', action='store_true', dest='detail', default=False,
479
                            help='show detailed output')
480

    
481
    def execute(self):
482
        path = '/api/%s/networks' % self.api
483
        if self.detail:
484
            path += '/detail'
485
        reply = self.http_get(path)
486

    
487
        for network in reply['networks']['values']:
488
            id = network.pop('id')
489
            name = network.pop('name')
490
            if self.detail:
491
                print '%d %s' % (id, name)
492
                print_dict(network)
493
                print
494
            else:
495
                print '%3d %s' % (id, name)
496

    
497

    
498
@command_name('createnet')
499
class CreateNetwork(Command):
500
    description = 'create network'
501
    syntax = '<network name>'
502

    
503
    def execute(self, name):
504
        path = '/api/%s/networks' % self.api
505
        network = {'name': name}
506
        body = json.dumps({'network': network})
507
        reply = self.http_post(path, body)
508
        print_dict(reply['network'])
509

    
510

    
511
@command_name('netinfo')
512
class GetNetworkDetails(Command):
513
    description = 'get network details'
514
    syntax = '<network id>'
515

    
516
    def execute(self, network_id):
517
        path = '/api/%s/networks/%d' % (self.api, int(network_id))
518
        reply = self.http_get(path)
519
        net = reply['network']
520
        name = net.pop('id')
521
        print_dict(net)
522

    
523

    
524
@command_name('renamenet')
525
class UpdateNetworkName(Command):
526
    description = 'update network name'
527
    syntax = '<network_id> <new name>'
528

    
529
    def execute(self, network_id, name):
530
        path = '/api/%s/networks/%d' % (self.api, int(network_id))
531
        body = json.dumps({'network': {'name': name}})
532
        self.http_put(path, body)
533

    
534

    
535
@command_name('deletenet')
536
class DeleteNetwork(Command):
537
    description = 'delete network'
538
    syntax = '<network id>'
539

    
540
    def execute(self, network_id):
541
        path = '/api/%s/networks/%d' % (self.api, int(network_id))
542
        self.http_delete(path)
543

    
544

    
545
@command_name('addnet')
546
class AddNetwork(Command):
547
    description = 'add server to a network'
548
    syntax = '<server id> <network id>'
549

    
550
    def execute(self, server_id, network_id):
551
        path = '/api/%s/networks/%d/action' % (self.api, int(network_id))
552
        body = json.dumps({'add': {'serverRef': server_id}})
553
        self.http_post(path, body, expected_status=202)
554

    
555

    
556
@command_name('removenet')
557
class RemoveNetwork(Command):
558
    description = 'remove server from a network'
559
    syntax = '<server id> <network id>'
560

    
561
    def execute(self, server_id, network_id):
562
        path = '/api/%s/networks/%s/action' % (self.api, int(network_id))
563
        body = json.dumps({'remove': {'serverRef': server_id}})
564
        self.http_post(path, body, expected_status=202)
565

    
566

    
567
def main():
568
    try:
569
        name = argv[1]    
570
        cls = commands[name]
571
    except (IndexError, KeyError):
572
        print 'Usage: %s <command>' % basename(argv[0])
573
        print
574
        print 'Commands:'
575
        for name, cls in sorted(commands.items()):
576
            description = getattr(cls, 'description', '')
577
            print '  %s %s' % (name.ljust(12), description)
578
        exit(1)
579
    
580
    try:
581
        commands[name](argv[2:])
582
    except TypeError:
583
        syntax = getattr(cls, 'syntax', '')
584
        if syntax:
585
            print 'Syntax: %s %s' % (name, syntax)
586
        else:
587
            print 'Invalid syntax'
588
        exit(1)
589

    
590

    
591
if __name__ == '__main__':
592
    main()