Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / quotaholder / callpoint.py @ ae16bcad

History | View | Annotate | Download (12.3 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 astakos.quotaholder.utils.newname import newname
46
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
47

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

    
52

    
53
class QuotaholderDjangoDBCallpoint(object):
54

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

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

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

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

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

    
73
        return quotas
74

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

    
87
    def _provisions_to_list(self, provisions):
88
        lst = []
89
        for provision in provisions:
90
            try:
91
                holder = provision['holder']
92
                source = provision['source']
93
                resource = provision['resource']
94
                quantity = provision['quantity']
95
                key = (holder, source, resource)
96
                lst.append((key, quantity))
97
            except KeyError:
98
                raise InvalidDataError("Malformed provision")
99
        return lst
100

    
101
    def _mkProvision(self, key, quantity):
102
        holder, source, resource = key
103
        return {'holder': holder,
104
                'source': source,
105
                'resource': resource,
106
                'quantity': quantity,
107
                }
108

    
109
    def set_holder_quota(self, quotas):
110
        q = self._level_quota_dict(quotas)
111
        self.set_quota(q)
112

    
113
    def _level_quota_dict(self, quotas):
114
        lst = []
115
        for holder, holder_quota in quotas.iteritems():
116
            for source, source_quota in holder_quota.iteritems():
117
                for resource, limit in source_quota.iteritems():
118
                    key = (holder, source, resource)
119
                    lst.append((key, limit))
120
        return lst
121

    
122
    def set_quota(self, quotas):
123
        holding_keys = [key for (key, limit) in quotas]
124
        holdings = self._get_holdings_for_update(holding_keys)
125

    
126
        for key, limit in quotas:
127
            try:
128
                h = holdings[key]
129
            except KeyError:
130
                holder, source, resource = key
131
                h = Holding(holder=holder,
132
                            source=source,
133
                            resource=resource)
134
            h.limit = limit
135
            h.save()
136
            holdings[key] = h
137

    
138
    def add_resource_limit(self, holders=None, sources=None, resources=None,
139
                           diff=0):
140
        holdings = Holding.objects.all()
141

    
142
        if holders is not None:
143
            holdings = holdings.filter(holder__in=holders)
144

    
145
        if sources is not None:
146
            holdings = holdings.filter(source__in=sources)
147

    
148
        if resources is not None:
149
            holdings = holdings.filter(resource__in=resources)
150

    
151
        holdings.update(limit=F('limit')+diff)
152

    
153
    def issue_commission(self,
154
                         context=None,
155
                         clientkey=None,
156
                         name=None,
157
                         force=False,
158
                         provisions=()):
159

    
160
        if name is None:
161
            name = ""
162

    
163
        operations = Operations()
164
        provisions_to_create = []
165

    
166
        provisions = self._provisions_to_list(provisions)
167
        keys = [key for (key, value) in provisions]
168
        holdings = self._get_holdings_for_update(keys)
169
        try:
170
            checked = []
171
            for key, quantity in provisions:
172
                if not isinstance(quantity, (int, long)):
173
                    raise InvalidDataError("Malformed provision")
174

    
175
                if key in checked:
176
                    m = "Duplicate provision for %s" % str(key)
177
                    provision = self._mkProvision(key, quantity)
178
                    raise DuplicateError(m,
179
                                         provision=provision)
180
                checked.append(key)
181

    
182
                # Target
183
                try:
184
                    th = holdings[key]
185
                except KeyError:
186
                    m = ("There is no such holding %s" % str(key))
187
                    provision = self._mkProvision(key, quantity)
188
                    raise NoHoldingError(m,
189
                                         provision=provision)
190

    
191
                if quantity >= 0:
192
                    operations.prepare(Import, th, quantity, force)
193

    
194
                else: # release
195
                    abs_quantity = -quantity
196
                    operations.prepare(Release, th, abs_quantity, force)
197

    
198
                holdings[key] = th
199
                provisions_to_create.append((key, quantity))
200

    
201
        except QuotaholderError:
202
            operations.revert()
203
            raise
204

    
205
        commission = Commission.objects.create(clientkey=clientkey, name=name)
206
        for (holder, source, resource), quantity in provisions_to_create:
207
            Provision.objects.create(serial=commission,
208
                                     holder=holder,
209
                                     source=source,
210
                                     resource=resource,
211
                                     quantity=quantity)
212

    
213
        return commission.serial
214

    
215
    def _log_provision(self,
216
                       commission, provision, holding, log_time, reason):
217

    
218
        kwargs = {
219
            'serial':              commission.serial,
220
            'name':                commission.name,
221
            'holder':              holding.holder,
222
            'source':              holding.source,
223
            'resource':            holding.resource,
224
            'limit':               holding.limit,
225
            'imported_min':        holding.imported_min,
226
            'imported_max':        holding.imported_max,
227
            'delta_quantity':      provision.quantity,
228
            'issue_time':          commission.issue_time,
229
            'log_time':            log_time,
230
            'reason':              reason,
231
        }
232

    
233
        ProvisionLog.objects.create(**kwargs)
234

    
235
    def _get_commissions_for_update(self, clientkey, serials):
236
        cs = Commission.objects.filter(
237
            clientkey=clientkey, serial__in=serials).select_for_update()
238

    
239
        commissions = {}
240
        for c in cs:
241
            commissions[c.serial] = c
242
        return commissions
243

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

    
253
    def resolve_pending_commissions(self,
254
                                    context=None, clientkey=None,
255
                                    accept_set=[], reject_set=[],
256
                                    reason=''):
257
        actions = dict.fromkeys(accept_set, True)
258
        conflicting = set()
259
        for serial in reject_set:
260
            if actions.get(serial) is True:
261
                actions.pop(serial)
262
                conflicting.add(serial)
263
            else:
264
                actions[serial] = False
265

    
266
        conflicting = list(conflicting)
267
        serials = actions.keys()
268
        commissions = self._get_commissions_for_update(clientkey, serials)
269
        ps = Provision.objects.filter(serial__in=serials).select_for_update()
270
        holding_keys = sorted(p.holding_key() for p in ps)
271
        holdings = self._get_holdings_for_update(holding_keys)
272
        provisions = self._partition_by(lambda p: p.serial_id, ps)
273

    
274
        log_time = now()
275

    
276
        accepted, rejected, notFound = [], [], []
277
        for serial, accept in actions.iteritems():
278
            commission = commissions.get(serial)
279
            if commission is None:
280
                notFound.append(serial)
281
                continue
282

    
283
            accepted.append(serial) if accept else rejected.append(serial)
284

    
285
            ps = provisions.get(serial)
286
            assert ps is not None
287
            for pv in ps:
288
                key = pv.holding_key()
289
                h = holdings.get(key)
290
                if h is None:
291
                    raise CorruptedError("Corrupted provision")
292

    
293
                quantity = pv.quantity
294
                action = finalize if accept else undo
295
                if quantity >= 0:
296
                    action(Import, h, quantity)
297
                else:  # release
298
                    action(Release, h, -quantity)
299

    
300
                prefix = 'ACCEPT:' if accept else 'REJECT:'
301
                comm_reason = prefix + reason[-121:]
302
                self._log_provision(commission, pv, h, log_time, comm_reason)
303
                pv.delete()
304
            commission.delete()
305
        return accepted, rejected, notFound, conflicting
306

    
307
    def resolve_pending_commission(self, clientkey, serial, accept=True):
308
        if accept:
309
            ok, notOk, notF, confl = self.resolve_pending_commissions(
310
                clientkey=clientkey, accept_set=[serial])
311
        else:
312
            notOk, ok, notF, confl = self.resolve_pending_commissions(
313
                clientkey=clientkey, reject_set=[serial])
314

    
315
        assert notOk == confl == []
316
        assert ok + notF == [serial]
317
        return bool(ok)
318

    
319
    def get_pending_commissions(self, context=None, clientkey=None):
320
        pending = Commission.objects.filter(clientkey=clientkey)
321
        pending_list = pending.values_list('serial', flat=True)
322
        return list(pending_list)
323

    
324
    def get_commission(self, clientkey=None, serial=None):
325
        try:
326
            commission = Commission.objects.get(clientkey=clientkey,
327
                                                serial=serial)
328
        except Commission.DoesNotExist:
329
            raise NoCommissionError(serial)
330

    
331
        objs = Provision.objects.select_related('holding')
332
        provisions = objs.filter(serial=commission)
333

    
334
        ps = [p.todict() for p in provisions]
335

    
336
        response = {'serial':     serial,
337
                    'provisions': ps,
338
                    'issue_time': commission.issue_time,
339
                    }
340
        return response
341

    
342

    
343
API_Callpoint = QuotaholderDjangoDBCallpoint