Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / quotas.py @ 2e46be99

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

    
43

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

    
52

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

    
57

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

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

    
72

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

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

    
81

    
82
def get_users_quotas(users, resources=None, sources=None):
83
    counters = get_counters(users, resources, sources)
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):
120
    q = _level_quota_dict(quotas)
121
    qh.set_quota(q)
122

    
123

    
124
def get_default_quota():
125
    _DEFAULT_QUOTA = {}
126
    resources = Resource.objects.select_related('service').all()
127
    for resource in resources:
128
        capacity = resource.uplimit
129
        _DEFAULT_QUOTA[resource.full_name()] = capacity
130

    
131
    return _DEFAULT_QUOTA
132

    
133

    
134
SYSTEM = 'system'
135
PENDING_APP_RESOURCE = 'astakos.pending_app'
136

    
137

    
138
def register_pending_apps(user, quantity, force=False):
139
    provision = (user.uuid, SYSTEM, PENDING_APP_RESOURCE), quantity
140
    try:
141
        s = qh.issue_commission(clientkey='astakos',
142
                                force=force,
143
                                provisions=[provision])
144
    except NoCapacityError as e:
145
        limit = e.data['limit']
146
        return False, limit
147
    qh.resolve_pending_commission('astakos', s)
148
    return True, None
149

    
150

    
151
def get_pending_app_quota(user):
152
    quota = get_user_quotas(user)
153
    return quota[SYSTEM][PENDING_APP_RESOURCE]
154

    
155

    
156
def add_base_quota(user, resource, capacity):
157
    resource = Resource.objects.get(name=resource)
158
    user = get_user_for_update(user.id)
159
    obj, created = AstakosUserQuota.objects.get_or_create(
160
        user=user, resource=resource, defaults={
161
            'capacity': capacity,
162
        })
163

    
164
    if not created:
165
        obj.capacity = capacity
166
        obj.save()
167
    qh_sync_locked_user(user)
168

    
169

    
170
def remove_base_quota(user, resource):
171
    user = get_user_for_update(user.id)
172
    AstakosUserQuota.objects.filter(
173
        user=user, resource__name=resource).delete()
174
    qh_sync_locked_user(user)
175

    
176

    
177
def initial_quotas(users):
178
    users = list(users)
179
    initial = {}
180
    default_quotas = get_default_quota()
181

    
182
    for user in users:
183
        uuid = user.uuid
184
        source_quota = {SYSTEM: dict(default_quotas)}
185
        initial[uuid] = source_quota
186

    
187
    userids = [user.pk for user in users]
188
    objs = AstakosUserQuota.objects.select_related()
189
    orig_quotas = objs.filter(user__pk__in=userids)
190
    for user_quota in orig_quotas:
191
        uuid = user_quota.user.uuid
192
        user_init = initial.get(uuid, {})
193
        source_quota = user_init.get(SYSTEM, {})
194
        resource = user_quota.resource.full_name()
195
        source_quota[resource] = user_quota.capacity
196
        user_init[SYSTEM] = source_quota
197
        initial[uuid] = user_init
198

    
199
    return initial
200

    
201

    
202
def get_grant_source(grant):
203
    return SYSTEM
204

    
205

    
206
def add_limits(x, y):
207
    return min(x+y, units.PRACTICALLY_INFINITE)
208

    
209

    
210
def astakos_users_quotas(users):
211
    users = list(users)
212
    quotas = initial_quotas(users)
213

    
214
    userids = [user.pk for user in users]
215
    ACTUALLY_ACCEPTED = ProjectMembership.ACTUALLY_ACCEPTED
216
    objs = ProjectMembership.objects.select_related(
217
        'project', 'person', 'project__application')
218
    memberships = objs.filter(
219
        person__pk__in=userids,
220
        state__in=ACTUALLY_ACCEPTED,
221
        project__state=Project.NORMAL,
222
        project__application__state=ProjectApplication.APPROVED)
