Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (6.2 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, CommandError
35

    
36
from optparse import make_option
37

    
38
from pithos.api.util import get_backend
39
from pithos.backends.modular import CLUSTER_NORMAL, DEFAULT_SOURCE
40
from synnefo.webproject.management import utils
41
from astakosclient.errors import QuotaLimit
42

    
43
backend = get_backend()
44

    
45

    
46
class Command(NoArgsCommand):
47
    help = """Reconcile resource usage of Astakos with Pithos DB.
48

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

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

    
68
    def handle_noargs(self, **options):
69
        try:
70
            users = (options['userid'],) if options['userid'] else None
71
            account_nodes = backend.node.node_accounts(users)
72
            if not account_nodes:
73
                raise CommandError('No users found.')
74

    
75
            db_usage = {}
76
            for path, node in account_nodes:
77
                size = backend.node.node_account_usage(node, CLUSTER_NORMAL)
78
                db_usage[path] = size or 0
79

    
80
            qh_result = backend.astakosclient.service_get_quotas(
81
                backend.service_token,
82
                users
83
            )
84

    
85
            resource = 'pithos.diskspace'
86
            pending_exists = False
87
            unknown_user_exists = False
88
            unsynced = []
89
            for uuid in db_usage.keys():
90
                db_value = db_usage[uuid]
91
                try:
92
                    qh_all = qh_result[uuid]
93
                except KeyError:
94
                    self.stdout.write(
95
                        "User '%s' does not exist in Quotaholder!\n" % uuid
96
                    )
97
                    unknown_user_exists = True
98
                    continue
99
                else:
100
                    qh = qh_all.get(DEFAULT_SOURCE, {})
101
                    try:
102
                        qh_resource = qh[resource]
103
                    except KeyError:
104
                        self.stdout.write(
105
                            "Resource '%s' does not exist in Quotaholder"
106
                            " for user '%s'!\n" % (resource, uuid))
107
                        continue
108

    
109
                    if qh_resource['pending']:
110
                        self.stdout.write(
111
                            "Pending commission. User '%s', resource '%s'.\n" %
112
                            (uuid, resource)
113
                        )
114
                        pending_exists = True
115
                        continue
116

    
117
                    qh_value = qh_resource['usage']
118

    
119
                    if  db_value != qh_value:
120
                        data = (uuid, resource, db_value, qh_value)
121
                        unsynced.append(data)
122

    
123
            if unsynced:
124
                headers = ("User", "Resource", "Database", "Quotaholder")
125
                utils.pprint_table(self.stdout, unsynced, headers)
126
                if options['fix']:
127
                    request = {}
128
                    request['force'] = options['force']
129
                    request['auto_accept'] = True
130
                    request['name'] = "RECONCILE"
131
                    request['provisions'] = map(create_provision, unsynced)
132
                    try:
133
                        backend.astakosclient.issue_commission(
134
                            backend.service_token, request
135
                            )
136
                    except QuotaLimit:
137
                        self.stdout.write(
138
                            "Reconciling failed because a limit has been "
139
                            "reached. Use --force to ignore the check.\n")
140
                        return
141

    
142

    
143
            if pending_exists:
144
                self.stdout.write(
145
                    "Found pending commissions. Run 'snf-manage"
146
                    " reconcile-commissions-pithos'\n"
147
                )
148
            elif not (unsynced or unknown_user_exists):
149
                self.stdout.write("Everything in sync.\n")
150
        finally:
151
            backend.close()
152

    
153

    
154
def create_provision(provision_info):
155
    user, resource, db_value, qh_value = provision_info
156
    return {"holder": user,
157
            "source": DEFAULT_SOURCE,
158
            "resource": resource,
159
            "quantity": db_value - qh_value}