Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / management / commands / cyclades-astakos-migrate-013.py @ 18c4414d

History | View | Annotate | Download (9.3 kB)

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

    
34
import itertools
35
import warnings
36
import functools
37

    
38
from optparse import make_option
39

    
40
from django.core.management.base import NoArgsCommand, CommandError, BaseCommand
41
from django.db import transaction
42
from django.conf import settings
43

    
44
from synnefo.api.util import get_existing_users
45
from synnefo.lib.utils import case_unique
46
from synnefo.db.models import Network, VirtualMachine
47
from synnefo.userdata.models import PublicKeyPair
48

    
49
from snf_django.lib import astakos
50

    
51
def warn(*msgs):
52
    print "WARNING: %s" % ' '.join(msgs)
53

    
54
get_displayname = functools.partial(astakos.get_displayname,
55
                                 settings.CYCLADES_SERVICE_TOKEN,
56
                                 url=settings.ASTAKOS_URL.replace('im/authenticate',
57
                                                                 'service/api/user_catalogs'))
58
get_user_uuid = functools.partial(astakos.get_user_uuid,
59
                                 settings.CYCLADES_SERVICE_TOKEN,
60
                                 url=settings.ASTAKOS_URL.replace('im/authenticate',
61
                                                                 'service/api/user_catalogs'))
62

    
63
def _validate_db_state(usernames):
64

    
65
    usernames = filter(bool, usernames)
66
    invalid_case_users = case_unique(usernames)
67
    if invalid_case_users:
68
        invalid_case_users.append(invalid_case_users[0].lower())
69
        raise CommandError("Duplicate case insensitive user identifiers exist %r" % invalid_case_users)
70

    
71
    uuidusers = filter(lambda uid:'@' in uid or uid == None, usernames)
72
    if len(uuidusers) != len(usernames):
73
        warn('It seems that mixed uuid/email user identifiers exist in database.')
74
        return False
75

    
76
    return True
77

    
78

    
79
@transaction.commit_manually
80
def delete_user(username, only_stats=True, dry=True):
81
    vms = VirtualMachine.objects.filter(userid__exact=username)
82
    networks = Network.objects.filter(userid__exact=username)
83
    keys = PublicKeyPair.objects.filter(user__exact=username)
84

    
85
    if not len(list(itertools.ifilter(bool, map(lambda q: q.count(), [vms,
86
                                                                      networks,
87
                                                                      keys])))):
88
        print "No entries exist for '%s'" % username
89
        return -1
90

    
91
    if only_stats:
92
        print "The following entries will be deleted if you decide to remove this user"
93
        print "%d Virtual Machines" % vms.exclude(operstate='DESTROYED').count()
94
        print "%d Destroyed Virtual Machines" % vms.filter(operstate='DESTROYED').count()
95
        print "%d Networks" % networks.count()
96
        print "%d PublicKeyPairs" % keys.count()
97
        return
98

    
99
    for o in itertools.chain(vms, networks):
100
        o.delete()
101

    
102
    for key in keys:
103
        key.delete()
104

    
105
    if dry:
106
        print "Skipping database commit."
107
        transaction.rollback()
108
    else:
109
        transaction.commit()
110
        print "User entries removed."
111

    
112

    
113
@transaction.commit_on_success
114
def merge_user(username):
115
    vms = VirtualMachine.objects.filter(userid__iexact=username)
116
    networks = Network.objects.filter(userid__iexact=username)
117
    keys = PublicKeyPair.objects.filter(user__iexact=username)
118

    
119
    for o in itertools.chain(vms, networks):
120
        o.userid = username.lower()
121
        o.save()
122

    
123
    for key in keys:
124
        key.user = username.lower()
125
        key.save()
126

    
127

    
128
def migrate_user(username, uuid):
129
    """
130
    Warn: no transaction handling. Consider wrapping within another function.
131
    """
132
    vms = VirtualMachine.objects.filter(userid__exact=username)
133
    networks = Network.objects.filter(userid__exact=username)
134
    keys = PublicKeyPair.objects.filter(user__exact=username)
