Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.4 kB)

1 a682eaea Kostas Papadimitriou
# Copyright 2012 GRNET S.A. All rights reserved.
2 a682eaea Kostas Papadimitriou
#
3 a682eaea Kostas Papadimitriou
# Redistribution and use in source and binary forms, with or
4 a682eaea Kostas Papadimitriou
# without modification, are permitted provided that the following
5 a682eaea Kostas Papadimitriou
# conditions are met:
6 a682eaea Kostas Papadimitriou
#
7 a682eaea Kostas Papadimitriou
#   1. Redistributions of source code must retain the above
8 a682eaea Kostas Papadimitriou
#      copyright notice, this list of conditions and the following
9 a682eaea Kostas Papadimitriou
#      disclaimer.
10 a682eaea Kostas Papadimitriou
#
11 a682eaea Kostas Papadimitriou
#   2. Redistributions in binary form must reproduce the above
12 a682eaea Kostas Papadimitriou
#      copyright notice, this list of conditions and the following
13 a682eaea Kostas Papadimitriou
#      disclaimer in the documentation and/or other materials
14 a682eaea Kostas Papadimitriou
#      provided with the distribution.
15 a682eaea Kostas Papadimitriou
#
16 a682eaea Kostas Papadimitriou
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 a682eaea Kostas Papadimitriou
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 a682eaea Kostas Papadimitriou
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 a682eaea Kostas Papadimitriou
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 a682eaea Kostas Papadimitriou
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 a682eaea Kostas Papadimitriou
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 a682eaea Kostas Papadimitriou
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 a682eaea Kostas Papadimitriou
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 a682eaea Kostas Papadimitriou
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 a682eaea Kostas Papadimitriou
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 a682eaea Kostas Papadimitriou
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 a682eaea Kostas Papadimitriou
# POSSIBILITY OF SUCH DAMAGE.
28 a682eaea Kostas Papadimitriou
#
29 a682eaea Kostas Papadimitriou
# The views and conclusions contained in the software and
30 a682eaea Kostas Papadimitriou
# documentation are those of the authors and should not be
31 a682eaea Kostas Papadimitriou
# interpreted as representing official policies, either expressed
32 a682eaea Kostas Papadimitriou
# or implied, of GRNET S.A.
33 a682eaea Kostas Papadimitriou
34 a682eaea Kostas Papadimitriou
import itertools
35 a682eaea Kostas Papadimitriou
import warnings
36 a682eaea Kostas Papadimitriou
import functools
37 a682eaea Kostas Papadimitriou
38 a682eaea Kostas Papadimitriou
from optparse import make_option
39 a682eaea Kostas Papadimitriou
40 a682eaea Kostas Papadimitriou
from django.core.management.base import NoArgsCommand, CommandError, BaseCommand
41 a682eaea Kostas Papadimitriou
from django.db import transaction
42 a682eaea Kostas Papadimitriou
from django.conf import settings
43 a682eaea Kostas Papadimitriou
44 a682eaea Kostas Papadimitriou
from synnefo.quotas import get_quota_holder
45 a682eaea Kostas Papadimitriou
from synnefo.api.util import get_existing_users
46 a682eaea Kostas Papadimitriou
from synnefo.lib.utils import case_unique
47 a682eaea Kostas Papadimitriou
from synnefo.db.models import Network, VirtualMachine
48 a682eaea Kostas Papadimitriou
from synnefo.ui.userdata.models import PublicKeyPair
49 a682eaea Kostas Papadimitriou
50 a682eaea Kostas Papadimitriou
from synnefo.lib import astakos
51 a682eaea Kostas Papadimitriou
52 a682eaea Kostas Papadimitriou
def warn(*msgs):
53 a682eaea Kostas Papadimitriou
    print "WARNING: %s" % ' '.join(msgs)
