Statistics
| Branch: | Tag: | Revision:

root / tools / cloud @ f533f224

History | View | Annotate | Download (18 kB)

1
#!/usr/bin/env python
2

    
3
from httplib import HTTPConnection
4
from optparse import OptionParser
5
from os.path import basename
6
from sys import argv, exit
7

    
8
import json
9

    
10
DEFAULT_HOST = '127.0.0.1:8000'
11
DEFAULT_API = 'v1.1'
12

    
13
TOKEN = '46e427d657b20defe352804f0eb6f8a2'
14

    
15

    
16
commands = {}
17

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

    
24

    
25
def print_addresses(networks):
26
    for i, net in enumerate(networks):
27
        key = 'addresses:'.rjust(13) if i == 0 else ' ' * 13
28
        addr = ''
29
        if 'values' in net:
30
            addr = '[%s]' % ' '.join(ip['addr'] for ip in net['values'])
31
        
32
        val = '%s/%s %s %s' % (net['id'], net['name'], net['mac'], addr)
33
        print '%s %s' % (key, val)
34

    
35
def print_dict(d, show_empty=True):
36
    for key, val in sorted(d.items()):
37
        if key == 'metadata':
38
            val = ', '.join('%s="%s"' % x for x in val['values'].items())
39
        elif key == 'addresses':
40
            print_addresses(val['values'])
41
            continue
42
        elif key == 'servers':
43
            val = ', '.join(str(server_id) for server_id in val['values'])
44
        if val or show_empty:
45
            print '%s: %s' % (key.rjust(12), val)
46

    
47

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

    
78
        kwargs = {}
79
        kwargs['headers'] = {'X-Auth-Token': TOKEN}
80
        if body:
81
            kwargs['headers']['Content-Type'] = 'application/json'
82
            kwargs['body'] = body
83
        conn.request(method, path, **kwargs)
84

    
85
        resp = conn.getresponse()
86
        if self.verbose:
87
            print '%d %s' % (resp.status, resp.reason)
88
            for key, val in resp.getheaders():
89
                print '%s: %s' % (key.capitalize(), val)
90
            print
91

    
92
        buf = resp.read() or '{}'
93
        try:
94
            reply = json.loads(buf)
95
        except ValueError:
96
            print 'Invalid response from the server.'
97
            if self.verbose:
98
                print buf
99
            exit(1)
100

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

    
115
        return reply
116

    
117
    def http_get(self, path, expected_status=200):
118
        return self.http_cmd('GET', path, None, expected_status)
119
    
120
    def http_post(self, path, body, expected_status=202):
121
        return self.http_cmd('POST', path, body, expected_status)
122

    
123
    def http_put(self, path, body, expected_status=204):
124
        return self.http_cmd('PUT', path, body, expected_status)
125

    
126
    def http_delete(self, path, expected_status=204):
127
        return self.http_cmd('DELETE', path, None, expected_status)
128

    
129

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

    
145
        reply = self.http_get(path)
146

    
147
        for server in reply['servers']['values']:
148
            id = server.pop('id')
149
            name = server.pop('name')
150
            if self.detail:
151
                print '%d %s' % (id, name)
152
                print_dict(server, self.show_empty)
153
                print
154
            else:
155
                print '%3d %s' % (id, name)
156

    
157

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

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

    
194

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

    
205

    
206
@command_name('delete')
207
class DeleteServer(Command):
208
    description = 'delete server'
209
    syntax = '<server id>'
210
    
211
    def execute(self, server_id):
212
        path = '/api/%s/servers/%d' % (self.api, int(server_id))
213
        self.http_delete(path)
214

    
215

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

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

    
242

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

    
253

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

    
268

    
269
@command_name('lsaddr')
270
class ListAddresses(Command):
271
    description = 'list server addresses'
272
    syntax = '<server id> [network]'
273
    
274
    def execute(self, server_id, network=None):
275
        path = '/api/%s/servers/%d/ips' % (self.api, int(server_id))
276
        if network:
277
            path += '/%s' % network
278
        reply = self.http_get(path)
279
        
280
        addresses = [reply['network']] if network else reply['addresses']['values']
281
        print_addresses(addresses)
282

    
283

    
284
@command_name('lsflv')
285
class ListFlavors(Command):
286
    description = 'list flavors'
287
    
288
    def add_options(self, parser):
289
        parser.add_option('-l', action='store_true', dest='detail', default=False,
290
                            help='show detailed output')
291
    
292
    def execute(self):
293
        path = '/api/%s/flavors' % self.api
294
        if self.detail:
295
            path += '/detail'
296
        reply = self.http_get(path)
297
        
298
        for flavor in reply['flavors']['values']:
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('flvinfo')
306
class GetFlavorDetails(Command):
307
    description = 'get flavor details'
308
    syntax = '<flavor id>'
309
    
310
    def execute(self, flavor_id):
311
        path = '/api/%s/flavors/%d' % (self.api, int(flavor_id))
312
        reply = self.http_get(path)
313
        
314
        flavor = reply['flavor']
