Statistics
| Branch: | Tag: | Revision:

root / tools / cloud @ d8e50a39

History | View | Annotate | Download (12.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('lsaddr')
235
class ListAddresses(Command):
236
    description = 'list server addresses'
237
    syntax = '<server id> [network]'
238
    
239
    def execute(self, server_id, network=None):
240
        path = '/api/%s/servers/%d/ips' % (self.api, int(server_id))
241
        if network:
242
            path += '/%s' % network
243
        reply = self.http_get(path)
244
        
245
        addresses = [reply['network']] if network else reply['addresses']['values']
246
        for address in addresses:
247
            print address_to_string(address)
248

    
249

    
250
@command_name('lsflv')
251
class ListFlavors(Command):
252
    description = 'list flavors'
253
    
254
    def add_options(self, parser):
255
        parser.add_option('-l', action='store_true', dest='detail', default=False,
256
                            help='show detailed output')
257
    
258
    def execute(self):
259
        path = '/api/%s/flavors' % self.api
260
        if self.detail:
261
            path += '/detail'
262
        reply = self.http_get(path)
263
        
264
        for flavor in reply['flavors']['values']:
265
            id = flavor.pop('id')
266
            name = flavor.pop('name')
267
            details = ' '.join('%s=%s' % item for item in sorted(flavor.items()))
268
            print '%3d %s %s' % (id, name, details)
269

    
270

    
271
@command_name('flvinfo')
272
class GetFlavorDetails(Command):
273
    description = 'get flavor details'
274
    syntax = '<flavor id>'
275
    
276
    def execute(self, flavor_id):
277
        path = '/api/%s/flavors/%d' % (self.api, int(flavor_id))
278
        reply = self.http_get(path)
279
        
280
        flavor = reply['flavor']
281
        id = flavor.pop('id')
282
        name = flavor.pop('name')
283
        details = ' '.join('%s=%s' % item for item in sorted(flavor.items()))
284
        print '%3d %s %s' % (id, name, details)
285

    
286

    
287
@command_name('lsimg')
288
class ListImages(Command):
289
    description = 'list images'
290
    
291
    def add_options(self, parser):
292
        parser.add_option('-l', action='store_true', dest='detail', default=False,
293
                            help='show detailed output')
294
    
295
    def execute(self):
296
        path = '/api/%s/images' % self.api
297
        if self.detail:
298
            path += '/detail'
299
        reply = self.http_get(path)
300
        
301
        for image in reply['images']['values']:
302
            id = image.pop('id')
303
            name = image.pop('name')
304
            if self.detail:
305
                print '%d %s' % (id, name)
306
                print_dict(image)
307
                print
308
            else:
309
                print '%3d %s' % (id, name)        
310

    
311

    
312
@command_name('imginfo')
313
class GetImageDetails(Command):
314
    description = 'get image details'
315
    syntax = '<image id>'
316
    
317
    def execute(self, image_id):
318
        path = '/api/%s/images/%d' % (self.api, int(image_id))
319
        reply = self.http_get(path)
320
        image = reply['image']
321
        image.pop('id')
322
        print_dict(image)
323

    
324

    
325
@command_name('createimg')
326
class CreateImage(Command):
327
    description = 'create image'
328
    syntax = '<server id> <image name>'
329
    
330
    def execute(self, server_id, name):
331
        path = '/api/%s/images' % self.api
332
        image = {'name': name, 'serverRef': int(server_id)}
333
        body = json.dumps({'image': image})
334
        reply = self.http_post(path, body)
335
        print_dict(reply['image'])
336

    
337
@command_name('deleteimg')
338
class DeleteImage(Command):
339
    description = 'delete image'
340
    syntax = '<image id>'
341
    
342
    def execute(self, image_id):
343
        path = '/api/%s/images/%d' % (self.api, int(image_id))
344
        self.http_delete(path)
345

    
346
@command_name('lsmeta')
347
class ListServerMeta(Command):
348
    description = 'list server meta'
349
    syntax = '<server id> [key]'
350

    
351
    def execute(self, server_id, key=None):
352
        path = '/api/%s/servers/%d/meta' % (self.api, int(server_id))
353
        if key:
354
            path += '/' + key
355
        reply = self.http_get(path)
356
        if key:
357
            print_dict(reply['meta'])
358
        else:
359
            print_dict(reply['metadata']['values'])
360

    
361
@command_name('setmeta')
362
class UpdateServerMeta(Command):
363
    description = 'update server meta'
364
    syntax = '<server id> <key> <val>'
365

    
366
    def execute(self, server_id, key, val):
367
        path = '/api/%s/servers/%d/meta' % (self.api, int(server_id))
368
        metadata = {key: val}
369
        body = json.dumps({'metadata': metadata})
370
        reply = self.http_post(path, body, expected_status=201)
371
        print_dict(reply['metadata'])
372

    
373
@command_name('addmeta')
374
class CreateServerMeta(Command):
375
    description = 'add server meta'
376
    syntax = '<server id> <key> <val>'
377

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

    
385
@command_name('delmeta')
386
class DeleteServerMeta(Command):
387
    description = 'delete server meta'
388
    syntax = '<server id> <key>'
389

    
390
    def execute(self, server_id, key):
391
        path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key)
392
        reply = self.http_delete(path)
393

    
394

    
395
def main():
396
    try:
397
        name = argv[1]    
398
        cls = commands[name]
399
    except (IndexError, KeyError):
400
        print 'Usage: %s <command>' % basename(argv[0])
401
        print
402
        print 'Commands:'
403
        for name, cls in sorted(commands.items()):
404
            description = getattr(cls, 'description', '')
405
            print '  %s %s' % (name.ljust(12), description)
406
        exit(1)
407
    
408
    try:
409
        commands[name](argv[2:])
410
    except TypeError:
411
        syntax = getattr(cls, 'syntax', '')
412
        if syntax:
413
            print 'Syntax: %s %s' % (name, syntax)
414
        else:
415
            print 'Invalid syntax'
416
        exit(1)
417

    
418

    
419
if __name__ == '__main__':
420
    main()