Statistics
| Branch: | Tag: | Revision:

root / kamaki / kamaki.py @ 5d1d131b

History | View | Annotate | Download (17.9 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2011 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

    
36
import inspect
37
import logging
38
import os
39
import sys
40

    
41
from collections import defaultdict
42
from optparse import OptionParser
43

    
44
from client import Client, ClientError
45

    
46

    
47
API_ENV = 'KAMAKI_API'
48
URL_ENV = 'KAMAKI_URL'
49
TOKEN_ENV = 'KAMAKI_TOKEN'
50
RCFILE = '.kamakirc'
51

    
52

    
53
def print_addresses(addresses, margin):
54
    for address in addresses:
55
        if address['id'] == 'public':
56
            net = 'public'
57
        else:
58
            net = '%s/%s' % (address['id'], address['name'])
59
        print '%s:' % net.rjust(margin + 4)
60
        
61
        ether = address.get('mac', None)
62
        if ether:
63
            print '%s: %s' % ('ether'.rjust(margin + 8), ether)
64
        
65
        firewall = address.get('firewallProfile', None)
66
        if firewall:
67
            print '%s: %s' % ('firewall'.rjust(margin + 8), firewall)
68
        
69
        for ip in address.get('values', []):
70
            key = 'inet' if ip['version'] == 4 else 'inet6'
71
            print '%s: %s' % (key.rjust(margin + 8), ip['addr'])
72

    
73

    
74
def print_metadata(metadata, margin):
75
    print '%s:' % 'metadata'.rjust(margin)
76
    for key, val in metadata.get('values', {}).items():
77
        print '%s: %s' % (key.rjust(margin + 4), val)
78

    
79

    
80
def print_dict(d, exclude=()):
81
    if not d:
82
        return
83
    margin = max(len(key) for key in d) + 1
84
    
85
    for key, val in sorted(d.items()):
86
        if key in exclude:
87
            continue
88
        
89
        if key == 'addresses':
90
            print '%s:' % 'addresses'.rjust(margin)
91
            print_addresses(val.get('values', []), margin)
92
            continue
93
        elif key == 'metadata':
94
            print_metadata(val, margin)
95
            continue
96
        elif key == 'servers':
97
            val = ', '.join(str(x) for x in val['values'])
98
        
99
        print '%s: %s' % (key.rjust(margin), val)
100

    
101

    
102
def print_items(items, detail=False):
103
    for item in items:
104
        print '%s %s' % (item['id'], item.get('name', ''))
105
        if detail:
106
            print_dict(item, exclude=('id', 'name'))
107
            print
108

    
109

    
110
class Command(object):
111
    """Abstract class.
112
    
113
    All commands should subclass this class.
114
    """
115
    
116
    api = 'openstack'
117
    group = '<group>'
118
    name = '<command>'
119
    syntax = ''
120
    description = ''
121
    
122
    def __init__(self, argv):
123
        self._init_parser(argv)
124
        self._init_logging()
125
        self._init_conf()
126
        if self.name != '<command>':
127
            self.client = Client(self.url, self.token)
128
    
129
    def _init_parser(self, argv):
130
        parser = OptionParser()
131
        parser.usage = '%%prog %s %s %s [options]' % (self.group, self.name,
132
                                                        self.syntax)
133
        parser.add_option('--api', dest='api', metavar='API',
134
                            help='API can be either openstack or synnefo')
135
        parser.add_option('--url', dest='url', metavar='URL',
136
                            help='API URL')
137
        parser.add_option('--token', dest='token', metavar='TOKEN',
138
                            help='use token TOKEN')
139
        parser.add_option('-v', action='store_true', dest='verbose',
140
                            default=False, help='use verbose output')
141
        parser.add_option('-d', action='store_true', dest='debug',
142
                            default=False, help='use debug output')
143
        
144
        self.add_options(parser)
145
        
146
        options, args = parser.parse_args(argv)
147
        
148
        # Add options to self
149
        for opt in parser.option_list:
150
            key = opt.dest
151
            if key:
152
                val = getattr(options, key)
153
                setattr(self, key, val)
154
        
155
        self.args = args
156
        self.parser = parser
157
    
158
    def _init_logging(self):
159
        ch = logging.StreamHandler()
160
        ch.setFormatter(logging.Formatter('%(message)s'))
161
        logger = logging.getLogger()
162
        logger.addHandler(ch)
163
        
164
        if self.debug:
165
            level = logging.DEBUG
166
        elif self.verbose:
167
            level = logging.INFO
168
        else:
169
            level = logging.WARNING
170
        
171
        logger.setLevel(level)
172
    
173
    def _init_conf(self):
174
        if not self.api:
175
            self.api = os.environ.get(API_ENV, None)
176
        if not self.url:
177
            self.url = os.environ.get(URL_ENV, None)
178
        if not self.token:
179
            self.token = os.environ.get(TOKEN_ENV, None)
180
        
181
        path = os.path.join(os.path.expanduser('~'), RCFILE)
182
        if not os.path.exists(path):
183
            return
184

    
185
        for line in open(path):
186
            key, sep, val = line.partition('=')
187
            if not sep:
188
                continue
189
            key = key.strip().lower()
190
            val = val.strip()
191
            if key == 'api' and not self.api:
192
                self.api = val
193
            elif key == 'url' and not self.url:
194
                self.url = val
195
            elif key == 'token' and not self.token:
196
                self.token = val
197
    
198
    def add_options(self, parser):
199
        pass
200
    
201
    def main(self, *args):
202
        pass
203
    
204
    def execute(self):
205
        try:
206
            self.main(*self.args)
207
        except TypeError:
208
            self.parser.print_help()
209

    
210

    
211
# Server Group
212

    
213
class ListServers(Command):
214
    group = 'server'
215
    name = 'list'
216
    description = 'list servers'
217
    
218
    def add_options(self, parser):
219
        parser.add_option('-l', action='store_true', dest='detail',
220
                            default=False, help='show detailed output')
221
    
222
    def main(self):
223
        servers = self.client.list_servers(self.detail)
224
        print_items(servers, self.detail)
225

    
226

    
227
class GetServerDetails(Command):
228
    group = 'server'
229
    name = 'info'
230
    syntax = '<server id>'
231
    description = 'get server details'
232
    
233
    def main(self, server_id):
234
        server = self.client.get_server_details(int(server_id))
235
        print_dict(server)
236

    
237

    
238
class CreateServer(Command):
239
    group = 'server'
240
    name = 'create'
241
    syntax = '<server name>'
242
    description = 'create server'
243

    
244
    def add_options(self, parser):
245
        parser.add_option('-f', dest='flavor', metavar='FLAVOR_ID', default=1,
246
                            help='use flavor FLAVOR_ID')
247
        parser.add_option('-i', dest='image', metavar='IMAGE_ID', default=1,
248
                            help='use image IMAGE_ID')
249

    
250
    def main(self, name):
251
        flavor_id = int(self.flavor)
252
        image_id = int(self.image)
253
        reply = self.client.create_server(name, flavor_id, image_id)
254
        print_dict(reply)
255

    
256

    
257
class UpdateServerName(Command):
258
    group = 'server'
259
    name = 'rename'
260
    syntax = '<server id> <new name>'
261
    description = 'update server name'
262
    
263
    def main(self, server_id, new_name):
264
        self.client.update_server_name(int(server_id), new_name)
265

    
266

    
267
class DeleteServer(Command):
268
    group = 'server'
269
    name = 'delete'
270
    syntax = '<server id>'
271
    description = 'delete server'
272
    
273
    def main(self, server_id):
274
        self.client.delete_server(int(server_id))
275

    
276

    
277
class RebootServer(Command):
278
    group = 'server'
279
    name = 'reboot'
280
    syntax = '<server id>'
281
    description = 'reboot server'
282
    
283
    def add_options(self, parser):
284
        parser.add_option('-f', action='store_true', dest='hard',
285
                            default=False, help='perform a hard reboot')
286
    
287
    def main(self, server_id):
288
        self.client.reboot_server(int(server_id), self.hard)
289

    
290

    
291
class StartServer(Command):
292
    api = 'synnefo'
293
    group = 'server'
294
    name = 'start'
295
    syntax = '<server id>'
296
    description = 'start server'
297
    
298
    def main(self, server_id):
299
        self.client.start_server(int(server_id))
300

    
301

    
302
class StartServer(Command):
303
    api = 'synnefo'
304
    group = 'server'
305
    name = 'shutdown'
306
    syntax = '<server id>'
307
    description = 'shutdown server'
308
    
309
    def main(self, server_id):
310
        self.client.shutdown_server(int(server_id))
311

    
312

    
313
class ServerConsole(Command):
314
    api = 'synnefo'
315
    group = 'server'
316
    name = 'console'
317
    syntax = '<server id>'
318
    description = 'get VNC console'
319

    
320
    def main(self, server_id):
321
        reply = self.client.get_server_console(int(server_id))
322
        print_dict(reply)
323

    
324

    
325
class SetFirewallProfile(Command):
326
    api = 'synnefo'
327
    group = 'server'
328
    name = 'firewall'
329
    syntax = '<server id> <profile>'
330
    description = 'set the firewall profile'
331
    
332
    def main(self, server_id, profile):
333
        self.client.set_firewall_profile(int(server_id), profile)
334

    
335

    
336
class ListAddresses(Command):
337
    group = 'server'
338
    name = 'addr'
339
    syntax = '<server id> [network]'
340
    description = 'list server addresses'
341
    
342
    def main(self, server_id, network=None):
343
        reply = self.client.list_server_addresses(int(server_id), network)
344
        margin = max(len(x['name']) for x in reply)
345
        print_addresses(reply, margin)
346

    
347

    
348
class GetServerMeta(Command):
349
    group = 'server'
350
    name = 'meta'
351
    syntax = '<server id> [key]'
352
    description = 'get server metadata'
353
    
354
    def main(self, server_id, key=None):
355
        reply = self.client.get_server_metadata(int(server_id), key)
356
        print_dict(reply)
357

    
358

    
359
class CreateServerMetadata(Command):
360
    group = 'server'
361
    name = 'addmeta'
362
    syntax = '<server id> <key> <val>'
363
    description = 'add server metadata'
364
    
365
    def main(self, server_id, key, val):
366
        reply = self.client.create_server_metadata(int(server_id), key, val)
367
        print_dict(reply)
368

    
369

    
370
class UpdateServerMetadata(Command):
371
    group = 'server'
372
    name = 'setmeta'
373
    syntax = '<server id> <key> <val>'
374
    description = 'update server metadata'
375
    
376
    def main(self, server_id, key, val):
377
        reply = self.client.update_server_metadata(int(server_id), key, val)
378
        print_dict(reply)
379

    
380

    
381
class DeleteServerMetadata(Command):
382
    group = 'server'
383
    name = 'delmeta'
384
    syntax = '<server id> <key>'
385
    description = 'delete server metadata'
386
    
387
    def main(self, server_id, key):
388
        self.client.delete_server_metadata(int(server_id), key)
389

    
390

    
391
class GetServerStats(Command):
392
    api = 'synnefo'
393
    group = 'server'
394
    name = 'stats'
395
    syntax = '<server id>'
396
    description = 'get server statistics'
397
    
398
    def main(self, server_id):
399
        reply = self.client.get_server_stats(int(server_id))
400
        print_dict(reply, exclude=('serverRef',))
401

    
402

    
403
# Flavor Group
404

    
405
class ListFlavors(Command):
406
    group = 'flavor'
407
    name = 'list'
408
    description = 'list flavors'
409
    
410
    def add_options(self, parser):
411
        parser.add_option('-l', action='store_true', dest='detail',
412
                            default=False, help='show detailed output')
413

    
414
    def main(self):
415
        flavors = self.client.list_flavors(self.detail)
416
        print_items(flavors, self.detail)
417

    
418

    
419
class GetFlavorDetails(Command):
420
    group = 'flavor'
421
    name = 'info'
422
    syntax = '<flavor id>'
423
    description = 'get flavor details'
424
    
425
    def main(self, flavor_id):
426
        flavor = self.client.get_flavor_details(int(flavor_id))
427
        print_dict(flavor)
428

    
429

    
430
class ListImages(Command):
431
    group = 'image'
432
    name = 'list'
433
    description = 'list images'
434

    
435
    def add_options(self, parser):
436
        parser.add_option('-l', action='store_true', dest='detail',
437
                            default=False, help='show detailed output')
438

    
439
    def main(self):
440
        images = self.client.list_images(self.detail)
441
        print_items(images, self.detail)
442

    
443

    
444
class GetImageDetails(Command):
445
    group = 'image'
446
    name = 'info'
447
    syntax = '<image id>'
448
    description = 'get image details'
449
    
450
    def main(self, image_id):
451
        image = self.client.get_image_details(int(image_id))
452
        print_dict(image)
453

    
454

    
455
class CreateImage(Command):
456
    group = 'image'
457
    name = 'create'
458
    syntax = '<server id> <image name>'
459
    description = 'create image'
460
    
461
    def main(self, server_id, name):
462
        reply = self.client.create_image(int(server_id), name)
463
        print_dict(reply)
464

    
465

    
466
class DeleteImage(Command):
467
    group = 'image'
468
    name = 'delete'
469
    syntax = '<image id>'
470
    description = 'delete image'
471
    
472
    def main(self, image_id):
473
        self.client.delete_image(int(image_id))
474

    
475

    
476
class GetImageMetadata(Command):
477
    group = 'image'
478
    name = 'meta'
479
    syntax = '<image id> [key]'
480
    description = 'get image metadata'
481
    
482
    def main(self, image_id, key=None):
483
        reply = self.client.get_image_metadata(int(image_id), key)
484
        print_dict(reply)
485

    
486

    
487
class CreateImageMetadata(Command):
488
    group = 'image'
489
    name = 'addmeta'
490
    syntax = '<image id> <key> <val>'
491
    description = 'add image metadata'
492
    
493
    def main(self, image_id, key, val):
494
        reply = self.client.create_image_metadata(int(image_id), key, val)
495
        print_dict(reply)
496

    
497

    
498
class UpdateImageMetadata(Command):
499
    group = 'image'
500
    name = 'setmeta'
501
    syntax = '<image id> <key> <val>'
502
    description = 'update image metadata'
503
    
504
    def main(self, image_id, key, val):
505
        reply = self.client.update_image_metadata(int(image_id), key, val)
506
        print_dict(reply)
507

    
508

    
509
class DeleteImageMetadata(Command):
510
    group = 'image'
511
    name = 'delmeta'
512
    syntax = '<image id> <key>'
513
    description = 'delete image metadata'
514
    
515
    def main(self, image_id, key):
516
        self.client.delete_image_metadata(int(image_id), key)
517

    
518

    
519
class ListNetworks(Command):
520
    api = 'synnefo'
521
    group = 'network'
522
    name = 'list'
523
    description = 'list networks'
524
    
525
    def add_options(self, parser):
526
        parser.add_option('-l', action='store_true', dest='detail',
527
                            default=False, help='show detailed output')
528
    
529
    def main(self):
530
        networks = self.client.list_networks(self.detail)
531
        print_items(networks, self.detail)
532

    
533

    
534
class CreateNetwork(Command):
535
    api = 'synnefo'
536
    group = 'network'
537
    name = 'create'
538
    syntax = '<network name>'
539
    description = 'create a network'
540
    
541
    def main(self, name):
542
        reply = self.client.create_network(name)
543
        print_dict(reply)
544

    
545

    
546
class GetNetworkDetails(Command):
547
    api = 'synnefo'
548
    group = 'network'
549
    name = 'info'
550
    syntax = '<network id>'
551
    description = 'get network details'
552

    
553
    def main(self, network_id):
554
        network = self.client.get_network_details(network_id)
555
        print_dict(network)
556

    
557

    
558
class RenameNetwork(Command):
559
    api = 'synnefo'
560
    group = 'network'
561
    name = 'rename'
562
    syntax = '<network id> <new name>'
563
    description = 'update network name'
564
    
565
    def main(self, network_id, name):
566
        self.client.update_network_name(network_id, name)
567

    
568

    
569
class DeleteNetwork(Command):
570
    api = 'synnefo'
571
    group = 'network'
572
    name = 'delete'
573
    syntax = '<network id>'
574
    description = 'delete a network'
575
    
576
    def main(self, network_id):
577
        self.client.delete_network(network_id)
578

    
579
class ConnectServer(Command):
580
    api = 'synnefo'
581
    group = 'network'
582
    name = 'connect'
583
    syntax = '<server id> <network id>'
584
    description = 'connect a server to a network'
585
    
586
    def main(self, server_id, network_id):
587
        self.client.connect_server(server_id, network_id)
588

    
589

    
590
class DisconnectServer(Command):
591
    api = 'synnefo'
592
    group = 'network'
593
    name = 'disconnect'
594
    syntax = '<server id> <network id>'
595
    description = 'disconnect a server from a network'
596

    
597
    def main(self, server_id, network_id):
598
        self.client.disconnect_server(server_id, network_id)
599

    
600

    
601

    
602
def print_usage(exe, groups, group=None):
603
    nop = Command([])
604
    nop.parser.print_help()
605
    
606
    print
607
    print 'Commands:'
608
    
609
    if group:
610
        items = [(group, groups[group])]
611
    else:
612
        items = sorted(groups.items())
613
    
614
    for group, commands in items:
615
        for command, cls in sorted(commands.items()):
616
            name = '  %s %s' % (group, command)
617
            print '%s %s' % (name.ljust(22), cls.description)
618
        print
619

    
620

    
621
def main():
622
    nop = Command([])
623
    groups = defaultdict(dict)
624
    module = sys.modules[__name__]
625
    for name, cls in inspect.getmembers(module, inspect.isclass):
626
        if issubclass(cls, Command) and cls != Command:
627
            if nop.api == 'openstack' and nop.api != cls.api:
628
                continue    # Ignore synnefo commands
629
            groups[cls.group][cls.name] = cls
630
    
631
    argv = list(sys.argv)
632
    exe = os.path.basename(argv.pop(0))
633
    group = argv.pop(0) if argv else None
634
    command = argv.pop(0) if argv else None
635
    
636
    if group not in groups:
637
        group = None
638
    
639
    if not group or command not in groups[group]:
640
        print_usage(exe, groups, group)
641
        sys.exit(1)
642
    
643
    cls = groups[group][command]
644
    
645
    try:
646
        cmd = cls(argv)
647
        cmd.execute()
648
    except ClientError, err:
649
        logging.error('%s', err.message)
650
        logging.info('%s', err.details)
651

    
652

    
653
if __name__ == '__main__':
654
    main()