Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (12.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
from astakos.im.models import (
35
    Resource, AstakosUserQuota, 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 synnefo.util.keypath import set_path
41

    
42

    
43
PROJECT_TAG = "project:"
44
USER_TAG = "user:"
45

    
46
def project_ref(value):
47
    return  PROJECT_TAG + value
48

    
49

    
50
def get_project_ref(project):
51
    return project_ref(project.uuid)
52

    
53

    
54
def user_ref(value):
55
    return USER_TAG + value
56

    
57

    
58
def get_user_ref(user):
59
    return user_ref(user.uuid)
60

    
61

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

    
71

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

    
79

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

    
86

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

    
98

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

    
105

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

    
116

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

    
123

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

    
128

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

    
133

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

    
145

    
146
def mk_limits_dict(counters):
147
    quota = {}
148
    for key, (limit, _, _) in counters.iteritems():
149
        path = list(key)
150
        set_path(quota, path, limit, createpath=True)
151
    return quota
152

    
153

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

    
162

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

    
167

    
168
def get_project_quota(project, resources=None, sources=None):
169
    quotas = get_projects_quota([project], resources, sources)
170
    return quotas.get(project.uuid, {})
171

    
172

    
173
def get_projects_quota_limits():
174
    project_counters = qh.get_quota(flt=Q(holder__startswith=PROJECT_TAG))
175
    user_counters = qh.get_quota(flt=Q(holder__startswith=USER_TAG))
176
    return mk_limits_dict(project_counters), mk_limits_dict(user_counters)
177

    
178

    
179
def _level_quota_dict(quotas):
180
    lst = []
181
    for holder, holder_quota in quotas.iteritems():
182
        for source, source_quota in holder_quota.iteritems():
183
            for resource, limit in source_quota.iteritems():
184
                key = (holder, source, resource)
185
                lst.append((key, limit))
186
    return lst
187

    
188

    
189
def set_quota(quotas, resource=None):
190
    q = _level_quota_dict(quotas)
191
    qh.set_quota(q, resource=resource)
192

    
193

    
194
PENDING_APP_RESOURCE = 'astakos.pending_app'
195

    
196

    
197
def mk_user_provision(user, source, resource, quantity):
198
    holder = user_ref(user)
199
    source = project_ref(source)
200
    return (holder, source, resource), quantity
201

    
202

    
203
def mk_project_provision(project, resource, quantity):
204
    holder = project_ref(project)
205
    return (holder, None, resource), quantity
206

    
207

    
208
def _mk_provisions(holder, source, resource, quantity):
209
    return [((holder, source, resource), quantity),
210
            ((source, None, resource), quantity)]
211

    
212

    
213
def register_pending_apps(user, project, quantity, force=False):
214
    provisions = _mk_provisions(get_user_ref(user), get_project_ref(project),
215
                                PENDING_APP_RESOURCE, quantity)
216
    try:
217
        s = qh.issue_commission(clientkey='astakos',
218
                                force=force,
219
                                provisions=provisions)
220
    except NoCapacityError as e:
221
        limit = e.data['limit']
222
        return False, limit
223
    qh.resolve_pending_commission('astakos', s)
224
    return True, None
225

    
226

    
227
def get_pending_app_quota(user):
228
    quota = get_user_quotas(user)
229
    source = user.base_project.uuid
230
    return quota[source][PENDING_APP_RESOURCE]
231

    
232

    
233
def update_base_quota(users, resource, value):
234
    userids = [user.pk for user in users]
235
    AstakosUserQuota.objects.\
236
        filter(resource__name=resource, user__pk__in=userids).\
237
        update(capacity=value)
238
    qh_sync_locked_users(users, resource=resource)
239

    
240

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

    
250

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

    
257
    objs = ProjectMembership.objects
258
    memberships = objs.initialized(projects).select_related(
259
        "person", "project")
260
    memberships_d = _partition_by(lambda m: m.project_id, memberships)
261

    
262
    user_quota = {}
263
    project_quota = {}
264

    
265
    for project in projects:
266
        pr_ref = get_project_ref(project)
267
        state = project.state
268
        if state not in Project.INITIALIZED_STATES:
269
            continue
270

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

    
284
    return project_quota, user_quota
285

    
286

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

    
291

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

    
297

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

    
301

    
302
def membership_quota(membership):
303
    project = membership.project
304
    pr_ref = get_project_ref(project)
305
    u_ref = get_user_ref(membership.person)
306
    objs = ProjectResourceQuota.objects.select_related()
307
    grants = objs.filter(project=project)
308
    user_quota = {}
309
    is_active = membership.is_active()
310
    for grant in grants:
311
        resource = grant.resource.name
312
        path = [u_ref, pr_ref, resource]
313
        value = grant.member_capacity if is_active else 0
314
        set_path(user_quota, path, value, createpath=True)
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 compute_diff_quota(local, registered):
324
    diff = {}
325
    for holder, h_quota in local.iteritems():
326
        for source, value in h_quota.iteritems():
327
            reg_h_quota = registered.get(holder, {})
328
            reg_value = reg_h_quota.get(source, {})
329
            if value != reg_value:
330
                set_path(diff, [holder, source], value, createpath=True)
331
    return diff
332

    
333

    
334
def qh_sync_projects_diffs(projects, sync=True):
335
    local_project, local_user = astakos_project_quotas(projects)
336
    registered_project, registered_user = get_projects_quota_limits(projects)
337
    diff_quotas = compute_diff_quota(local_project, registered_project)
338
    diff_quotas.update(compute_diff_quota(local_user, registered_user))
339

    
340
    if sync:
341
        set_quota(diff_quotas)
342

    
343
    all_registered = registered_project
344
    all_registered.update(registered_user)
345
    return all_registered, diff_quotas
346

    
347

    
348
def pick_limit_scheme(project, resource):
349
    return resource.uplimit if project.is_base else resource.project_default
350

    
351

    
352
def qh_sync_new_resource(resource):
353
    projects = Project.objects.filter(state__in=Project.INITIALIZED_STATES).\
354
        select_for_update()
355

    
356
    entries = []
357
    for project in projects:
358
        limit = pick_limit_scheme(project, resource)
359
        entries.append(
360
            ProjectResourceQuota(
361
                project=project,
362
                resource=resource,
363
                project_capacity=limit,
364
                member_capacity=limit))
365

    
366
    ProjectResourceQuota.objects.bulk_create(entries)
367
    qh_sync_projects(projects, resource=resource.name)