Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / quotaholder_app / callpoint.py @ ea1369dc

History | View | Annotate | Download (9.9 kB)

1
# Copyright 2012, 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 django.db.models import F
35
from astakos.quotaholder_app.exception import (
36
    QuotaholderError,
37
    NoCommissionError,
38
    CorruptedError, InvalidDataError,
39
    NoHoldingError,
40
    DuplicateError)
41

    
42
from astakos.quotaholder_app.commission import (
43
    Import, Release, Operations, finalize, undo)
44

    
45
from astakos.quotaholder_app.models import (
46
    Holding, Commission, Provision, ProvisionLog, now)
47

    
48

    
49
def get_quota(holders=None, sources=None, resources=None):
50
    holdings = Holding.objects.all()
51

    
52
    if holders is not None:
53
        holdings = holdings.filter(holder__in=holders)
54

    
55
    if sources is not None:
56
        holdings = holdings.filter(source__in=sources)
57

    
58
    if resources is not None:
59
        holdings = holdings.filter(resource__in=resources)
60

    
61
    quotas = {}
62
    for holding in holdings:
63
        key = (holding.holder, holding.source, holding.resource)
64
        value = (holding.limit, holding.usage_min, holding.usage_max)
65
        quotas[key] = value
66

    
67
    return quotas
68

    
69

    
70
def _get_holdings_for_update(holding_keys):
71
    holders = set(holder for (holder, source, resource) in holding_keys)
72
    objs = Holding.objects
73
    hs = objs.filter(holder__in=holders).order_by('pk').select_for_update()
74

    
75
    holdings = {}
76
    for h in hs:
77
        key = h.holder, h.source, h.resource
78
        holdings[key] = h
79

    
80
    return holdings
81

    
82

    
83
def _mkProvision(key, quantity):
84
    holder, source, resource = key
85
    return {'holder': holder,
86
            'source': source,
87
            'resource': resource,
88
            'quantity': quantity,
89
            }
90

    
91

    
92
def set_quota(quotas):
93
    holding_keys = [key for (key, limit) in quotas]
94
    holdings = _get_holdings_for_update(holding_keys)
95

    
96
    for key, limit in quotas:
97
        try:
98
            h = holdings[key]
99
        except KeyError:
100
            holder, source, resource = key
101
            h = Holding(holder=holder,
102
                        source=source,
103
                        resource=resource)
104
        h.limit = limit
105
        h.save()
106
        holdings[key] = h
107

    
108

    
109
def add_resource_limit(holders=None, sources=None, resources=None, diff=0):
110
    holdings = Holding.objects.all()
111

    
112
    if holders is not None:
113
        holdings = holdings.filter(holder__in=holders)
114

    
115
    if sources is not None:
116
        holdings = holdings.filter(source__in=sources)
117

    
118
    if resources is not None:
119
        holdings = holdings.filter(resource__in=resources)
120

    
121
    holdings.update(limit=F('limit')+diff)
122

    
123

    
124
def issue_commission(clientkey, provisions, name="", force=False):
125
    operations = Operations()
126
    provisions_to_create = []
127

    
128
    keys = [key for (key, value) in provisions]
129
    holdings = _get_holdings_for_update(keys)
130
    try:
131
        checked = []
132
        for key, quantity in provisions:
133
            if not isinstance(quantity, (int, long)):
134
                raise InvalidDataError("Malformed provision")
135

    
136
            if key in checked:
137
                m = "Duplicate provision for %s" % str(key)
138
                provision = _mkProvision(key, quantity)
139
                raise DuplicateError(m,
140
                                     provision=provision)
141
            checked.append(key)
142

    
143
            # Target
144
            try:
145
                th = holdings[key]
146
            except KeyError:
147
                m = ("There is no such holding %s" % str(key))
148
                provision = _mkProvision(key, quantity)
149
                raise NoHoldingError(m,
150
                                     provision=provision)
151

    
152
            if quantity >= 0:
153
                operations.prepare(Import, th, quantity, force)
154

    
155
            else:  # release
156
                abs_quantity = -quantity
157
                operations.prepare(Release, th, abs_quantity, False)
158

    
159
            holdings[key] = th
160
            provisions_to_create.append((key, quantity))
161

    
162
    except QuotaholderError:
163
        operations.revert()
164
        raise
165

    
166
    commission = Commission.objects.create(clientkey=clientkey,
167
                                           name=name,
168
                                           issue_time=now())
169
    for (holder, source, resource), quantity in provisions_to_create:
170
        Provision.objects.create(serial=commission,
171
                                 holder=holder,
172
                                 source=source,
173
                                 resource=resource,
174
                                 quantity=quantity)