223

    
224
    apps = set(m.project.application_id for m in memberships)
225

    
226
    objs = ProjectResourceGrant.objects.select_related()
227
    grants = objs.filter(project_application__in=apps)
228

    
229
    for membership in memberships:
230
        uuid = membership.person.uuid
231
        userquotas = quotas.get(uuid, {})
232

    
233
        application = membership.project.application
234

    
235
        for grant in grants:
236
            if grant.project_application_id != application.id:
237
                continue
238

    
239
            source = get_grant_source(grant)
240
            source_quotas = userquotas.get(source, {})
241

    
242
            resource = grant.resource.full_name()
243
            prev = source_quotas.get(resource, 0)
244
            new = add_limits(prev, grant.member_capacity)
245
            source_quotas[resource] = new
246
            userquotas[source] = source_quotas
247
        quotas[uuid] = userquotas
248

    
249
    return quotas
250

    
251

    
252
def list_user_quotas(users):
253
    qh_quotas = get_users_quotas(users)
254
    astakos_initial = initial_quotas(users)
255
    return qh_quotas, astakos_initial
256

    
257

    
258
# Syncing to quotaholder
259

    
260
def get_users_for_update(user_ids):
261
    uids = sorted(user_ids)
262
    objs = AstakosUser.objects
263
    return list(objs.filter(id__in=uids).order_by('id').select_for_update())
264

    
265

    
266
def get_user_for_update(user_id):
267
    return get_users_for_update([user_id])[0]
268

    
269

    
270
def qh_sync_locked_users(users):
271
    astakos_quotas = astakos_users_quotas(users)
272
    _set_user_quota(astakos_quotas)
273

    
274

    
275
def qh_sync_users(users):
276
    uids = [user.id for user in users]
277
    users = get_users_for_update(uids)
278
    qh_sync_locked_users(users)
279

    
280

    
281
def qh_sync_users_diffs(users, sync=True):
282
    uids = [user.id for user in users]
283
    if sync:
284
        users = get_users_for_update(uids)
285

    
286
    astakos_quotas = astakos_users_quotas(users)
287
    qh_limits = get_users_quota_limits(users)
288
    diff_quotas = {}
289
    for holder, local in astakos_quotas.iteritems():
290
        registered = qh_limits.get(holder, None)
291
        if local != registered:
292
            diff_quotas[holder] = dict(local)
293

    
294
    if sync:
295
        _set_user_quota(diff_quotas)
296
    return qh_limits, diff_quotas
297

    
298

    
299
def qh_sync_locked_user(user):
300
    qh_sync_locked_users([user])
301

    
302

    
303
def qh_sync_user(user):
304
    qh_sync_users([user])
305

    
306

    
307
def members_to_sync(project):
308
    objs = ProjectMembership.objects.select_related('person')
309
    memberships = objs.filter(project=project,
310
                              state__in=ProjectMembership.ACTUALLY_ACCEPTED)
311
    return set(m.person for m in memberships)
312

    
313

    
314
def qh_sync_project(project):
315
    users = members_to_sync(project)
316
    qh_sync_users(users)
317

    
318

    
319
def qh_change_resource_limit(resource):
320
    objs = AstakosUser.objects.filter(
321
        Q(moderated=True, is_rejected=False) & ~Q(policy=resource))
322
    users = objs.order_by('id').select_for_update()
323
    quota = astakos_users_quotas(users)
324
    _set_user_quota(quota)
325

    
326

    
327
def qh_sync_new_resource(resource):
328
    users = AstakosUser.objects.filter(
329
        moderated=True, is_rejected=False).order_by('id').select_for_update()
330

    
331
    resource_name = resource.name
332
    limit = resource.uplimit
333
    data = []
334
    for user in users:
335
        uuid = user.uuid
336
        key = uuid, SYSTEM, resource_name
337
        data.append((key, limit))
338

    
339
    qh.set_quota(data)