Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / quotas.py @ 548938f6

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 initial_quotas(users, flt=None):
155
    if flt is None:
156
        flt = Q()
157

    
158
    userids = [user.pk for user in users]
159
    objs = AstakosUserQuota.objects.select_related()
160
    orig_quotas = objs.filter(user__pk__in=userids).filter(flt)
161
    initial = {}
162
    for user_quota in orig_quotas:
163
        uuid = user_quota.user.uuid
164
        user_init = initial.get(uuid, {})
165
        source_quota = user_init.get(SYSTEM, {})
166
        resource = user_quota.resource.full_name()
167
        source_quota[resource] = user_quota.capacity
168
        user_init[SYSTEM] = source_quota
169
        initial[uuid] = user_init
170

    
171
    return initial
172

    
173

    
174
def get_grant_source(grant):
175
    return SYSTEM
176

    
177

    
178
def add_limits(x, y):
179
    return min(x+y, units.PRACTICALLY_INFINITE)
180

    
181

    
182
def astakos_users_quotas(users, resource=None):
183
    users = list(users)
184
    flt = Q(resource__name=resource) if resource is not None else Q()
185
    quotas = initial_quotas(users, flt=flt)
186

    
187
    userids = [user.pk for user in users]
188
    ACTUALLY_ACCEPTED = ProjectMembership.ACTUALLY_ACCEPTED
189
    objs = ProjectMembership.objects.select_related(
190
        'project', 'person', 'project__application')
191
    memberships = objs.filter(
192
        person__pk__in=userids,
193
        state__in=ACTUALLY_ACCEPTED,
194
        project__state=Project.NORMAL,
195
        project__application__state=ProjectApplication.APPROVED)
196

    
197
    apps = set(m.project.application_id for m in memberships)
198

    
199
    objs = ProjectResourceGrant.objects.select_related()
200
    grants = objs.filter(project_application__in=apps).filter(flt)
201

    
202
    for membership in memberships:
203
        uuid = membership.person.uuid
204
        userquotas = quotas.get(uuid, {})
205

    
206
        application = membership.project.application
207

    
208
        for grant in grants:
209
            if grant.project_application_id != application.id:
210
                continue
211

    
212
            source = get_grant_source(grant)
213
            source_quotas = userquotas.get(source, {})
214

    
215
            resource = grant.resource.full_name()
216
            prev = source_quotas.get(resource, 0)
217
            new = add_limits(prev, grant.member_capacity)
218
            source_quotas[resource] = new
219
            userquotas[source] = source_quotas
220
        quotas[uuid] = userquotas
221

    
222
    return quotas
223

    
224

    
225
def list_user_quotas(users, qhflt=None, initflt=None):
226
    qh_quotas = get_users_quotas(users, flt=qhflt)
227
    astakos_initial = initial_quotas(users, flt=initflt)
228
    return qh_quotas, astakos_initial
229

    
230

    
231
# Syncing to quotaholder
232

    
233
def get_users_for_update(user_ids):
234
    uids = sorted(user_ids)
235
    objs = AstakosUser.objects
236
    return list(objs.filter(id__in=uids).order_by('id').select_for_update())
237

    
238

    
239
def get_user_for_update(user_id):
240
    return get_users_for_update([user_id])[0]
241

    
242

    
243
def qh_sync_locked_users(users, resource=None):
244
    astakos_quotas = astakos_users_quotas(users, resource=resource)
245
    _set_user_quota(astakos_quotas, resource=resource)
246

    
247

    
248
def qh_sync_users(users, resource=None):
249
    uids = [user.id for user in users]
250
    users = get_users_for_update(uids)
251
    qh_sync_locked_users(users, resource=resource)
252

    
253

    
254
def qh_sync_users_diffs(users, sync=True):
255
    uids = [user.id for user in users]
256
    if sync:
257
        users = get_users_for_update(uids)
258

    
259
    astakos_quotas = astakos_users_quotas(users)
260
    qh_limits = get_users_quota_limits(users)
261
    diff_quotas = {}
262
    for holder, local in astakos_quotas.iteritems():
263
        registered = qh_limits.get(holder, None)
264
        if local != registered:
265
            diff_quotas[holder] = dict(local)
266

    
267
    if sync:
268
        _set_user_quota(diff_quotas)
269
    return qh_limits, diff_quotas
270

    
271

    
272
def qh_sync_locked_user(user):
273
    qh_sync_locked_users([user])
274

    
275

    
276
def qh_sync_user(user):
277
    qh_sync_users([user])
278

    
279

    
280
def qh_sync_new_users(users):
281
    entries = []
282
    for resource in Resource.objects.all():
283
        for user in users:
284
            entries.append(
285
                AstakosUserQuota(user=user, resource=resource,
286
                                 capacity=resource.uplimit))
287
    AstakosUserQuota.objects.bulk_create(entries)
288
    qh_sync_users(users)
289

    
290

    
291
def qh_sync_new_user(user):
292
    qh_sync_new_users([user])
293

    
294

    
295
def members_to_sync(project):
296
    objs = ProjectMembership.objects.select_related('person')
297
    memberships = objs.filter(project=project,
298
                              state__in=ProjectMembership.ACTUALLY_ACCEPTED)
299
    return set(m.person for m in memberships)
300

    
301

    
302
def qh_sync_project(project):
303
    users = members_to_sync(project)
304
    qh_sync_users(users)
305

    
306

    
307
def qh_sync_new_resource(resource):
308
    users = AstakosUser.objects.filter(
309
        moderated=True, is_rejected=False).order_by('id').select_for_update()
310

    
311
    entries = []
312
    for user in users:
313
        entries.append(
314
            AstakosUserQuota(user=user, resource=resource,
315
                             capacity=resource.uplimit))
316
    AstakosUserQuota.objects.bulk_create(entries)
317
    qh_sync_users(users, resource=resource.name)