54 a682eaea Kostas Papadimitriou
55 890c2065 Sofia Papagiannaki
get_displayname = functools.partial(astakos.get_displayname,
56 a682eaea Kostas Papadimitriou
                                 settings.CYCLADES_ASTAKOS_SERVICE_TOKEN,
57 2a7276e7 Sofia Papagiannaki
                                 url=settings.ASTAKOS_URL.replace('im/authenticate',
58 2a7276e7 Sofia Papagiannaki
                                                                 'service/api/user_catalogs'))
59 a682eaea Kostas Papadimitriou
get_user_uuid = functools.partial(astakos.get_user_uuid,
60 a682eaea Kostas Papadimitriou
                                 settings.CYCLADES_ASTAKOS_SERVICE_TOKEN,
61 2a7276e7 Sofia Papagiannaki
                                 url=settings.ASTAKOS_URL.replace('im/authenticate',
62 2a7276e7 Sofia Papagiannaki
                                                                 'service/api/user_catalogs'))
63 a682eaea Kostas Papadimitriou
64 a682eaea Kostas Papadimitriou
def _validate_db_state(usernames):
65 a682eaea Kostas Papadimitriou
66 a682eaea Kostas Papadimitriou
    usernames = filter(bool, usernames)
67 a682eaea Kostas Papadimitriou
    invalid_case_users = case_unique(usernames)
68 a682eaea Kostas Papadimitriou
    if invalid_case_users:
69 a682eaea Kostas Papadimitriou
        invalid_case_users.append(invalid_case_users[0].lower())
70 a682eaea Kostas Papadimitriou
        raise CommandError("Duplicate case insensitive user identifiers exist %r" % invalid_case_users)
71 a682eaea Kostas Papadimitriou
72 a682eaea Kostas Papadimitriou
    uuidusers = filter(lambda uid:'@' in uid or uid == None, usernames)
73 a682eaea Kostas Papadimitriou
    if len(uuidusers) != len(usernames):
74 a682eaea Kostas Papadimitriou
        warn('It seems that mixed uuid/email user identifiers exist in database.')
75 a682eaea Kostas Papadimitriou
        return False
76 a682eaea Kostas Papadimitriou
77 a682eaea Kostas Papadimitriou
    return True
78 a682eaea Kostas Papadimitriou
79 a682eaea Kostas Papadimitriou
80 a682eaea Kostas Papadimitriou
@transaction.commit_manually
81 a682eaea Kostas Papadimitriou
def delete_user(username, only_stats=True, dry=True):
82 a682eaea Kostas Papadimitriou
    vms = VirtualMachine.objects.filter(userid__exact=username)
83 a682eaea Kostas Papadimitriou
    networks = Network.objects.filter(userid__exact=username)
84 a682eaea Kostas Papadimitriou
    keys = PublicKeyPair.objects.filter(user__exact=username)
85 a682eaea Kostas Papadimitriou
86 a682eaea Kostas Papadimitriou
    if not len(list(itertools.ifilter(bool, map(lambda q: q.count(), [vms,
87 a682eaea Kostas Papadimitriou
                                                                      networks,
88 a682eaea Kostas Papadimitriou
                                                                      keys])))):
89 a682eaea Kostas Papadimitriou
        print "No entries exist for '%s'" % username
90 a682eaea Kostas Papadimitriou
        return -1
91 a682eaea Kostas Papadimitriou
92 a682eaea Kostas Papadimitriou
    if only_stats:
93 a682eaea Kostas Papadimitriou
        print "The following entries will be deleted if you decide to remove this user"
94 a682eaea Kostas Papadimitriou
        print "%d Virtual Machines" % vms.exclude(operstate='DESTROYED').count()
95 a682eaea Kostas Papadimitriou
        print "%d Destroyed Virtual Machines" % vms.filter(operstate='DESTROYED').count()
96 a682eaea Kostas Papadimitriou
        print "%d Networks" % networks.count()
97 a682eaea Kostas Papadimitriou
        print "%d PublicKeyPairs" % keys.count()
98 a682eaea Kostas Papadimitriou
        return
