Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / management / commands / cyclades-astakos-migrate-013.py @ c83d0ada

History | View | Annotate | Download (9.7 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 functools
36

    
37
from optparse import make_option
38

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

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

    
48
from snf_django.lib import astakos
49

    
50

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

    
60

    
61
@transaction.commit_on_success
62
def merge_user(username):
63
    vms = VirtualMachine.objects.filter(userid__iexact=username)
64
    networks = Network.objects.filter(userid__iexact=username)
65
    keys = PublicKeyPair.objects.filter(user__iexact=username)
66

    
67
    for o in itertools.chain(vms, networks):
68
        o.userid = username.lower()
69
        o.save()
70

    
71
    for key in keys:
72
        key.user = username.lower()
73
        key.save()
74

    
75

    
76
def migrate_user(username, uuid):
77
    """
78
    Warn: no transaction handling. Consider wrapping within another function.
79
    """
80
    vms = VirtualMachine.objects.filter(userid__exact=username)
81
    networks = Network.objects.filter(userid__exact=username)
82
    keys = PublicKeyPair.objects.filter(user__exact=username)
83

    
84
    for o in itertools.chain(vms, networks):
85
        o.userid = uuid or o.userid
86
        o.save()
87

    
88
    for key in keys:
89
        key.user = uuid
90
        key.save()
91

    
92

    
93
class Command(BaseCommand):
94
    help = "Quotas migration helper"
95

    
96
    option_list = BaseCommand.option_list + (
97
        make_option('--strict',
98
                    dest='strict',
99
                    action="store_false",
100
                    default=True,
101
                    help="Exit on warnings."),
102
        make_option('--validate-db',
103
                    dest='validate',
104
                    action="store_true",
105
                    default=True,
106
                    help=("Check if cyclades database contents are valid for "
107
                          "migration.")),
108
        make_option('--migrate-users',
109
                    dest='migrate_users',
110
                    action="store_true",
111
                    default=False,
112
                    help=("Convert emails to uuids for all users stored in "
113
                          "database.")),
114
        make_option('--merge-user',
115
                    dest='merge_user',
116
                    default=False,
117
                    help="Merge case insensitive duplicates of a user."),
118
        make_option('--delete-user',
119
                    dest='delete_user',
120
                    action='store',
121
                    default=False,
122
                    help="Delete user entries."),
123
        make_option('--user-entries',
124
                    dest='user_entries',
125
                    action='store',
126
                    default=False,
127
                    help="Display user summary."),
128
        make_option('--dry',
129
                    dest='dry',
130
                    action="store_true",
131
                    default=False,
132
                    help="Do not commit database changes. Do not communicate "
133
                         "with quotaholder"),
134
        make_option('--user',
135
                    dest='user',
136
                    action="store",
137
                    default=False,)
138
    )
139

    
140
    def resolve_conflicts(self, options):
141
        conflicting = map(options.get, ['migrate_users',
142
                                        'merge_user'])
143
        if len(filter(bool, conflicting)) > 1:
144
            raise CommandError('You can use only one of --validate,'
145
                               '--migrate-users')
146

    
147
    def handle(self, *args, **options):
148
        self.resolve_conflicts(options)
149
        self.strict = options.get('strict')
150
        self.dry = options.get('dry')
151

    
152
        if options.get('validate') and not options.get('merge_user') and not \
153
                options.get('delete_user') and not options.get('user_entries'):
154
            usernames = get_existing_users()
155
            self._validate_db_state(usernames)
156

    
157
        if options.get('migrate_users'):
158
            self.migrate_users(usernames, dry=self.dry)
159

    
160
        if options.get('merge_user'):
161
            merge_user(options.get('merge_user'))
162
            self.stderr.write("Merge finished.")
163

    
164
        if options.get('delete_user'):
165
            entries = self.delete_user(options.get('delete_user'),
166
                                       only_stats=True)
167
            if entries == -1:
168
                return
169

    
170
            confirm = raw_input("Type 'yes of course' if you are sure you want"
171
                                " to remove those entries: ")
172
            if not confirm == 'yes of course':
173
                return
174
            else:
175
                self.delete_user(options.get('delete_user'), only_stats=False,
176
                                 dry=self.dry)
177

    
178
        if options.get('user_entries'):
179
            self.delete_user(options.get('user_entries'))
180

    
181
    @transaction.commit_manually
182
    def delete_user(self, username, only_stats=True, dry=True):
183
        vms = VirtualMachine.objects.filter(userid__exact=username)
184
        networks = Network.objects.filter(userid__exact=username)
185
        keys = PublicKeyPair.objects.filter(user__exact=username)
186

    
187
        if not len(list(itertools.ifilter(bool,
188
                                          map(lambda q: q.count(),
189
                                              [vms, networks, keys])))):
190
            self.stderr.write("No entries exist for '%s'" % username)
191
            return -1
192

    
193
        if only_stats:
194
            self.stderr.write("The following entries will be deleted if "
195
                              "you decide to remove this user")
196
            self.stderr.write("%d Virtual Machines"
197
                              % vms.exclude(operstate='DESTROYED').count())
198
            self.stderr.write("%d Destroyed Virtual Machines"
199
                              % vms.filter(operstate='DESTROYED').count())
200
            self.stderr.write("%d Networks" % networks.count())
201
            self.stderr.write("%d PublicKeyPairs" % keys.count())
202
            return
203

    
204
        for o in itertools.chain(vms, networks):
205
            o.delete()
206

    
207
        for key in keys:
208
            key.delete()
209

    
210
        if dry:
211
            self.stderr.write("Skipping database commit.")
212
            transaction.rollback()
213
        else:
214
            transaction.commit()
215
            self.stderr.write("User entries removed.")
216

    
217
    def warn(self, *msgs):
218
        self.stderr.write("WARNING: %s" % ' '.join(msgs))
219

    
220
    def _validate_db_state(self, usernames):
221

    
222
        usernames = filter(bool, usernames)
223
        invalid_case_users = case_unique(usernames)
224
        if invalid_case_users:
225
            invalid_case_users.append(invalid_case_users[0].lower())
226
            raise CommandError(
227
                "Duplicate case insensitive user identifiers exist %r"
228
                % invalid_case_users)
229

    
230
        uuidusers = filter(lambda uid: '@' in uid or uid is None, usernames)
231
        if len(uuidusers) != len(usernames):
232
            self.warn("It seems that mixed uuid/email user "
233
                      "identifiers exist in database.")
234
            return False
235

    
236
        return True
237

    
238
    @transaction.commit_manually
239
    def migrate_users(self, usernames, dry=True):
240
        usernames = filter(bool, usernames)
241
        count = 0
242
        for u in usernames:
243
            if not '@' in u:
244
                self.warn('Skipping %s. It doesn\'t seem to be an email' % u)
245
                continue
246

    
247
            try:
248
                uuid = get_user_uuid(u)
249
                self.stderr.write("%s -> %s" % (u, uuid))
250
                if not uuid:
251
                    raise Exception("No uuid for %s" % u)
252
                migrate_user(u, uuid)
253
                count += 1
254
            except Exception, e:
255
                self.stderr.write("ERROR: User id migration failed (%s)" % e)
256

    
257
        if dry:
258
            self.stderr.write("Skipping database commit.")
259
            transaction.rollback()
260
        else:
261
            transaction.commit()
262
            self.stderr.write("Migrated %d users" % count)