175

    
176
    return commission.serial
177

    
178

    
179
def _log_provision(commission, provision, holding, log_time, reason):
180

    
181
    kwargs = {
182
        'serial':              commission.serial,
183
        'name':                commission.name,
184
        'holder':              holding.holder,
185
        'source':              holding.source,
186
        'resource':            holding.resource,
187
        'limit':               holding.limit,
188
        'usage_min':           holding.usage_min,
189
        'usage_max':           holding.usage_max,
190
        'delta_quantity':      provision.quantity,
191
        'issue_time':          commission.issue_time,
192
        'log_time':            log_time,
193
        'reason':              reason,
194
    }
195

    
196
    ProvisionLog.objects.create(**kwargs)
197

    
198

    
199
def _get_commissions_for_update(clientkey, serials):
200
    cs = Commission.objects.filter(
201
        clientkey=clientkey, serial__in=serials).select_for_update()
202

    
203
    commissions = {}
204
    for c in cs:
205
        commissions[c.serial] = c
206
    return commissions
207

    
208

    
209
def _partition_by(f, l):
210
    d = {}
211
    for x in l:
212
        group = f(x)
213
        group_l = d.get(group, [])
214
        group_l.append(x)
215
        d[group] = group_l
216
    return d
217

    
218

    
219
def resolve_pending_commissions(clientkey, accept_set=None, reject_set=None,
220
                                reason=''):
221
    if accept_set is None:
222
        accept_set = []
223
    if reject_set is None:
224
        reject_set = []
225

    
226
    actions = dict.fromkeys(accept_set, True)
227
    conflicting = set()
228
    for serial in reject_set:
229
        if actions.get(serial) is True:
230
            actions.pop(serial)
231
            conflicting.add(serial)
232
        else:
233
            actions[serial] = False
234

    
235
    conflicting = list(conflicting)
236
    serials = actions.keys()
237
    commissions = _get_commissions_for_update(clientkey, serials)
238
    ps = Provision.objects.filter(serial__in=serials).select_for_update()
239
    holding_keys = sorted(p.holding_key() for p in ps)
240
    holdings = _get_holdings_for_update(holding_keys)
241
    provisions = _partition_by(lambda p: p.serial_id, ps)
242

    
243
    log_time = now()
244

    
245
    accepted, rejected, notFound = [], [], []
246
    for serial, accept in actions.iteritems():
247
        commission = commissions.get(serial)
248
        if commission is None:
249
            notFound.append(serial)
250
            continue
251

    
252
        accepted.append(serial) if accept else rejected.append(serial)
253

    
254
        ps = provisions.get(serial, [])
255
        for pv in ps:
256
            key = pv.holding_key()
257
            h = holdings.get(key)
258
            if h is None:
259
                raise CorruptedError("Corrupted provision")
260

    
261
            quantity = pv.quantity
262
            action = finalize if accept else undo
263
            if quantity >= 0:
264
                action(Import, h, quantity)
265
            else:  # release
266
                action(Release, h, -quantity)
267

    
268
            prefix = 'ACCEPT:' if accept else 'REJECT:'
269
            comm_reason = prefix + reason[-121:]
270
            _log_provision(commission, pv, h, log_time, comm_reason)
271
            pv.delete()
272
        commission.delete()
273
    return accepted, rejected, notFound, conflicting
274

    
275

    
276
def resolve_pending_commission(clientkey, serial, accept=True):
277
    if accept:
278
        ok, notOk, notF, confl = resolve_pending_commissions(
279
            clientkey=clientkey, accept_set=[serial])
280
    else:
281
        notOk, ok, notF, confl = resolve_pending_commissions(
282
            clientkey=clientkey, reject_set=[serial])
283

    
284
    assert notOk == confl == []
285
    assert ok + notF == [serial]
286
    return bool(ok)
287

    
288

    
289
def get_pending_commissions(clientkey):
290
    pending = Commission.objects.filter(clientkey=clientkey)
291
    pending_list = pending.values_list('serial', flat=True)
292
    return list(pending_list)
293

    
294

    
295
def get_commission(clientkey, serial):
296
    try:
297
        commission = Commission.objects.get(clientkey=clientkey,
298
                                            serial=serial)
299
    except Commission.DoesNotExist:
300
        raise NoCommissionError(serial)
301

    
302
    objs = Provision.objects.select_related('holding')
303
    provisions = objs.filter(serial=commission)
304

    
305
    ps = [p.todict() for p in provisions]
306

    
307
    response = {'serial':     serial,
308
                'provisions': ps,
309
                'issue_time': commission.issue_time,
310
                'name':       commission.name,
311
                }
312
    return response