Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / management / commands / reconcile-resources-pithos.py @ d758784b

History | View | Annotate | Download (6.7 kB)

1
# Copyright 2012 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 django.core.management.base import NoArgsCommand
35

    
36
from optparse import make_option
37

    
38
from pithos.api.util import get_backend
39
from pithos.api.resources import resources
40
from pithos.backends.modular import DEFAULT_SOURCE
41

    
42
from snf_django.management import utils
43

    
44
from astakosclient.errors import QuotaLimit, NotFound
45

    
46
backend = get_backend()
47

    
48

    
49
class Command(NoArgsCommand):
50
    help = """Reconcile resource usage of Astakos with Pithos DB.
51

52
    Detect unsynchronized usage between Astakos and Pithos DB resources and
53
    synchronize them if specified so.
54

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

    
71
    def handle_noargs(self, **options):
72
        try:
73
            backend.pre_exec()
74
            userid = options['userid']
75

    
76
            # Get holding from Pithos DB
77
            db_usage = backend.node.node_account_usage(userid)
78

    
79
            users = set(db_usage.keys())
80
            if userid and userid not in users:
81
                if backend._lookup_account(userid) is None:
82
                    self.stdout.write("User '%s' does not exist in DB!\n" %
83
                                      userid)
84
                    return
85

    
86
            # Get holding from Quotaholder
87
            try:
88
                qh_result = backend.astakosclient.service_get_quotas(
89
                    backend.service_token, userid)
90
            except NotFound:
91
                self.stdout.write(
92
                    "User '%s' does not exist in Quotaholder!\n" % userid)
93
                return
94

    
95
            users.update(qh_result.keys())
96

    
97
            pending_exists = False
98
            unknown_user_exists = False
99
            unsynced = []
100
            for uuid in users:
101
                db_value = db_usage.get(uuid, 0)
102
                try:
103
                    qh_all = qh_result[uuid]
104
                except KeyError:
105
                    self.stdout.write(
106
                        "User '%s' does not exist in Quotaholder!\n" % uuid)
107
                    unknown_user_exists = True
108
                    continue
109
                else:
110
                    qh = qh_all.get(DEFAULT_SOURCE, {})
111
                    for resource in [r['name'] for r in resources]:
112
                        try:
113
                            qh_resource = qh[resource]
114
                        except KeyError:
115
                            self.stdout.write(
116
                                "Resource '%s' does not exist in Quotaholder "
117
                                "for user '%s'!\n" % (resource, uuid))
118
                            continue
119

    
120
                        if qh_resource['pending']:
121
                            self.stdout.write(
122
                                "Pending commission. "
123
                                "User '%s', resource '%s'.\n" %
124
                                (uuid, resource))
125
                            pending_exists = True
126
                            continue
127

    
128
                        qh_value = qh_resource['usage']
129

    
130
                        if db_value != qh_value:
131
                            data = (uuid, resource, db_value, qh_value)
132
                            unsynced.append(data)
133

    
134
            if unsynced:
135
                headers = ("User", "Resource", "Database", "Quotaholder")
136
                utils.pprint_table(self.stdout, unsynced, headers)
137
                if options['fix']:
138
                    request = {}
139
                    request['force'] = options['force']
140
                    request['auto_accept'] = True
141
                    request['name'] = "RECONCILE"
142
                    request['provisions'] = map(create_provision, unsynced)
143
                    try:
144
                        backend.astakosclient.issue_commission(
145
                            backend.service_token, request)
146
                    except QuotaLimit:
147
                        self.stdout.write(
148
                            "Reconciling failed because a limit has been "
149
                            "reached. Use --force to ignore the check.\n")
150
                        return
151
                    self.stdout.write("Fixed unsynced resources\n")
152

    
153
            if pending_exists:
154
                self.stdout.write(
155
                    "Found pending commissions. Run 'snf-manage"
156
                    " reconcile-commissions-pithos'\n")
157
            elif not (unsynced or unknown_user_exists):
158
                self.stdout.write("Everything in sync.\n")
159
        except:
160
            backend.post_exec(False)
161
        else:
162
            backend.post_exec(True)
163
        finally:
164
            backend.close()
165

    
166

    
167
def create_provision(provision_info):
168
    user, resource, db_value, qh_value = provision_info
169
    return {"holder": user,
170
            "source": DEFAULT_SOURCE,
171
            "resource": resource,
172
            "quantity": int(db_value - qh_value)}