135

    
136
    for o in itertools.chain(vms, networks):
137
        o.userid = uuid or o.userid
138
        o.save()
139

    
140
    for key in keys:
141
        key.user = uuid
142
        key.save()
143

    
144

    
145
@transaction.commit_manually
146
def migrate_users(usernames, dry=True):
147
    usernames = filter(bool, usernames)
148
    count = 0
149
    for u in usernames:
150
        if not '@' in u:
151
            warn('Skipping %s. It doesn\'t seem to be an email' % u)
152
            continue
153

    
154
        try:
155
            uuid = get_user_uuid(u)
156
            print "%s -> %s" % (u, uuid)
157
            if not uuid:
158
                raise Exception("No uuid for %s" % u)
159
            migrate_user(u, uuid)
160
            count += 1
161
        except Exception, e:
162
            print "ERROR: User id migration failed (%s)" % e
163

    
164
    if dry:
165
        print "Skipping database commit."
166
        transaction.rollback()
167
    else:
168
        transaction.commit()
169
        print "Migrated %d users" % count
170

    
171

    
172
class Command(NoArgsCommand):
173
    help = "Quotas migration helper"
174

    
175
    option_list = BaseCommand.option_list + (
176
        make_option('--strict',
177
                    dest='strict',
178
                    action="store_false",
179
                    default=True,
180
                    help="Exit on warnings."),
181
        make_option('--validate-db',
182
                    dest='validate',
183
                    action="store_true",
184
                    default=True,
185
                    help=("Check if cyclades database contents are valid for "
186
                          "migration.")),
187
        make_option('--migrate-users',
188
                    dest='migrate_users',
189
                    action="store_true",
190
                    default=False,
191
                    help=("Convert emails to uuids for all users stored in "
192
                          "database.")),
193
        make_option('--merge-user',
194
                    dest='merge_user',
195
                    default=False,
196
                    help="Merge case insensitive duplicates of a user."),
197
        make_option('--delete-user',
198
                    dest='delete_user',
199
                    action='store',
200
                    default=False,
201
                    help="Delete user entries."),
202
        make_option('--user-entries',
203
                    dest='user_entries',
204
                    action='store',
205
                    default=False,
206
                    help="Display user summary."),
207
        make_option('--dry',
208
                    dest='dry',
209
                    action="store_true",
210
                    default=False,
211
                    help="Do not commit database changes. Do not communicate "
212
                         "with quotaholder"),
213
        make_option('--user',
214
                    dest='user',
215
                    action="store",
216
                    default=False,)
217
    )
218

    
219
    def resolve_conflicts(self, options):
220
        conflicting = map(options.get, ['migrate_users',
221
                                        'merge_user'])
222
        if len(filter(bool, conflicting)) > 1:
223
            raise CommandError('You can use only one of --validate,'
224
                               '--migrate-users')
225

    
226
    def handle(self, *args, **options):
227
        self.resolve_conflicts(options)
228
        self.strict = options.get('strict')
229
        self.dry = options.get('dry')
230

    
231
        if options.get('validate') and not options.get('merge_user') and not \
232
                options.get('delete_user') and not options.get('user_entries'):
233
            usernames = get_existing_users()
234
            _validate_db_state(usernames)
235

    
236
        if options.get('migrate_users'):
237
            migrate_users(usernames, dry=self.dry)
238

    
239
        if options.get('merge_user'):
240
            merge_user(options.get('merge_user'))
241
            print "Merge finished."
242

    
243
        if options.get('delete_user'):
244
            entries = delete_user(options.get('delete_user'), only_stats=True)
245
            if entries == -1:
246
                return
247

    
248
            confirm = raw_input("Type 'yes of course' if you are sure you want"
249
                                " to remove those entries: ")
250
            if not confirm == 'yes of course':
251
                return
252
            else:
253
                delete_user(options.get('delete_user'), only_stats=False,
254
                            dry=self.dry)
255

    
256
        if options.get('user_entries'):
257
            delete_user(options.get('user_entries'))