Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.5 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, 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 service_get_project_quotas(component, projects=None):
169
    name_values = Service.objects.filter(
170
        component=component).values_list('name')
171
    service_names = [t for (t,) in name_values]
172
    resources = Resource.objects.filter(service_origin__in=service_names)
173
    resource_names = [r.name for r in resources]
174
    ps = Project.objects.initialized()
175
    if projects is not None:
176
        ps = ps.filter(uuid__in=projects)
177
    return get_projects_quota(ps, resources=resource_names)
178

    
179

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

    
184

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

    
190

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

    
200

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

    
205

    
206
PENDING_APP_RESOURCE = 'astakos.pending_app'
207

    
208

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

    
214

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

    
219

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

    
224

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

    
238

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

    
244

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

    
254

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

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

    
266
    user_quota = {}
267
    project_quota = {}
268

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

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

    
288
    return project_quota, user_quota
289

    
290

    
291
def list_user_quotas(users, qhflt=None):
292
    qh_quotas = get_users_quotas(users, flt=qhflt)
293
    return qh_quotas
294

    
295

    
296
def qh_sync_projects(projects, resource=None):
297
    p_quota, u_quota = astakos_project_quotas(projects, resource=resource)
298
    p_quota.update(u_quota)
299
    set_quota(p_quota, resource=resource)
300

    
301

    
302
def qh_sync_project(project):
303
    qh_sync_projects([project])
304

    
305

    
306
def membership_quota(membership):
307
    project = membership.project
308
    pr_ref = get_project_ref(project)
309
    u_ref = get_user_ref(membership.person)
310
    objs = ProjectResourceQuota.objects.select_related()
311
    grants = objs.filter(project=project)
312
    user_quota = {}
313
    is_active = membership.is_active()
314
    for grant in grants:
315
        resource = grant.resource.name
316
        path = [u_ref, pr_ref, resource]
317
        value = grant.member_capacity if is_active else 0
318
        set_path(user_quota, path, value, createpath=True)
319
    return user_quota
320

    
321

    
322
def qh_sync_membership(membership):
323
    quota = membership_quota(membership)
324
    set_quota(quota)
325

    
326

    
327
def pick_limit_scheme(project, resource):
328
    return resource.uplimit if project.is_base else resource.project_default
329

    
330

    
331
def qh_sync_new_resource(resource):
332
    projects = Project.objects.filter(state__in=Project.INITIALIZED_STATES).\
333
        select_for_update()
334

    
335
    entries = []
336
    for project in projects:
337
        limit = pick_limit_scheme(project, resource)
338
        entries.append(
339
            ProjectResourceQuota(
340
                project=project,
341
                resource=resource,
342
                project_capacity=limit,
343
                member_capacity=limit))
344

    
345
    ProjectResourceQuota.objects.bulk_create(entries)
346
    qh_sync_projects(projects, resource=resource.name)