Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / quotaholder / callpoint.py @ 61660c26

History | View | Annotate | Download (11.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 functools import partial
35

    
36
from astakos.quotaholder.exception import (
37
    QuotaholderError,
38
    NoCommissionError,
39
    CorruptedError, InvalidDataError,
40
    NoHoldingError,
41
    DuplicateError)
42

    
43
from astakos.quotaholder.commission import (
44
    Import, Release, Operations)
45

    
46
from astakos.quotaholder.utils.newname import newname
47
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
48

    
49
from .models import (Holding,
50
                     Commission, Provision, ProvisionLog,
51
                     now,
52
                     db_get_holding,
53
                     db_get_commission, db_filter_provision)
54

    
55

    
56
class QuotaholderDjangoDBCallpoint(object):
57

    
58
    def get_holder_quota(self, holders=None, sources=None, resources=None):
59
        holdings = Holding.objects.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 issue_commission(self,
139
                         context=None,
140
                         clientkey=None,
141
                         name=None,
142
                         force=False,
143
                         provisions=()):
144

    
145
        if name is None:
146
            name = ""
147
        create = Commission.objects.create
148
        commission = create(clientkey=clientkey, name=name)
149
        serial = commission.serial
150

    
151
        operations = Operations()
152

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

    
162
                if key in checked:
163
                    m = "Duplicate provision for %s" % str(key)
164
                    provision = self._mkProvision(key, quantity)
165
                    raise DuplicateError(m,
166
                                         provision=provision)
167
                checked.append(key)
168

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

    
178
                if quantity >= 0:
179
                    operations.prepare(Import, th, quantity, force)
180

    
181
                else: # release
182
                    abs_quantity = -quantity
183
                    operations.prepare(Release, th, abs_quantity, force)
184

    
185
                holdings[key] = th
186
                Provision.objects.create(serial=commission,
187
                                         holding=th,
188
                                         quantity=quantity)
189

    
190
        except QuotaholderError:
191
            operations.revert()
192
            raise
193

    
194
        return serial
195

    
196
    def _log_provision(self,
197
                       commission, provision, log_time, reason):
198

    
199
        holding = provision.holding
200

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

    
216
        ProvisionLog.objects.create(**kwargs)
217

    
218
    def accept_commission(self,
219
                          context=None, clientkey=None,
220
                          serial=None, reason=''):
221
        log_time = now()
222

    
223
        try:
224
            c = db_get_commission(clientkey=clientkey, serial=serial,
225
                                  for_update=True)
226
        except Commission.DoesNotExist:
227
            return False
228

    
229
        operations = Operations()
230

    
231
        provisions = db_filter_provision(serial=serial, for_update=True)
232
        for pv in provisions:
233
            try:
234
                th = db_get_holding(id=pv.holding_id,
235
                                    for_update=True)
236
            except Holding.DoesNotExist:
237
                m = "Corrupted provision"
238
                raise CorruptedError(m)
239

    
240
            quantity = pv.quantity
241

    
242
            if quantity >= 0:
243
                operations.finalize(Import, th, quantity)
244
            else: # release
245
                abs_quantity = -quantity
246
                operations.finalize(Release, th, abs_quantity)
247

    
248
            reason = 'ACCEPT:' + reason[-121:]
249
            self._log_provision(c, pv, log_time, reason)
250
            pv.delete()
251
        c.delete()
252
        return True
253

    
254
    def reject_commission(self,
255
                          context=None, clientkey=None,
256
                          serial=None, reason=''):
257
        log_time = now()
258

    
259
        try:
260
            c = db_get_commission(clientkey=clientkey, serial=serial,
261
                                  for_update=True)
262
        except Commission.DoesNotExist:
263
            return False
264

    
265
        operations = Operations()
266

    
267
        provisions = db_filter_provision(serial=serial, for_update=True)
268
        for pv in provisions:
269
            try:
270
                th = db_get_holding(id=pv.holding_id,
271
                                    for_update=True)
272
            except Holding.DoesNotExist:
273
                m = "Corrupted provision"
274
                raise CorruptedError(m)
275

    
276
            quantity = pv.quantity
277

    
278
            if quantity >= 0:
279
                operations.undo(Import, th, quantity)
280
            else: # release
281
                abs_quantity = -quantity
282
                operations.undo(Release, th, abs_quantity)
283

    
284
            reason = 'REJECT:' + reason[-121:]
285
            self._log_provision(c, pv, log_time, reason)
286
            pv.delete()
287
        c.delete()
288
        return True
289

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

    
295
    def get_commission(self, clientkey=None, serial=None):
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
                    }
311
        return response
312

    
313
    def _resolve(self, include, exclude, operation):
314
        done = []
315
        failed = []
316
        for serial in include:
317
            if serial in exclude:
318
                failed.append((serial, 'CONFLICT'))
319
            else:
320
                response = operation(serial=serial)
321
                if response:
322
                    done.append(serial)
323
                else:
324
                    failed.append((serial, 'NOTFOUND'))
325
        return done, failed
326

    
327
    def resolve_pending_commissions(self,
328
                                    context=None, clientkey=None,
329
                                    accept_set=[], reject_set=[]):
330
        accept_set = set(accept_set)
331
        reject_set = set(reject_set)
332

    
333
        accept = partial(self.accept_commission, clientkey=clientkey)
334
        reject = partial(self.reject_commission, clientkey=clientkey)
335

    
336
        accepted, failed_ac = self._resolve(accept_set, reject_set, accept)
337
        rejected, failed_re = self._resolve(reject_set, accept_set, reject)
338

    
339
        failed = list(set(failed_ac + failed_re))
340
        return accepted, rejected, failed
341

    
342

    
343
API_Callpoint = QuotaholderDjangoDBCallpoint