99 a682eaea Kostas Papadimitriou
100 a682eaea Kostas Papadimitriou
    for o in itertools.chain(vms, networks):
101 a682eaea Kostas Papadimitriou
        o.delete()
102 a682eaea Kostas Papadimitriou
103 a682eaea Kostas Papadimitriou
    for key in keys:
104 a682eaea Kostas Papadimitriou
        key.delete()
105 a682eaea Kostas Papadimitriou
106 a682eaea Kostas Papadimitriou
    if dry:
107 a682eaea Kostas Papadimitriou
        print "Skipping database commit."
108 a682eaea Kostas Papadimitriou
        transaction.rollback()
109 a682eaea Kostas Papadimitriou
    else:
110 a682eaea Kostas Papadimitriou
        transaction.commit()
111 a682eaea Kostas Papadimitriou
        print "User entries removed."
112 a682eaea Kostas Papadimitriou
113 a682eaea Kostas Papadimitriou
114 a682eaea Kostas Papadimitriou
@transaction.commit_on_success
115 a682eaea Kostas Papadimitriou
def merge_user(username):
116 a682eaea Kostas Papadimitriou
    vms = VirtualMachine.objects.filter(userid__iexact=username)
117 a682eaea Kostas Papadimitriou
    networks = Network.objects.filter(userid__iexact=username)
118 a682eaea Kostas Papadimitriou
    keys = PublicKeyPair.objects.filter(user__iexact=username)
119 a682eaea Kostas Papadimitriou
120 a682eaea Kostas Papadimitriou
    for o in itertools.chain(vms, networks):
121 a682eaea Kostas Papadimitriou
        o.userid = username.lower()
122 a682eaea Kostas Papadimitriou
        o.save()
123 a682eaea Kostas Papadimitriou
124 a682eaea Kostas Papadimitriou
    for key in keys:
125 a682eaea Kostas Papadimitriou
        key.user = username.lower()
126 a682eaea Kostas Papadimitriou
        key.save()
127 a682eaea Kostas Papadimitriou
128 ec0c9d21 Kostas Papadimitriou
129 a682eaea Kostas Papadimitriou
def migrate_user(username, uuid):
130 a682eaea Kostas Papadimitriou
    """
131 a682eaea Kostas Papadimitriou
    Warn: no transaction handling. Consider wrapping within another function.
132 a682eaea Kostas Papadimitriou
    """
133 a682eaea Kostas Papadimitriou
    vms = VirtualMachine.objects.filter(userid__exact=username)
134 a682eaea Kostas Papadimitriou
    networks = Network.objects.filter(userid__exact=username)
135 a682eaea Kostas Papadimitriou
    keys = PublicKeyPair.objects.filter(user__exact=username)
136 a682eaea Kostas Papadimitriou
137 a682eaea Kostas Papadimitriou
    for o in itertools.chain(vms, networks):
138 ec0c9d21 Kostas Papadimitriou
        o.userid = uuid or o.userid
139 a682eaea Kostas Papadimitriou
        o.save()
140 a682eaea Kostas Papadimitriou
141 a682eaea Kostas Papadimitriou
    for key in keys:
142 a682eaea Kostas Papadimitriou
        key.user = uuid
143 a682eaea Kostas Papadimitriou
        key.save()
144 a682eaea Kostas Papadimitriou
145 a682eaea Kostas Papadimitriou
146 a682eaea Kostas Papadimitriou
@transaction.commit_manually
147 a682eaea Kostas Papadimitriou
def migrate_users(usernames, dry=True):
148 a682eaea Kostas Papadimitriou
    usernames = filter(bool, usernames)
149 a682eaea Kostas Papadimitriou
    count = 0
150 a682eaea Kostas Papadimitriou
    for u in usernames:
151 a682eaea Kostas Papadimitriou
        if not '@' in u:
152 a682eaea Kostas Papadimitriou
            warn('Skipping %s. It doesn\'t seem to be an email' % u)
153 a682eaea Kostas Papadimitriou
            continue
