Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / quotas.py @ ff5edb80

History | View | Annotate | Download (11.3 kB)

1
# Copyright 2013-2014 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 astakos.im.models import (
35
    Resource, AstakosUser, Service,
36
    Project, ProjectMembership, ProjectResourceQuota)
37
import astakos.quotaholder_app.callpoint as qh
38
from astakos.quotaholder_app.exception import NoCapacityError
39
from django.db.models import Q
40
from collections import defaultdict
41

    
42

    
43
QuotaDict = lambda: defaultdict(lambda: defaultdict(dict))
44

    
45
PROJECT_TAG = "project:"
46
USER_TAG = "user:"
47

    
48
def project_ref(value):
49
    return  PROJECT_TAG + value
50

    
51

    
52
def get_project_ref(project):
53
    return project_ref(project.uuid)
54

    
55

    
56
def user_ref(value):
57
    return USER_TAG + value
58

    
59

    
60
def get_user_ref(user):
61
    return user_ref(user.uuid)
62

    
63

    
64
def from_holding(holding, is_project=False):
65
    limit, usage_min, usage_max = holding
66
    prefix = 'project_' if is_project else ''
67
    body = {prefix+'limit':   limit,
68
            prefix+'usage':   usage_max,
69
            prefix+'pending': usage_max-usage_min,
70
            }
71
    return body
72

    
73

    
74
def get_user_counters(users, resources=None, sources=None, flt=None):
75
    holders = [get_user_ref(user) for user in users]
76
    return qh.get_quota(holders=holders,
77
                        resources=resources,
78
                        sources=sources,
79
                        flt=flt)
80

    
81

    
82
def get_project_counters(projects, resources=None, sources=None):
83
    holders = [get_project_ref(project) for project in projects]
84
    return qh.get_quota(holders=holders,
85
                        resources=resources,
86
                        sources=sources)
87

    
88

    
89
def strip_names(counters):
90
    stripped = {}
91
    for ((holder, source, resource), value) in counters.iteritems():
92
        prefix, sep, holder = holder.partition(":")
93
        assert prefix in ["user", "project"]
94
        if source is not None:
95
            prefix, sep, source = source.partition(":")
96
            assert prefix == "project"
97
        stripped[(holder, source, resource)] = value
98
    return stripped
99

    
100

    
101
def get_related_sources(counters):
102
    projects = set()
103
    for (holder, source, resource) in counters.iterkeys():
104
        projects.add(source)
105
    return list(projects)
106

    
107

    
108
def mk_quota_dict(users_counters, project_counters):
109
    quota = QuotaDict()
110
    for (holder, source, resource), u_value in users_counters.iteritems():
111
        p_value = project_counters[(source, None, resource)]
112
        values_dict = from_holding(u_value)
113
        values_dict.update(from_holding(p_value, is_project=True))
114
        quota[holder][source][resource] = values_dict
115
    return quota
116

    
117

    
118
def get_users_quotas_counters(users, resources=None, sources=None, flt=None):
119
    user_counters = get_user_counters(users, resources, sources, flt=flt)
120
    projects = get_related_sources(user_counters)
121
    project_counters = qh.get_quota(holders=projects, resources=resources)
122
    return strip_names(user_counters), strip_names(project_counters)
123

    
124

    
125
def get_users_quotas(users, resources=None, sources=None, flt=None):
126
    u_c, p_c = get_users_quotas_counters(users, resources, sources, flt=flt)
127
    return mk_quota_dict(u_c, p_c)
128

    
129

    
130
def get_user_quotas(user, resources=None, sources=None):
131
    quotas = get_users_quotas([user], resources, sources)
132
    return quotas.get(user.uuid, {})
133

    
134

    
135
def service_get_quotas(component, users=None):
136
    name_values = Service.objects.filter(
137
        component=component).values_list('name')
138
    service_names = [t for (t,) in name_values]
139
    resources = Resource.objects.filter(service_origin__in=service_names)
140
    resource_names = [r.name for r in resources]
141
    astakosusers = AstakosUser.objects.verified()
142
    if users is not None:
143
        astakosusers = astakosusers.filter(uuid__in=users)
144
    return get_users_quotas(astakosusers, resources=resource_names)
145

    
146

    
147
def mk_limits_dict(counters):
148
    quota = QuotaDict()
149
    for (holder, source, resource), (limit, _, _) in counters.iteritems():
150
        quota[holder][source][resource] = limit
151
    return quota
152

    
153

    
154
def mk_project_quota_dict(project_counters):
155
    quota = QuotaDict()
156
    for (holder, _, resource), p_value in project_counters.iteritems():
157
        values_dict = from_holding(p_value, is_project=True)
158
        quota[holder][resource] = values_dict
159
    return quota
160

    
161

    
162
def get_projects_quota(projects, resources=None, sources=None):
163
    project_counters = get_project_counters(projects, resources, sources)
164
    return mk_project_quota_dict(strip_names(project_counters))
165

    
166

    
167
def service_get_project_quotas(component, projects=None):
168
    name_values = Service.objects.filter(
169
        component=component).values_list('name')
170
    service_names = [t for (t,) in name_values]
171
    resources = Resource.objects.filter(service_origin__in=service_names)
172
    resource_names = [r.name for r in resources]
173
    ps = Project.objects.initialized()
174
    if projects is not None:
175
        ps = ps.filter(uuid__in=projects)
176
    return get_projects_quota(ps, resources=resource_names)
177

    
178

    
179
def get_project_quota(project, resources=None, sources=None):
180
    quotas = get_projects_quota([project], resources, sources)
181
    return quotas.get(project.uuid, {})
