Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / quotaholder / callpoint.py @ 6c0f4562

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

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

    
44
from astakos.quotaholder.utils.newname import newname
45
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
46

    
47
from .models import (Holding,
48
                     Commission, Provision, ProvisionLog,
49
                     now)
50

    
51

    
52
class QuotaholderDjangoDBCallpoint(object):
53

    
54
    def get_holder_quota(self, holders=None, sources=None, resources=None):
55
        holdings = Holding.objects.filter(holder__in=holders)
56

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

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

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

    
69
        return quotas
70

    
71
    def _get_holdings_for_update(self, 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
    def _provisions_to_list(self, provisions):
84
        lst = []
85
        for provision in provisions:
86
            try:
87
                holder = provision['holder']
88
                source = provision['source']
89
                resource = provision['resource']
90
                quantity = provision['quantity']
91
                key = (holder, source, resource)
92
                lst.append((key, quantity))
93
            except KeyError:
94
                raise InvalidDataError("Malformed provision")
95
        return lst
96

    
97
    def _mkProvision(self, key, quantity):
98
        holder, source, resource = key
99
        return {'holder': holder,
100
                'source': source,
101
                'resource': resource,
102
                'quantity': quantity,
103
                }
104

    
105
    def set_holder_quota(self, quotas):
106
        q = self._level_quota_dict(quotas)
107
        self.set_quota(q)
108

    
109
    def _level_quota_dict(self, quotas):
110
        lst = []
111
        for holder, holder_quota in quotas.iteritems():
112
            for source, source_quota in holder_quota.iteritems():
113
                for resource, limit in source_quota.iteritems():
114
                    key = (holder, source, resource)
115
                    lst.append((key, limit))
116
        return lst
117

    
118
    def set_quota(self, quotas):
119
        holding_keys = [key for (key, limit) in quotas]
120
        holdings = self._get_holdings_for_update(holding_keys)
121

    
122
        for key, limit in quotas:
123
            try:
124
                h = holdings[key]
125
            except KeyError:
126
                holder, source, resource = key
127
                h = Holding(holder=holder,
128
                            source=source,
129
                            resource=resource)
130
            h.limit = limit
131
            h.save()
132
            holdings[key] = h
133

    
134
    def issue_commission(self,
135
                         context=None,
136
                         clientkey=None,
137
                         name=None,
138
                         force=False,
139
                         provisions=()):
140

    
141
        if name is None:
142
            name = ""
143
        create = Commission.objects.create
144
        commission = create(clientkey=clientkey, name=name)
145
        serial = commission.serial
146

    
147
        operations = Operations()
148

    
149
        provisions = self._provisions_to_list(provisions)
150
        keys = [key for (key, value) in provisions]
151
        holdings = self._get_holdings_for_update(keys)
152
        try:
153
            checked = []
154
            for key, quantity in provisions:
155
                if not isinstance(quantity, (int, long)):
156
                    raise InvalidDataError("Malformed provision")
157

    
158
                if key in checked:
159
                    m = "Duplicate provision for %s" % str(key)
160
                    provision = self._mkProvision(key, quantity)
161
                    raise DuplicateError(m,
162
                                         provision=provision)
163
                checked.append(key)
164

    
165
                # Target
166
                try:
167
                    th = holdings[key]
168
                except KeyError:
169
                    m = ("There is no such holding %s" % key)
170
                    provision = self._mkProvision(key, quantity)
171
                    raise NoHoldingError(m,
172
                                         provision=provision)
173

    
174
                if quantity >= 0:
175
                    operations.prepare(Import, th, quantity, force)
176

    
177
                else: # release
178
                    abs_quantity = -quantity
179
                    operations.prepare(Release, th, abs_quantity, force)
180

    
181
                holdings[key] = th
182
                Provision.objects.create(serial=commission,
183
                                         holder=th.holder,
184
                                         source=th.source,
185
                                         resource=th.resource,
186
                                         quantity=quantity)
187

    
188
        except QuotaholderError:
189
            operations.revert()
190
            raise
191

    
192
        return serial
193

    
194
    def _log_provision(self,
195
                       commission, provision, holding, log_time, reason):
196

    
197
        kwargs = {
198
            'serial':              commission.serial,
199
            'name':                commission.name,
200
            'holder':              holding.holder,
201
            'source':              holding.source,
202
            'resource':            holding.resource,
203
            'limit':               holding.limit,
204
            'imported_min':        holding.imported_min,
205
            'imported_max':        holding.imported_max,
206
            'delta_quantity':      provision.quantity,
207
            'issue_time':          commission.issue_time,
208
            'log_time':            log_time,
209
            'reason':              reason,
210
        }
211

    
212
        ProvisionLog.objects.create(**kwargs)
213

    
214
    def _get_commissions_for_update(self, clientkey, serials):
215
        cs = Commission.objects.filter(
216
            clientkey=clientkey, serial__in=serials).select_for_update()
217

    
218
        commissions = {}
219
        for c in cs:
220
            commissions[c.serial] = c
221
        return commissions
222

    
223
    def _partition_by(self, f, l):
224
        d = {}
225
        for x in l:
226
            group = f(x)
227
            group_l = d.get(group, [])
228
            group_l.append(x)
229
            d[group] = group_l
230
        return d
231

    
232
    def resolve_pending_commissions(self,
233
                                    context=None, clientkey=None,
234
                                    accept_set=[], reject_set=[],
235
                                    reason=''):
236
        actions = dict.fromkeys(accept_set, True)
237
        conflicting = set()
238
        for serial in reject_set:
239
            if actions.get(serial) is True:
240
                actions.pop(serial)
241
                conflicting.add(serial)
242
            else:
243
                actions[serial] = False
244

    
245
        conflicting = list(conflicting)
246
        serials = actions.keys()
247
        commissions = self._get_commissions_for_update(clientkey, serials)
248
        ps = Provision.objects.filter(serial__in=serials).select_for_update()
249
        holding_keys = sorted(p.holding_key() for p in ps)
250
        holdings = self._get_holdings_for_update(holding_keys)
251
        provisions = self._partition_by(lambda p: p.serial_id, ps)
252

    
253
        log_time = now()
254

    
255
        accepted, rejected, notFound = [], [], []
256
        for serial, accept in actions.iteritems():
257
            commission = commissions.get(serial)
258
            if commission is None:
259
                notFound.append(serial)
260
                continue
261

    
262
            accepted.append(serial) if accept else rejected.append(serial)
263

    
264
            ps = provisions.get(serial)
265
            assert ps is not None
266
            for pv in ps:
267
                key = pv.holding_key()
268
                h = holdings.get(key)
269
                if h is None:
270
                    raise CorruptedError("Corrupted provision")
271

    
272
                quantity = pv.quantity
273
                action = finalize if accept else undo
274
                if quantity >= 0:
275
                    action(Import, h, quantity)
276
                else:  # release
277
                    action(Release, h, -quantity)
278

    
279
                prefix = 'ACCEPT:' if accept else 'REJECT:'
280
                comm_reason = prefix + reason[-121:]
281
                self._log_provision(commission, pv, h, log_time, comm_reason)
282
                pv.delete()
283
            commission.delete()
284
        return accepted, rejected, notFound, conflicting
285

    
286
    def resolve_pending_commission(self, clientkey, serial, accept=True):
287
        if accept:
288
            ok, notOk, notF, confl = self.resolve_pending_commissions(
289
                clientkey=clientkey, accept_set=[serial])
290
        else:
291
            notOk, ok, notF, confl = self.resolve_pending_commissions(
292
                clientkey=clientkey, reject_set=[serial])
293

    
294
        assert notOk == confl == []
295
        assert ok + notF == [serial]
296
        return bool(ok)
297

    
298
    def get_pending_commissions(self, context=None, clientkey=None):
299
        pending = Commission.objects.filter(clientkey=clientkey)
300
        pending_list = pending.values_list('serial', flat=True)
301
        return list(pending_list)
302

    
303
    def get_commission(self, clientkey=None, serial=None):
304
        try:
305
            commission = Commission.objects.get(clientkey=clientkey,
306
                                                serial=serial)
307
        except Commission.DoesNotExist:
308
            raise NoCommissionError(serial)
309

    
310
        objs = Provision.objects.select_related('holding')
311
        provisions = objs.filter(serial=commission)
312

    
313
        ps = [p.todict() for p in provisions]
314

    
315
        response = {'serial':     serial,
316
                    'provisions': ps,
317
                    'issue_time': commission.issue_time,
318
                    }
319
        return response
320

    
321

    
322
API_Callpoint = QuotaholderDjangoDBCallpoint