Statistics
| Branch: | Tag: | Revision:

root / tools / snf-admin @ 13d2b7f7

History | View | Annotate | Download (13 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
from django.core.management import setup_environ
37
try:
38
    from synnefo import settings
39
except ImportError:
40
    raise Exception("Cannot import settings, make sure PYTHONPATH contains "
41
                    "the parent directory of the Synnefo Django project.")
42
setup_environ(settings)
43

    
44
import inspect
45
import logging
46
import sys
47

    
48
from collections import defaultdict
49
from optparse import OptionParser
50
from os.path import basename
51

    
52
from synnefo.db import models
53

    
54

    
55
def get_user(uid):
56
    try:
57
        uid = int(uid)
58
        return models.SynnefoUser.objects.get(id=uid)
59
    except ValueError:
60
        return None
61
    except models.SynnefoUser.DoesNotExist:
62
        return None
63

    
64
def print_dict(d, exclude=()):
65
    if not d:
66
        return
67
    margin = max(len(key) for key in d) + 1
68

    
69
    for key, val in sorted(d.items()):
70
        if key in exclude or key.startswith('_'):
71
            continue
72
        print '%s: %s' % (key.rjust(margin), val)
73

    
74
def print_item(item):
75
    print '%d %s' % (item.id, item.name)
76
    print_dict(item.__dict__, exclude=('id', 'name'))
77

    
78
def print_items(items, detail=False):
79
    for item in items:
80
        print '%d %s' % (item.id, item.name)
81
        if detail:
82
            print_dict(item.__dict__, exclude=('id', 'name'))
83
            print
84

    
85

    
86
class Command(object):
87
    group = '<group>'
88
    name = '<command>'
89
    syntax = ''
90
    description = ''
91
    
92
    def __init__(self, exe, argv):
93
        parser = OptionParser()
94
        syntax = '%s [options]' % self.syntax if self.syntax else '[options]'
95
        parser.usage = '%s %s %s' % (exe, self.name, syntax)
96
        parser.description = self.description
97
        self.add_options(parser)
98
        options, self.args = parser.parse_args(argv)
99
        
100
        # Add options to self
101
        for opt in parser.option_list:
102
            key = opt.dest
103
            if key:
104
                val = getattr(options, key)
105
                setattr(self, key, val)
106
        
107
        ch = logging.StreamHandler()
108
        ch.setFormatter(logging.Formatter('%(message)s'))
109
        logger = logging.getLogger()
110
        logger.addHandler(ch)
111
        level = logging.WARNING
112
        logger.setLevel(level)
113
        
114
        self.parser = parser
115
    
116
    def add_options(self, parser):
117
        pass
118
    
119
    def execute(self):
120
        try:
121
            self.main(*self.args)
122
        except TypeError:
123
            self.parser.print_help()
124

    
125

    
126
# Server commands
127

    
128
class ListServers(Command):
129
    group = 'server'
130
    name = 'list'
131
    syntax = '[server id]'
132
    description = 'list servers'
133
    
134
    def add_options(self, parser):
135
        parser.add_option('-a', action='store_true', dest='show_deleted',
136
                        default=False, help='also list deleted servers')
137
        parser.add_option('-l', action='store_true', dest='detail',
138
                        default=False, help='show detailed output')
139
        parser.add_option('-u', dest='uid', metavar='UID',
140
                            help='show servers of user with id UID')
141
    
142
    def main(self, server_id=None):
143
        servers = models.VirtualMachine.objects.order_by('id')
144
        if server_id:
145
            servers = servers.filter(id=server_id)
146
        if not self.show_deleted:
147
            servers = servers.filter(deleted=False)
148
        if self.uid:
149
            user = get_user(self.uid)
150
            if user:
151
                servers = servers.filter(owner=user)
152
            else:
153
                print 'Unknown user id'
154
                return
155
        
156
        print_items(servers, self.detail)
157

    
158

    
159
# User commands
160

    
161
class ListUsers(Command):
162
    group = 'user'
163
    name = 'list'
164
    syntax = '[user id]'
165
    description = 'list users'
166
    
167
    def add_options(self, parser):
168
        parser.add_option('-l', action='store_true', dest='detail',
169
                        default=False, help='show detailed output')
170
    
171
    def main(self, user_id=None):
172
        if user_id:
173
            users = [models.SynnefoUser.objects.get(id=user_id)]
174
        else:
175
            users = models.SynnefoUser.objects.order_by('id')
176
        print_items(users, self.detail)
177

    
178

    
179
class ModifyUser(Command):
180
    group = 'user'
181
    name = 'modify'
182
    syntax = '<user id>'
183
    description = 'modify a user'
184
    
185
    def add_options(self, parser):
186
        parser.add_option('--credit', dest='credit', metavar='VALUE',
187
                            help='set user credits')
188
        parser.add_option('--invitations', dest='invitations',
189
                            metavar='VALUE', help='set max invitations')
190
        parser.add_option('--realname', dest='realname', metavar='NAME',
191
                            help='set real name of user')
192
        parser.add_option('--type', dest='type', metavar='TYPE',
193
                            help='set user type')
194
        parser.add_option('--username', dest='username', metavar='NAME',
195
                            help='set username')
196
    
197
    def main(self, user_id):
198
        user = get_user(user_id)
199
        
200
        if self.credit:
201
            user.credit = self.credit
202
        if self.invitations:
203
            user.max_invitations = self.invitations
204
        if self.realname:
205
            user.realname = self.realname
206
        if self.type:
207
            allowed = [x[0] for x in models.SynnefoUser.ACCOUNT_TYPE]
208
            if self.type not in allowed:
209
                print 'Invalid type'
210
                return
211
            user.type = self.type
212
        if self.username:
213
            user.name = self.username
214
        
215
        user.save()
216
        print_item(user)
217

    
218

    
219
# Image commands
220

    
221
class ListImages(Command):
222
    group = 'image'
223
    name = 'list'
224
    syntax = '[image id]'
225
    description = 'list images'
226
    
227
    def add_options(self, parser):
228
        parser.add_option('-l', action='store_true', dest='detail',
229
                        default=False, help='show detailed output')
230
    
231
    def main(self, image_id=None):
232
        if image_id:
233
            images = [models.Image.objects.get(id=image_id)]
234
        else:
235
            images = models.Image.objects.order_by('id')
236
        print_items(images, self.detail)
237

    
238

    
239
class RegisterImage(Command):
240
    group = 'image'
241
    name = 'register'
242
    syntax = '<name> <backend id>'
243
    description = 'register an image'
244
    
245
    def add_options(self, parser):
246
        parser.add_option('--public', action='store_true', dest='public',
247
                            default=False, help='make image public')
248
        parser.add_option('-u', dest='uid', metavar='UID',
249
                            help='assign image to user with id UID')
250
    
251
    def main(self, name, backend_id):
252
        user = None
253
        if self.uid:
254
            user = get_user(self.uid)
255
            if not user:
256
                print 'Unknown user id'
257
                return
258
        
259
        image = models.Image.objects.create(
260
            name=name,
261
            state='ACTIVE',
262
            owner=user,
263
            backend_id=backend_id,
264
            public=self.public)
265
        
266
        print_item(image)
267

    
268

    
269
class ModifyImage(Command):
270
    group = 'image'
271
    name = 'modify'
272
    syntax = '<image id>'
273
    description = 'modify an image'
274
    
275
    def add_options(self, parser):
276
        parser.add_option('-b', dest='backend_id', metavar='BACKEND_ID',
277
                            help='set image backend id')
278
        parser.add_option('-f', dest='format', metavar='FORMAT',
279
                            help='set image format')
280
        parser.add_option('-n', dest='name', metavar='NAME',
281
                            help='set image name')
282
        parser.add_option('--public', action='store_true', dest='public',
283
                            default=False, help='make image public')
284
        parser.add_option('--nopublic', action='store_true', dest='private',
285
                            default=False, help='make image private')
286
        parser.add_option('-s', dest='state', metavar='STATE',
287
                            default=False, help='set image state')
288
        parser.add_option('-u', dest='uid', metavar='UID',
289
                            help='assign image to user with id UID')
290
    
291
    def main(self, image_id):
292
        try:
293
            image = models.Image.objects.get(id=image_id)
294
        except:
295
            print 'Image not found'
296
            return
297
        
298
        if self.backend_id:
299
            image.backend_id = self.backend_id
300
        if self.format:
301
            allowed = [x[0] for x in models.Image.FORMATS]
302
            if self.format not in allowed:
303
                print 'Invalid format'
304
                return
305
            image.format = self.format
306
        if self.name:
307
            image.name = self.name
308
        if self.public:
309
            image.public = True
310
        if self.private:
311
            image.public = False
312
        if self.state:
313
            allowed = [x[0] for x in models.Image.IMAGE_STATES]
314
            if self.state not in allowed:
315
                print 'Invalid state'
316
                return
317
            image.state = self.state
318
        if self.uid:
319
            image.owner = get_user(self.uid)
320
        
321
        image.save()
322
        print_item(image)
323

    
324

    
325
class ModifyImageMeta(Command):
326
    group = 'image'
327
    name = 'meta'
328
    syntax = '<image id> [key[=val]]'
329
    description = 'get and manipulate image metadata'
330
    
331
    def main(self, image_id, arg=''):
332
        try:
333
            image = models.Image.objects.get(id=image_id)
334
        except:
335
            print 'Image not found'
336
            return
337
        
338
        key, sep, val = arg.partition('=')
339
        if not sep:
340
            val = None
341
        
342
        if not key:
343
            metadata = {}
344
            for meta in image.imagemetadata_set.order_by('meta_key'):
345
                metadata[meta.meta_key] = meta.meta_value
346
            print_dict(metadata)
347
            return
348
        
349
        try:
350
            meta = image.imagemetadata_set.get(meta_key=key)
351
        except models.ImageMetadata.DoesNotExist:
352
            meta = None
353
        
354
        if val is None:
355
            if meta:
356
                print_dict({key: meta.meta_value})
357
            return
358
        
359
        if val:
360
            if not meta:
361
                meta = image.imagemetadata_set.create(meta_key=key)
362
            meta.meta_value = val
363
            meta.save()
364
        else:
365
            # Delete if val is empty
366
            if meta:
367
                meta.delete()
368

    
369

    
370
def print_usage(exe, groups, group=None, shortcut=False):
371
    nop = Command(exe, [])
372
    nop.parser.print_help()
373
    if group:
374
        groups = {group: groups[group]}
375

    
376
    print
377
    print 'Commands:'
378
    
379
    for group, commands in sorted(groups.items()):
380
        for command, cls in sorted(commands.items()):
381
            name = '  %s %s' % (group, command)
382
            print '%s %s' % (name.ljust(22), cls.description)
383
        print
384

    
385

    
386
def main():
387
    groups = defaultdict(dict)
388
    module = sys.modules[__name__]
389
    for name, cls in inspect.getmembers(module, inspect.isclass):
390
        if not issubclass(cls, Command) or cls == Command:
391
            continue
392
        groups[cls.group][cls.name] = cls
393
    
394
    argv = list(sys.argv)
395
    exe = basename(argv.pop(0))
396
    prefix, sep, suffix = exe.partition('-')
397
    if sep and prefix == 'snf' and suffix in groups:
398
        # Allow shortcut aliases like snf-image, snf-server, etc
399
        group = suffix
400
    else:
401
        group = argv.pop(0) if argv else None
402
        if group in groups:
403
            exe = '%s %s' % (exe, group)
404
        else:
405
            exe = '%s <group>' % exe
406
            group = None
407
    
408
    command = argv.pop(0) if argv else None
409
    
410
    if group not in groups or command not in groups[group]:
411
        print_usage(exe, groups, group)
412
        sys.exit(1)
413
    
414
    cls = groups[group][command]
415
    cmd = cls(exe, argv)
416
    cmd.execute()
417

    
418

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