Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / management / commands / pithos-usage.py @ 8108c68c

History | View | Annotate | Download (7.9 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 collections import namedtuple
37
from optparse import make_option
38
from sqlalchemy import func
39
from sqlalchemy.sql import select, and_, or_
40

    
41
from pithos.api.util import get_backend
42
from pithos.backends.modular import (
43
    CLUSTER_NORMAL, CLUSTER_HISTORY, CLUSTER_DELETED
44
)
45
clusters = (CLUSTER_NORMAL, CLUSTER_HISTORY, CLUSTER_DELETED)
46

    
47
Usage = namedtuple('Usage', ('node', 'path', 'size', 'cluster'))
48
GetQuota = namedtuple('GetQuota', ('entity', 'resource', 'key'))
49

    
50
class ResetHoldingPayload(namedtuple('ResetHoldingPayload', (
51
    'entity', 'resource', 'key', 'imported', 'exported', 'returned', 'released'
52
))):
53
    __slots__ = ()
54

    
55
    def __str__(self):
56
        return '%s: %s' % (self.entity, self.imported)
57

    
58

    
59
ENTITY_KEY = '1'
60

    
61
backend = get_backend()
62
table = {}
63
table['nodes'] = backend.node.nodes
64
table['versions'] = backend.node.versions
65
table['policy'] = backend.node.policy
66
conn = backend.node.conn
67

    
68
def _retrieve_user_nodes(users=()):
69
    s = select([table['nodes'].c.path, table['nodes'].c.node])
70
    s = s.where(and_(table['nodes'].c.node != 0,
71
                     table['nodes'].c.parent == 0))
72
    if users:
73
        s = s.where(table['nodes'].c.path.in_(users))
74
    return conn.execute(s).fetchall()
75

    
76
def _compute_usage(nodes):
77
    usage = []
78
    append = usage.append
79
    for path, node in nodes:
80
        select_children = select(
81
            [table['nodes'].c.node]).where(table['nodes'].c.parent == node)
82
        select_descendants = select([table['nodes'].c.node]).where(
83
            or_(table['nodes'].c.parent.in_(select_children),
84
                table['nodes'].c.node.in_(select_children)))
85
        s = select([table['versions'].c.cluster,
86
                    func.sum(table['versions'].c.size)])
87
        s = s.group_by(table['versions'].c.cluster)
88
        s = s.where(table['nodes'].c.node == table['versions'].c.node)
89
        s = s.where(table['nodes'].c.node.in_(select_descendants))
90
        s = s.where(table['versions'].c.cluster == CLUSTER_NORMAL)
91
        d2 = dict(conn.execute(s).fetchall())
92

    
93
        try:
94
            size = d2[CLUSTER_NORMAL]
95
        except KeyError:
96
            size = 0
97
        append(Usage(
98
            node=node,
99
            path=path,
100
            size=size,
101
            cluster=CLUSTER_NORMAL))
102
    return usage
103

    
104
def _get_quotaholder_usage(usage):
105
    payload = []
106
    append = payload.append
107
    [append(GetQuota(
108
        entity=item.path,
109
        resource='pithos+.diskspace',
110
        key=ENTITY_KEY
111
    )) for item in usage]
112

    
113
    result = backend.quotaholder.get_quota(
114
        context={}, clientkey='pithos', get_quota=payload
115
    )
116
    return dict((entity, imported - exported + returned - released) for (
117
        entity, resource, quantity, capacity, import_limit, export_limit,
118
        imported, exported, returned, released, flags
119
    ) in result)
120

    
121

    
122
def _prepare_reset_holding(usage, only_diverging=False):
123
    """Verify usage and set quotaholder user usage"""
124
    reset_holding = []
125
    append = reset_holding.append
126

    
127
    quotaholder_usage = {}
128
    if only_diverging:
129
        quotaholder_usage = _get_quotaholder_usage(usage)
130

    
131
    for item in(usage):
132
        if only_diverging and quotaholder_usage.get(item.path) == item.size:
133
            continue
134

    
135
        if item.cluster == CLUSTER_NORMAL:
136
            append(ResetHoldingPayload(
137
                    entity=item.path,
138
                    resource='pithos+.diskspace',
139
                    key=ENTITY_KEY,
140
                    imported=item.size,
141
                    exported=0,
142
                    returned=0,
143
                    released=0))
144
    return reset_holding
145

    
146

    
147
class Command(NoArgsCommand):
148
    help = "List and reset pithos usage"
149

    
150
    option_list = NoArgsCommand.option_list + (
151
        make_option('--list',
152
                    dest='list',
153
                    action="store_true",
154
                    default=True,
155
                    help="List usage for all or specified user"),
156
        make_option('--reset',
157
                    dest='reset',
158
                    action="store_true",
159
                    default=False,
160
                    help="Reset usage for all or specified users"),
161
        make_option('--diverging',
162
                    dest='diverging',
163
                    action="store_true",
164
                    default=False,
165
                    help=("List or reset diverging usages")),
166
        make_option('--user',
167
                    dest='users',
168
                    action='append',
169
                    metavar='USER_UUID',
170
                    help=("Specify which users --list or --reset applies."
171
                          "This option can be repeated several times."
172
                          "If no user is specified --list or --reset will be applied globally.")),
173
    )
174

    
175
    def handle_noargs(self, **options):
176
        try:
177
            user_nodes = _retrieve_user_nodes(options['users'])
178
            if not user_nodes:
179
                raise CommandError('No users found.')
180
            usage = _compute_usage(user_nodes)
181
            reset_holding = _prepare_reset_holding(
182
                usage, only_diverging=options['diverging']
183
            )
184

    
185
            if options['list']:
186
                print '\n'.join([str(i) for i in reset_holding])
187

    
188
            if options['reset']:
189
                if not backend.quotaholder_enabled:
190
                    raise CommandError('Quotaholder component is not enabled')
191

    
192
                if not backend.quotaholder_url:
193
                    raise CommandError('Quotaholder url is not set')
194

    
195
                if not backend.quotaholder_token:
196
                    raise CommandError('Quotaholder token is not set')
197

    
198
                while True:
199
                    result = backend.quotaholder.reset_holding(
200
                        context={},
201
                        clientkey='pithos',
202
                        reset_holding=reset_holding)
203

    
204
                    if not result:
205
                        break
206

    
207
                    missing_entities = [reset_holding[x].entity for x in result]
208
                    self.stdout.write(
209
                            'Unknown quotaholder users: %s\n' %
210
                            ', '.join(missing_entities))
211
                    m = 'Retrying sending quota usage for the rest...\n'
212
                    self.stdout.write(m)
213
                    missing_indexes = set(result)
214
                    reset_holding = [x for i, x in enumerate(reset_holding)
215
                                     if i not in missing_indexes]
216
        finally:
217
            backend.close()