154 a682eaea Kostas Papadimitriou
155 a682eaea Kostas Papadimitriou
        try:
156 a682eaea Kostas Papadimitriou
            uuid = get_user_uuid(u)
157 a682eaea Kostas Papadimitriou
            print "%s -> %s" % (u, uuid)
158 ec0c9d21 Kostas Papadimitriou
            if not uuid:
159 ec0c9d21 Kostas Papadimitriou
                raise Exception("No uuid for %s" % u)
160 a682eaea Kostas Papadimitriou
            migrate_user(u, uuid)
161 a682eaea Kostas Papadimitriou
            count += 1
162 a682eaea Kostas Papadimitriou
        except Exception, e:
163 a682eaea Kostas Papadimitriou
            print "ERROR: User id migration failed (%s)" % e
164 a682eaea Kostas Papadimitriou
165 a682eaea Kostas Papadimitriou
    if dry:
166 a682eaea Kostas Papadimitriou
        print "Skipping database commit."
167 a682eaea Kostas Papadimitriou
        transaction.rollback()
168 a682eaea Kostas Papadimitriou
    else:
169 a682eaea Kostas Papadimitriou
        transaction.commit()
170 a682eaea Kostas Papadimitriou
        print "Migrated %d users" % count
171 a682eaea Kostas Papadimitriou
172 a682eaea Kostas Papadimitriou
173 a682eaea Kostas Papadimitriou
class Command(NoArgsCommand):
174 a682eaea Kostas Papadimitriou
    help = "Quotas migration helper"
175 a682eaea Kostas Papadimitriou
176 a682eaea Kostas Papadimitriou
    option_list = BaseCommand.option_list + (
177 a682eaea Kostas Papadimitriou
        make_option('--strict',
178 a682eaea Kostas Papadimitriou
                    dest='strict',
179 a682eaea Kostas Papadimitriou
                    action="store_false",
180 a682eaea Kostas Papadimitriou
                    default=True,
181 a682eaea Kostas Papadimitriou
                    help="Exit on warnings."),
182 a682eaea Kostas Papadimitriou
        make_option('--validate-db',
183 a682eaea Kostas Papadimitriou
                    dest='validate',
184 a682eaea Kostas Papadimitriou
                    action="store_true",
185 a682eaea Kostas Papadimitriou
                    default=True,
186 ec0c9d21 Kostas Papadimitriou
                    help=("Check if cyclades database contents are valid for "
187 ec0c9d21 Kostas Papadimitriou
                          "migration.")),
188 a682eaea Kostas Papadimitriou
        make_option('--migrate-users',
189 a682eaea Kostas Papadimitriou
                    dest='migrate_users',
190 a682eaea Kostas Papadimitriou
                    action="store_true",
191 a682eaea Kostas Papadimitriou
                    default=False,
192 ec0c9d21 Kostas Papadimitriou
                    help=("Convert emails to uuids for all users stored in "
193 ec0c9d21 Kostas Papadimitriou
                          "database.")),
194 a682eaea Kostas Papadimitriou
        make_option('--merge-user',
195 a682eaea Kostas Papadimitriou
                    dest='merge_user',
196 a682eaea Kostas Papadimitriou
                    default=False,
197 a682eaea Kostas Papadimitriou
                    help="Merge case insensitive duplicates of a user."),
198 a682eaea Kostas Papadimitriou
        make_option('--delete-user',
199 a682eaea Kostas Papadimitriou
                    dest='delete_user',
200 a682eaea Kostas Papadimitriou
                    action='store',
201 a682eaea Kostas Papadimitriou
                    default=False,
202 a682eaea Kostas Papadimitriou
                    help="Delete user entries."),
203 a682eaea Kostas Papadimitriou
        make_option('--user-entries',
204 a682eaea Kostas Papadimitriou
                    dest='user_entries',
205 a682eaea Kostas Papadimitriou
                    action='store',
206 a682eaea Kostas Papadimitriou
                    default=False,
207 a682eaea Kostas Papadimitriou
                    help="Display user summary."),
208 a682eaea Kostas Papadimitriou
        make_option('--dry',
209 a682eaea Kostas Papadimitriou
                    dest='dry',
210 a682eaea Kostas Papadimitriou
                    action="store_true",
211 a682eaea Kostas Papadimitriou
                    default=False,
212 a682eaea Kostas Papadimitriou
                    help="Do not commit database changes. Do not communicate "
213 a682eaea Kostas Papadimitriou
                         "with quotaholder"),
214 a682eaea Kostas Papadimitriou
        make_option('--user',
215 a682eaea Kostas Papadimitriou
                    dest='user',
216 a682eaea Kostas Papadimitriou
                    action="store",
217 a682eaea Kostas Papadimitriou
                    default=False,)
218 a682eaea Kostas Papadimitriou
    )