182

    
183

    
184
def get_projects_quota_limits():
185
    project_counters = qh.get_quota(flt=Q(holder__startswith=PROJECT_TAG))
186
    user_counters = qh.get_quota(flt=Q(holder__startswith=USER_TAG))
187
    return mk_limits_dict(project_counters), mk_limits_dict(user_counters)
188

    
189

    
190
def _level_quota_dict(quotas):
191
    lst = []
192
    for holder, holder_quota in quotas.iteritems():
193
        for source, source_quota in holder_quota.iteritems():
194
            for resource, limit in source_quota.iteritems():
195
                key = (holder, source, resource)
196
                lst.append((key, limit))
197
    return lst
198

    
199

    
200
def set_quota(quotas, resource=None):
201
    q = _level_quota_dict(quotas)
202
    qh.set_quota(q, resource=resource)
203

    
204

    
205
PENDING_APP_RESOURCE = 'astakos.pending_app'
206

    
207

    
208
def mk_user_provision(user, source, resource, quantity):
209
    holder = user_ref(user)
210
    source = project_ref(source)
211
    return (holder, source, resource), quantity
212

    
213

    
214
def mk_project_provision(project, resource, quantity):
215
    holder = project_ref(project)
216
    return (holder, None, resource), quantity
217

    
218

    
219
def _mk_provisions(holder, source, resource, quantity):
220
    return [((holder, source, resource), quantity),
221
            ((source, None, resource), quantity)]
222

    
223

    
224
def register_pending_apps(user, project, quantity, force=False):
225
    provisions = _mk_provisions(get_user_ref(user), get_project_ref(project),
226
                                PENDING_APP_RESOURCE, quantity)
227
    try:
228
        s = qh.issue_commission(clientkey='astakos',
229
                                force=force,
230
                                provisions=provisions)
231
    except NoCapacityError as e:
232
        limit = e.data['limit']
233
        return False, limit
234
    qh.resolve_pending_commission('astakos', s)
235
    return True, None
236

    
237

    
238
def get_pending_app_quota(user):
239
    quota = get_user_quotas(user)
240
    source = user.base_project.uuid
241
    return quota[source][PENDING_APP_RESOURCE]
242

    
243

    
244
def _partition_by(f, l):
245
    d = {}
246
    for x in l:
247
        group = f(x)
248
        group_l = d.get(group, [])
249
        group_l.append(x)
250
        d[group] = group_l
251
    return d
252

    
253

    
254
def astakos_project_quotas(projects, resource=None):
255
    objs = ProjectResourceQuota.objects.select_related()
256
    flt = Q(resource__name=resource) if resource is not None else Q()
257
    grants = objs.filter(project__in=projects).filter(flt)
258
    grants_d = _partition_by(lambda g: g.project_id, grants)
259

    
260
    objs = ProjectMembership.objects
261
    memberships = objs.initialized(projects).select_related(
262
        "person", "project")
263
    memberships_d = _partition_by(lambda m: m.project_id, memberships)
264

    
265
    user_quota = QuotaDict()
266
    project_quota = QuotaDict()
267

    
268
    for project in projects:
269
        pr_ref = get_project_ref(project)
270
        state = project.state
271
        if state not in Project.INITIALIZED_STATES:
272
            continue
273

    
274
        project_grants = grants_d.get(project.id, [])
275
        project_memberships = memberships_d.get(project.id, [])
276
        for grant in project_grants:
277
            resource = grant.resource.name
278
            val = grant.project_capacity if state == Project.NORMAL else 0
279
            project_quota[pr_ref][None][resource] = val
280
            for membership in project_memberships:
281
                u_ref = get_user_ref(membership.person)
282
                val = grant.member_capacity if membership.is_active() else 0
283
                user_quota[u_ref][pr_ref][resource] = val
284

    
285
    return project_quota, user_quota
286

    
287

    
288
def list_user_quotas(users, qhflt=None):
289
    qh_quotas = get_users_quotas(users, flt=qhflt)
290
    return qh_quotas
291

    
292

    
293
def qh_sync_projects(projects, resource=None):
294
    p_quota, u_quota = astakos_project_quotas(projects, resource=resource)
295
    p_quota.update(u_quota)
296
    set_quota(p_quota, resource=resource)
297

    
298

    
299
def qh_sync_project(project):
300
    qh_sync_projects([project])
301

    
302

    
303
def membership_quota(membership):
304
    project = membership.project
305
    pr_ref = get_project_ref(project)
306
    u_ref = get_user_ref(membership.person)
307
    objs = ProjectResourceQuota.objects.select_related()
308
    grants = objs.filter(project=project)
309
    user_quota = QuotaDict()
310
    is_active = membership.is_active()
311
    for grant in grants:
312
        resource = grant.resource.name
313
        value = grant.member_capacity if is_active else 0
314
        user_quota[u_ref][pr_ref][resource] = value
315
    return user_quota
316

    
317

    
318
def qh_sync_membership(membership):
319
    quota = membership_quota(membership)
320
    set_quota(quota)
321

    
322

    
323
def pick_limit_scheme(project, resource):
324
    return resource.uplimit if project.is_base else resource.project_default
325

    
326

    
327
def qh_sync_new_resource(resource):
328
    projects = Project.objects.filter(state__in=Project.INITIALIZED_STATES).\
329
        select_for_update()
330

    
331
    entries = []
332
    for project in projects:
333
        limit = pick_limit_scheme(project, resource)
334
        entries.append(
335
            ProjectResourceQuota(
336
                project=project,
337
                resource=resource,
338
                project_capacity=limit,
339
                member_capacity=limit))
340
    ProjectResourceQuota.objects.bulk_create(entries)
341
    qh_sync_projects(projects, resource=resource.name)