Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / quotaholder / callpoint.py @ 3679f852

History | View | Annotate | Download (10 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.exception import (
36
    QuotaholderError,
37
    NoCommissionError,
38
    CorruptedError, InvalidDataError,
39
    NoHoldingError,
40
    DuplicateError)
41

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

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

    
49

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

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

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

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

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

    
68
    return quotas
69

    
70

    
71
def _get_holdings_for_update(holding_keys):
72
    holding_keys = sorted(holding_keys)
73
    holdings = {}
74
    for (holder, source, resource) in holding_keys:
75
        try:
76
            h = Holding.objects.get_for_update(
77
                holder=holder, source=source, resource=resource)
78
            holdings[(holder, source, resource)] = h
79
        except Holding.DoesNotExist:
80
            pass
81
    return holdings
82

    
83

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

    
92

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

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

    
109

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

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

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

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

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

    
124

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

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

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

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

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

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

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

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

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

    
177
    return commission.serial
178

    
179

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

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

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

    
199

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

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

    
209

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

    
219

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

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

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

    
244
    log_time = now()
245

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

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

    
255
        ps = provisions.get(serial)
256
        assert ps is not None
257
        for pv in ps:
258
            key = pv.holding_key()
259
            h = holdings.get(key)
260
            if h is None:
261
                raise CorruptedError("Corrupted provision")
262

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

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

    
277

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

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

    
290

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

    
296

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

    
304
    objs = Provision.objects.select_related('holding')
305
    provisions = objs.filter(serial=commission)
306

    
307
    ps = [p.todict() for p in provisions]
308

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