Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / quotas.py @ 46b6a8b4

History | View | Annotate | Download (9.9 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 synnefo.util import units
35
from astakos.im.models import (
36
    Resource, AstakosUserQuota, AstakosUser, Service,
37
    Project, ProjectMembership, ProjectResourceGrant, ProjectApplication)
38
import astakos.quotaholder_app.callpoint as qh
39
from astakos.quotaholder_app.exception import NoCapacityError
40
from django.db.models import Q
41

    
42

    
43
def from_holding(holding):
44
    limit, usage_min, usage_max = holding
45
    body = {'limit':       limit,
46
            'usage':       usage_max,
47
            'pending':     usage_max-usage_min,
48
            }
49
    return body
50

    
51

    
52
def limits_only(holding):
53
    limit, usage_min, usage_max = holding
54
    return limit
55

    
56

    
57
def transform_data(holdings, func=None):
58
    if func is None:
59
        func = from_holding
60

    
61
    quota = {}
62
    for (holder, source, resource), value in holdings.iteritems():
63
        holder_quota = quota.get(holder, {})
64
        source_quota = holder_quota.get(source, {})
65
        body = func(value)
66
        source_quota[resource] = body
67
        holder_quota[source] = source_quota
68
        quota[holder] = holder_quota
69
    return quota
70

    
71

    
72
def get_counters(users, resources=None, sources=None, flt=None):
73
    uuids = [user.uuid for user in users]
74

    
75
    counters = qh.get_quota(holders=uuids,
76
                            resources=resources,
77
                            sources=sources,
78
                            flt=flt)
79
    return counters
80

    
81

    
82
def get_users_quotas(users, resources=None, sources=None, flt=None):
83
    counters = get_counters(users, resources, sources, flt=flt)
84
    quotas = transform_data(counters)
85
    return quotas
86

    
87

    
88
def get_users_quota_limits(users, resources=None, sources=None):
89
    counters = get_counters(users, resources, sources)
90
    limits = transform_data(counters, limits_only)
91
    return limits
92

    
93

    
94
def get_user_quotas(user, resources=None, sources=None):
95
    quotas = get_users_quotas([user], resources, sources)
96
    return quotas.get(user.uuid, {})
97

    
98

    
99
def service_get_quotas(component, users=None):
100
    name_values = Service.objects.filter(
101
        component=component).values_list('name')
102
    service_names = [t for (t,) in name_values]
103
    resources = Resource.objects.filter(service_origin__in=service_names)
104
    resource_names = [r.name for r in resources]
105
    counters = qh.get_quota(holders=users, resources=resource_names)
106
    return transform_data(counters)
107

    
108

    
109
def _level_quota_dict(quotas):
110
    lst = []
111
    for holder, holder_quota in quotas.iteritems():
112
        for source, source_quota in holder_quota.iteritems():
113
            for resource, limit in source_quota.iteritems():
114
                key = (holder, source, resource)
115
                lst.append((key, limit))
116
    return lst
117

    
118

    
119
def _set_user_quota(quotas, resource=None):
120
    q = _level_quota_dict(quotas)
121
    qh.set_quota(q, resource=resource)
122

    
123

    
124
SYSTEM = 'system'
125
PENDING_APP_RESOURCE = 'astakos.pending_app'
126

    
127

    
128
def register_pending_apps(user, quantity, force=False):
129
    provision = (user.uuid, SYSTEM, PENDING_APP_RESOURCE), quantity
130
    try:
131
        s = qh.issue_commission(clientkey='astakos',
132
                                force=force,
133
                                provisions=[provision])
134
    except NoCapacityError as e:
135
        limit = e.data['limit']
136
        return False, limit
137
    qh.resolve_pending_commission('astakos', s)
138
    return True, None
139

    
140

    
141
def get_pending_app_quota(user):
142
    quota = get_user_quotas(user)
143
    return quota[SYSTEM][PENDING_APP_RESOURCE]
144

    
145

    
146
def update_base_quota(users, resource, value):
147
    userids = [user.pk for user in users]
148
    AstakosUserQuota.objects.\
149
        filter(resource__name=resource, user__pk__in=userids).\
150
        update(capacity=value)
151
    qh_sync_locked_users(users, resource=resource)
152

    
153

    
154
def _partition_by(f, l):
155
    d = {}
156
    for x in l:
157
        group = f(x)
158
        group_l = d.get(group, [])
159
        group_l.append(x)
160
        d[group] = group_l
161
    return d
162

    
163

    
164
def initial_quotas(users, flt=None):
165
    if flt is None:
166
        flt = Q()
167

    
168
    userids = [user.pk for user in users]
169
    objs = AstakosUserQuota.objects.select_related('resource')
170
    orig_quotas = objs.filter(user__pk__in=userids).filter(flt)
171
    orig_quotas = _partition_by(lambda q: q.user_id, orig_quotas)
172

    
173
    initial = {}
174
    for user in users:
175
        qs = {}
176
        for q in orig_quotas.get(user.pk, []):
177
            qs[q.resource.name] = q.capacity
178
        initial[user.uuid] = {SYSTEM: qs}
179
    return initial
180

    
181

    
182
def get_grant_source(grant):
183
    return SYSTEM
184

    
185

    
186
def add_limits(x, y):
187
    return min(x+y, units.PRACTICALLY_INFINITE)
188

    
189

    
190
def astakos_users_quotas(users, resource=None):
191
    users = list(users)
192
    flt = Q(resource__name=resource) if resource is not None else Q()
193
    quotas = initial_quotas(users, flt=flt)
194

    
195
    userids = [user.pk for user in users]
196
    ACTUALLY_ACCEPTED = ProjectMembership.ACTUALLY_ACCEPTED
197
    objs = ProjectMembership.objects.select_related(
198
        'project', 'person', 'project__application')
199
    memberships = objs.filter(
200
        person__pk__in=userids,
201
        state__in=ACTUALLY_ACCEPTED,
202
        project__state=Project.NORMAL,
203
        project__application__state=ProjectApplication.APPROVED)
204

    
205
    apps = set(m.project.application_id for m in memberships)
206

    
207
    objs = ProjectResourceGrant.objects.select_related()
208
    grants = objs.filter(project_application__in=apps).filter(flt)
209

    
210
    for membership in memberships:
211
        uuid = membership.person.uuid
212
        userquotas = quotas.get(uuid, {})
213

    
214
        application = membership.project.application
215

    
216
        for grant in grants:
217
            if grant.project_application_id != application.id:
218
                continue
219

    
220
            source = get_grant_source(grant)
221
            source_quotas = userquotas.get(source, {})
222

    
223
            resource = grant.resource.full_name()
224
            prev = source_quotas.get(resource, 0)
225
            new = add_limits(prev, grant.member_capacity)
226
            source_quotas[resource] = new
227
            userquotas[source] = source_quotas
228
        quotas[uuid] = userquotas
229

    
230
    return quotas
231

    
232

    
233
def list_user_quotas(users, qhflt=None, initflt=None):
234
    qh_quotas = get_users_quotas(users, flt=qhflt)
235
    astakos_initial = initial_quotas(users, flt=initflt)
236
    return qh_quotas, astakos_initial
237

    
238

    
239
# Syncing to quotaholder
240

    
241
def get_users_for_update(user_ids):
242
    uids = sorted(user_ids)
243
    objs = AstakosUser.objects
244
    return list(objs.filter(id__in=uids).order_by('id').select_for_update())
245

    
246

    
247
def get_user_for_update(user_id):
248
    return get_users_for_update([user_id])[0]
249

    
250

    
251
def qh_sync_locked_users(users, resource=None):
252
    astakos_quotas = astakos_users_quotas(users, resource=resource)
253
    _set_user_quota(astakos_quotas, resource=resource)
254

    
255

    
256
def qh_sync_users(users, resource=None):
257
    uids = [user.id for user in users]
258
    users = get_users_for_update(uids)
259
    qh_sync_locked_users(users, resource=resource)
260

    
261

    
262
def qh_sync_users_diffs(users, sync=True):
263
    uids = [user.id for user in users]
264
    if sync:
265
        users = get_users_for_update(uids)
266

    
267
    astakos_quotas = astakos_users_quotas(users)
268
    qh_limits = get_users_quota_limits(users)
269
    diff_quotas = {}
270
    for holder, local in astakos_quotas.iteritems():
271
        registered = qh_limits.get(holder, None)
272
        if local != registered:
273
            diff_quotas[holder] = dict(local)
274

    
275
    if sync:
276
        _set_user_quota(diff_quotas)
277
    return qh_limits, diff_quotas
278

    
279

    
280
def qh_sync_locked_user(user):
281
    qh_sync_locked_users([user])
282

    
283

    
284
def qh_sync_user(user):
285
    qh_sync_users([user])
286

    
287

    
288
def qh_sync_new_users(users):
289
    entries = []
290
    for resource in Resource.objects.all():
291
        for user in users:
292
            entries.append(
293
                AstakosUserQuota(user=user, resource=resource,
294
                                 capacity=resource.uplimit))
295
    AstakosUserQuota.objects.bulk_create(entries)
296
    qh_sync_users(users)
297

    
298

    
299
def qh_sync_new_user(user):
300
    qh_sync_new_users([user])
301

    
302

    
303
def members_to_sync(project):
304
    objs = ProjectMembership.objects.select_related('person')
305
    memberships = objs.filter(project=project,
306
                              state__in=ProjectMembership.ACTUALLY_ACCEPTED)
307
    return set(m.person for m in memberships)
308

    
309

    
310
def qh_sync_project(project):
311
    users = members_to_sync(project)
312
    qh_sync_users(users)
313

    
314

    
315
def qh_sync_new_resource(resource):
316
    users = AstakosUser.objects.filter(
317
        moderated=True, is_rejected=False).order_by('id').select_for_update()
318

    
319
    entries = []
320
    for user in users:
321
        entries.append(
322
            AstakosUserQuota(user=user, resource=resource,
323
                             capacity=resource.uplimit))
324
    AstakosUserQuota.objects.bulk_create(entries)
325
    qh_sync_users(users, resource=resource.name)