315
        id = flavor.pop('id')
316
        name = flavor.pop('name')
317
        details = ' '.join('%s=%s' % item for item in sorted(flavor.items()))
318
        print '%3d %s %s' % (id, name, details)
319

    
320

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

    
345

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

    
358

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

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

    
380
@command_name('lsmeta')
381
class ListServerMeta(Command):
382
    description = 'list server meta'
383
    syntax = '<server id> [key]'
384

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

    
395
@command_name('setmeta')
396
class UpdateServerMeta(Command):
397
    description = 'update server meta'
398
    syntax = '<server id> <key> <val>'
399

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

    
407
@command_name('addmeta')
408
class CreateServerMeta(Command):
409
    description = 'add server meta'
410
    syntax = '<server id> <key> <val>'
411

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

    
419
@command_name('delmeta')
420
class DeleteServerMeta(Command):
421
    description = 'delete server meta'
422
    syntax = '<server id> <key>'
423

    
424
    def execute(self, server_id, key):
425
        path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key)
426
        reply = self.http_delete(path)
427

    
428
@command_name('lsimgmeta')
429
class ListImageMeta(Command):
430
    description = 'list image meta'
431
    syntax = '<image id> [key]'
432

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

    
443
@command_name('setimgmeta')
444
class UpdateImageMeta(Command):
445
    description = 'update image meta'
446
    syntax = '<image id> <key> <val>'
447

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

    
455
@command_name('addimgmeta')
456
class CreateImageMeta(Command):
457
    description = 'add image meta'
458
    syntax = '<image id> <key> <val>'
459

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

    
467
@command_name('delimgmeta')
468
class DeleteImageMeta(Command):
469
    description = 'delete image meta'
470
    syntax = '<image id> <key>'
471

    
472
    def execute(self, image_id, key):
473
        path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key)
474
        reply = self.http_delete(path)
475

    
476

    
477
@command_name('lsnet')
478
class ListNetworks(Command):
479
    description = 'list networks'
480

    
481
    def add_options(self, parser):
482
        parser.add_option('-l', action='store_true', dest='detail', default=False,
483
                            help='show detailed output')
484

    
485
    def execute(self):
486
        path = '/api/%s/networks' % self.api
487
        if self.detail:
488
            path += '/detail'
489
        reply = self.http_get(path)
490

    
491
        for network in reply['networks']['values']:
492
            id = network.pop('id')
493
            name = network.pop('name')
494
            if self.detail:
495
                print '%s %s' % (id, name)
496
                print_dict(network)
497
                print
498
            else:
499
                print '%3s %s' % (id, name)
500

    
501

    
502
@command_name('createnet')
503
class CreateNetwork(Command):
504
    description = 'create network'
505
    syntax = '<network name>'
506

    
507
    def execute(self, name):
508
        path = '/api/%s/networks' % self.api
509
        network = {'name': name}
510
        body = json.dumps({'network': network})
511
        reply = self.http_post(path, body)
512
        print_dict(reply['network'])
513

    
514

    
515
@command_name('netinfo')
516
class GetNetworkDetails(Command):
517
    description = 'get network details'
518
    syntax = '<network id>'
519

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

    
527

    
528
@command_name('renamenet')
529
class UpdateNetworkName(Command):
530
    description = 'update network name'
531
    syntax = '<network_id> <new name>'
532

    
533
    def execute(self, network_id, name):
534
        path = '/api/%s/networks/%d' % (self.api, int(network_id))
535
        body = json.dumps({'network': {'name': name}})
536
        self.http_put(path, body)
537

    
538

    
539
@command_name('deletenet')
540
class DeleteNetwork(Command):
541
    description = 'delete network'
542
    syntax = '<network id>'
543

    
544
    def execute(self, network_id):
545
        path = '/api/%s/networks/%d' % (self.api, int(network_id))
546
        self.http_delete(path)
547

    
548

    
549
@command_name('connect')
550
class AddNetwork(Command):
551
    description = 'connect a server to a network'
552
    syntax = '<server id> <network id>'
553

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

    
559

    
560
@command_name('disconnect')
561
class RemoveNetwork(Command):
562
    description = 'disconnect a server from a network'
563
    syntax = '<server id> <network id>'
564

    
565
    def execute(self, server_id, network_id):
566
        path = '/api/%s/networks/%s/action' % (self.api, int(network_id))
567
        body = json.dumps({'remove': {'serverRef': server_id}})
568
        self.http_post(path, body, expected_status=202)
569

    
570

    
571
def print_usage():
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

    
579
def main():
580
    try:
581
        name = argv[1]    
582
        cls = commands[name]
583
    except (IndexError, KeyError):
584
        print_usage()
585
        exit(1)
586
    
587
    try:
588
        cls(argv[2:])
589
    except TypeError:
590
        syntax = getattr(cls, 'syntax', '')
591
        if syntax:
592
            print 'Syntax: %s %s' % (name, syntax)
593
        else:
594
            print 'Invalid syntax'
595
        exit(1)
596

    
597

    
598
if __name__ == '__main__':
599
    main()