219 a682eaea Kostas Papadimitriou
220 a682eaea Kostas Papadimitriou
    def resolve_conflicts(self, options):
221 a682eaea Kostas Papadimitriou
        conflicting = map(options.get, ['migrate_users',
222 a682eaea Kostas Papadimitriou
                                        'merge_user'])
223 a682eaea Kostas Papadimitriou
        if len(filter(bool, conflicting)) > 1:
224 a682eaea Kostas Papadimitriou
            raise CommandError('You can use only one of --validate,'
225 ec0c9d21 Kostas Papadimitriou
                               '--migrate-users')
226 a682eaea Kostas Papadimitriou
227 a682eaea Kostas Papadimitriou
    def handle(self, *args, **options):
228 a682eaea Kostas Papadimitriou
        self.resolve_conflicts(options)
229 a682eaea Kostas Papadimitriou
        self.strict = options.get('strict')
230 a682eaea Kostas Papadimitriou
        self.dry = options.get('dry')
231 a682eaea Kostas Papadimitriou
232 a682eaea Kostas Papadimitriou
        if options.get('validate') and not options.get('merge_user') and not \
233 a682eaea Kostas Papadimitriou
                options.get('delete_user') and not options.get('user_entries'):
234 a682eaea Kostas Papadimitriou
            usernames = get_existing_users()
235 a682eaea Kostas Papadimitriou
            _validate_db_state(usernames)
236 a682eaea Kostas Papadimitriou
237 a682eaea Kostas Papadimitriou
        if options.get('migrate_users'):
238 a682eaea Kostas Papadimitriou
            migrate_users(usernames, dry=self.dry)
239 a682eaea Kostas Papadimitriou
240 a682eaea Kostas Papadimitriou
        if options.get('merge_user'):
241 a682eaea Kostas Papadimitriou
            merge_user(options.get('merge_user'))
242 a682eaea Kostas Papadimitriou
            print "Merge finished."
243 a682eaea Kostas Papadimitriou
244 a682eaea Kostas Papadimitriou
        if options.get('delete_user'):
245 a682eaea Kostas Papadimitriou
            entries = delete_user(options.get('delete_user'), only_stats=True)
246 a682eaea Kostas Papadimitriou
            if entries == -1:
247 a682eaea Kostas Papadimitriou
                return
248 a682eaea Kostas Papadimitriou
249 a682eaea Kostas Papadimitriou
            confirm = raw_input("Type 'yes of course' if you are sure you want"
250 a682eaea Kostas Papadimitriou
                                " to remove those entries: ")
251 a682eaea Kostas Papadimitriou
            if not confirm == 'yes of course':
252 a682eaea Kostas Papadimitriou
                return
253 a682eaea Kostas Papadimitriou
            else:
254 a682eaea Kostas Papadimitriou
                delete_user(options.get('delete_user'), only_stats=False,
255 a682eaea Kostas Papadimitriou
                            dry=self.dry)
256 a682eaea Kostas Papadimitriou
257 a682eaea Kostas Papadimitriou
        if options.get('user_entries'):
258 a682eaea Kostas Papadimitriou
            delete_user(options.get('user_entries'))