Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / quotaholder_app / callpoint.py @ 5718706f

History | View | Annotate | Download (10.1 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 datetime import datetime
35
from django.db.models import F
36
from astakos.quotaholder_app.exception import (
37
    QuotaholderError,
38
    NoCommissionError,
39
    CorruptedError, InvalidDataError,
40
    NoHoldingError,
41
    DuplicateError)
42

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

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

    
49

    
50
def format_datetime(d):
51
    return d.strftime('%Y-%m-%dT%H:%M:%S.%f')[:24]
52

    
53

    
54
def get_quota(holders=None, sources=None, resources=None):
55
    holdings = Holding.objects.all()
56

    
57
    if holders is not None:
58
        holdings = holdings.filter(holder__in=holders)
59

    
60
    if sources is not None:
61
        holdings = holdings.filter(source__in=sources)
62

    
63
    if resources is not None:
64
        holdings = holdings.filter(resource__in=resources)
65

    
66
    quotas = {}
67
    for holding in holdings:
68
        key = (holding.holder, holding.source, holding.resource)
69
        value = (holding.limit, holding.usage_min, holding.usage_max)
70
        quotas[key] = value
71

    
72
    return quotas
73

    
74

    
75
def _get_holdings_for_update(holding_keys):
76
    holders = set(holder for (holder, source, resource) in holding_keys)
77
    objs = Holding.objects
78
    hs = objs.filter(holder__in=holders).order_by('pk').select_for_update()
79

    
80
    holdings = {}
81
    for h in hs:
82
        key = h.holder, h.source, h.resource
83
        holdings[key] = h
84

    
85
    return holdings
86

    
87

    
88
def _mkProvision(key, quantity):
89
    holder, source, resource = key
90
    return {'holder': holder,
91
            'source': source,
92
            'resource': resource,
93
            'quantity': quantity,
94
            }
95

    
96

    
97
def set_quota(quotas):
98
    holding_keys = [key for (key, limit) in quotas]
99
    holdings = _get_holdings_for_update(holding_keys)
100

    
101
    for key, limit in quotas:
102
        try:
103
            h = holdings[key]
104
        except KeyError:
105
            holder, source, resource = key
106
            h = Holding(holder=holder,
107
                        source=source,
108
                        resource=resource)
109
        h.limit = limit
110
        h.save()
111
        holdings[key] = h
112

    
113

    
114
def add_resource_limit(holders=None, sources=None, resources=None, diff=0):
115
    holdings = Holding.objects.all()
116

    
117
    if holders is not None:
118
        holdings = holdings.filter(holder__in=holders)
119

    
120
    if sources is not None:
121
        holdings = holdings.filter(source__in=sources)
122

    
123
    if resources is not None:
124
        holdings = holdings.filter(resource__in=resources)
125

    
126
    holdings.update(limit=F('limit')+diff)
127

    
128

    
129
def issue_commission(clientkey, provisions, name="", force=False):
130
    operations = Operations()
131
    provisions_to_create = []
132

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

    
141
            if key in checked:
142
                m = "Duplicate provision for %s" % str(key)
143
                provision = _mkProvision(key, quantity)
144
                raise DuplicateError(m,
145
                                     provision=provision)
146
            checked.append(key)
147

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

    
157
            if quantity >= 0:
158
                operations.prepare(Import, th, quantity, force)
159

    
160
            else:  # release
161
                abs_quantity = -quantity
162
                operations.prepare(Release, th, abs_quantity, False)
163

    
164
            holdings[key] = th
165
            provisions_to_create.append((key, quantity))
166

    
167
    except QuotaholderError:
168
        operations.revert()
169
        raise
170

    
171
    commission = Commission.objects.create(clientkey=clientkey,
172
                                           name=name,
173
                                           issue_datetime=datetime.now())
174
    for (holder, source, resource), quantity in provisions_to_create:
175
        Provision.objects.create(serial=commission,
176
                                 holder=holder,
177
                                 source=source,
178
                                 resource=resource,
179
                                 quantity=quantity)
180

    
181
    return commission.serial
182

    
183

    
184
def _log_provision(commission, provision, holding, log_datetime, reason):
185

    
186
    kwargs = {
187
        'serial':              commission.serial,
188
        'name':                commission.name,
189
        'holder':              holding.holder,
190
        'source':              holding.source,
191
        'resource':            holding.resource,
192
        'limit':               holding.limit,
193
        'usage_min':           holding.usage_min,
194
        'usage_max':           holding.usage_max,
195
        'delta_quantity':      provision.quantity,
196
        'issue_time':          format_datetime(commission.issue_datetime),
197
        'log_time':            format_datetime(log_datetime),
198
        'reason':              reason,
199
    }
200

    
201
    ProvisionLog.objects.create(**kwargs)
202

    
203

    
204
def _get_commissions_for_update(clientkey, serials):
205
    cs = Commission.objects.filter(
206
        clientkey=clientkey, serial__in=serials).select_for_update()
207

    
208
    commissions = {}
209
    for c in cs:
210
        commissions[c.serial] = c
211
    return commissions
212

    
213

    
214
def _partition_by(f, l):
215
    d = {}
216
    for x in l:
217
        group = f(x)
218
        group_l = d.get(group, [])
219
        group_l.append(x)
220
        d[group] = group_l
221
    return d
222

    
223

    
224
def resolve_pending_commissions(clientkey, accept_set=None, reject_set=None,
225
                                reason=''):
226
    if accept_set is None:
227
        accept_set = []
228
    if reject_set is None:
229
        reject_set = []
230

    
231
    actions = dict.fromkeys(accept_set, True)
232
    conflicting = set()
233
    for serial in reject_set:
234
        if actions.get(serial) is True:
235
            actions.pop(serial)
236
            conflicting.add(serial)
237
        else:
238
            actions[serial] = False
239

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

    
248
    log_datetime = datetime.now()
249

    
250
    accepted, rejected, notFound = [], [], []
251
    for serial, accept in actions.iteritems():
252
        commission = commissions.get(serial)
253
        if commission is None:
254
            notFound.append(serial)
255
            continue
256

    
257
        accepted.append(serial) if accept else rejected.append(serial)
258

    
259
        ps = provisions.get(serial, [])
260
        for pv in ps:
261
            key = pv.holding_key()
262
            h = holdings.get(key)
263
            if h is None:
264
                raise CorruptedError("Corrupted provision")
265

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

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

    
280

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

    
289
    assert notOk == confl == []
290
    assert ok + notF == [serial]
291
    return bool(ok)
292

    
293

    
294
def get_pending_commissions(clientkey):
295
    pending = Commission.objects.filter(clientkey=clientkey)
296
    pending_list = pending.values_list('serial', flat=True)
297
    return list(pending_list)
298

    
299

    
300
def get_commission(clientkey, serial):
301
    try:
302
        commission = Commission.objects.get(clientkey=clientkey,
303
                                            serial=serial)
304
    except Commission.DoesNotExist:
305
        raise NoCommissionError(serial)
306

    
307
    objs = Provision.objects
308
    provisions = objs.filter(serial=commission)
309

    
310
    ps = [p.todict() for p in provisions]
311

    
312
    response = {'serial':     serial,
313
                'provisions': ps,
314
                'issue_time': commission.issue_datetime,
315
                'name':       commission.name,
316
                }
317
    return response