Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / management / commands / reconcile-resources-astakos.py @ d9a7f0bb

History | View | Annotate | Download (6.1 kB)

1
# Copyright 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
from optparse import make_option
35

    
36
from django.core.management.base import BaseCommand, CommandError
37
from django.db import transaction
38

    
39
from snf_django.management.utils import pprint_table
40
from astakos.im.models import Component, AstakosUser
41
from astakos.im.quotas import service_get_quotas, SYSTEM
42
from astakos.im.functions import count_pending_app
43
import astakos.quotaholder_app.callpoint as qh
44
import astakos.quotaholder_app.exception as qh_exception
45

    
46

    
47
class Command(BaseCommand):
48
    help = """Reconcile resource usage of Quotaholder with Astakos DB.
49

50
    Detect unsynchronized usage between Quotaholder and Astakos DB resources
51
    and synchronize them if specified so.
52

53
    """
54

    
55
    option_list = BaseCommand.option_list + (
56
        make_option("--userid", dest="userid",
57
                    default=None,
58
                    help="Reconcile resources only for this user"),
59
        make_option("--fix", dest="fix",
60
                    default=False,
61
                    action="store_true",
62
                    help="Synchronize Quotaholder with Astakos DB."),
63
        make_option("--force",
64
                    default=False,
65
                    action="store_true",
66
                    help="Override Quotaholder. Force Quotaholder to impose"
67
                         " the quota, independently of their value.")
68
    )
69

    
70
    @transaction.commit_on_success
71
    def handle(self, *args, **options):
72
        write = self.stderr.write
73
        force = options['force']
74
        userid = options['userid']
75

    
76
        try:
77
            astakos = Component.objects.get(name="astakos")
78
        except Component.DoesNotExist:
79
            raise CommandError("Component 'astakos' not found.")
80

    
81
        query = [userid] if userid is not None else None
82
        qh_holdings = service_get_quotas(astakos, query)
83

    
84
        if userid is None:
85
            users = AstakosUser.objects.accepted()
86
        else:
87
            try:
88
                user = AstakosUser.objects.get(uuid=userid)
89
            except AstakosUser.DoesNotExist:
90
                raise CommandError("There is no user with uuid '%s'." % userid)
91
            if not user.is_accepted():
92
                raise CommandError("%s is not an accepted user." % userid)
93
            users = [user]
94

    
95
        db_holdings = count_pending_app(users)
96

    
97
        pending_exists = False
98
        unknown_user_exists = False
99
        unsynced = []
100
        for user in users:
101
            uuid = user.uuid
102
            db_value = db_holdings.get(uuid, 0)
103
            try:
104
                qh_all = qh_holdings[uuid]
105
            except KeyError:
106
                write("User '%s' does not exist in Quotaholder!\n" % uuid)
107
                unknown_user_exists = True
108
                continue
109

    
110
            # Assuming only one source
111
            system_qh = qh_all.get(SYSTEM, {})
112
            # Assuming only one resource
113
            resource = 'astakos.pending_app'
114
            try:
115
                qh_values = system_qh[resource]
116
                qh_value = qh_values['usage']
117
                qh_pending = qh_values['pending']
118
            except KeyError:
119
                write("Resource '%s' does not exist in Quotaholder"
120
                      " for user '%s'!\n" % (resource, uuid))
121
                continue
122
            if qh_pending:
123
                write("Pending commission. User '%s', resource '%s'.\n" %
124
                      (uuid, resource))
125
                pending_exists = True
126
                continue
127
            if db_value != qh_value:
128
                data = (uuid, resource, db_value, qh_value)
129
                unsynced.append(data)
130

    
131
        headers = ("User", "Resource", "Astakos", "Quotaholder")
132
        if unsynced:
133
            pprint_table(self.stdout, unsynced, headers)
134
            if options["fix"]:
135
                provisions = map(create_provision, unsynced)
136
                try:
137
                    s = qh.issue_commission('astakos', provisions,
138
                                            name='RECONCILE', force=force)
139
                except qh_exception.NoCapacityError:
140
                    write("Reconciling failed because a limit has been "
141
                          "reached. Use --force to ignore the check.\n")
142
                    return
143

    
144
                qh.resolve_pending_commission('astakos', s)
145
                write("Fixed unsynced resources\n")
146

    
147
        if pending_exists:
148
            write("Found pending commissions. "
149
                  "This is probably a bug. Please report.\n")
150
        elif not (unsynced or unknown_user_exists):
151
            write("Everything in sync.\n")
152

    
153

    
154
def create_provision(provision_info):
155
    user, resource, db_value, qh_value = provision_info
156
    return (user, SYSTEM, resource), (db_value - qh_value)