Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11 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 set_holder_quota(self, quotas):
76
        holders = quotas.keys()
77
        hs = Holding.objects.filter(holder__in=holders).select_for_update()
78
        holdings = {}
79
        for h in hs:
80
            holdings[(h.holder, h.source, h.resource)] = h
81

    
82
        for holder, holder_quota in quotas.iteritems():
83
            for source, source_quota in holder_quota.iteritems():
84
                for resource, limit in source_quota.iteritems():
85
                    try:
86
                        h = holdings[(holder, source, resource)]
87
                    except KeyError:
88
                        h = Holding(holder=holder,
89
                                    source=source,
90
                                    resource=resource)
91

    
92
                    h.limit = limit
93
                    h.save()
94

    
95
    def issue_commission(self,
96
                         context=None,
97
                         clientkey=None,
98
                         name=None,
99
                         force=False,
100
                         provisions=()):
101

    
102
        if name is None:
103
            name = ""
104
        create = Commission.objects.create
105
        commission = create(clientkey=clientkey, name=name)
106
        serial = commission.serial
107

    
108
        operations = Operations()
109

    
110
        try:
111
            checked = []
112
            for provision in provisions:
113
                try:
114
                    holder = provision['holder']
115
                    source = provision['source']
116
                    resource = provision['resource']
117
                    quantity = provision['quantity']
118
                except KeyError:
119
                    raise InvalidDataError("Malformed provision")
120

    
121
                if not isinstance(quantity, (int, long)):
122
                    raise InvalidDataError("Malformed provision")
123

    
124
                ent_res = holder, resource
125
                if ent_res in checked:
126
                    m = "Duplicate provision for %s.%s" % ent_res
127
                    details = {'message': m,
128
                               }
129
                    raise DuplicateError(m,
130
                                         provision=provision,
131
                                         details=details)
132
                checked.append(ent_res)
133

    
134
                # Target
135
                try:
136
                    th = db_get_holding(holder=holder,
137
                                        resource=resource,
138
                                        source=source,
139
                                        for_update=True)
140
                except Holding.DoesNotExist:
141
                    m = ("There is no such holding %s.%s"
142
                         % (holder, resource))
143
                    raise NoHoldingError(m,
144
                                         provision=provision)
145

    
146
                if quantity >= 0:
147
                    operations.prepare(Import, th, quantity, force)
148

    
149
                else: # release
150
                    abs_quantity = -quantity
151
                    operations.prepare(Release, th, abs_quantity, force)
152

    
153
                Provision.objects.create(serial=commission,
154
                                         holding=th,
155
                                         quantity=quantity)
156

    
157
        except QuotaholderError:
158
            operations.revert()
159
            raise
160

    
161
        return serial
162

    
163
    def _log_provision(self,
164
                       commission, provision, log_time, reason):
165

    
166
        holding = provision.holding
167

    
168
        kwargs = {
169
            'serial':              commission.serial,
170
            'name':                commission.name,
171
            'holder':              holding.holder,
172
            'source':              holding.source,
173
            'resource':            holding.resource,
174
            'limit':               holding.limit,
175
            'imported_min':        holding.imported_min,
176
            'imported_max':        holding.imported_max,
177
            'delta_quantity':      provision.quantity,
178
            'issue_time':          commission.issue_time,
179
            'log_time':            log_time,
180
            'reason':              reason,
181
        }
182

    
183
        ProvisionLog.objects.create(**kwargs)
184

    
185
    def accept_commission(self,
186
                          context=None, clientkey=None,
187
                          serial=None, reason=''):
188
        log_time = now()
189

    
190
        try:
191
            c = db_get_commission(clientkey=clientkey, serial=serial,
192
                                  for_update=True)
193
        except Commission.DoesNotExist:
194
            return False
195

    
196
        operations = Operations()
197

    
198
        provisions = db_filter_provision(serial=serial, for_update=True)
199
        for pv in provisions:
200
            try:
201
                th = db_get_holding(id=pv.holding_id,
202
                                    for_update=True)
203
            except Holding.DoesNotExist:
204
                m = "Corrupted provision"
205
                raise CorruptedError(m)
206

    
207
            quantity = pv.quantity
208

    
209
            if quantity >= 0:
210
                operations.finalize(Import, th, quantity)
211
            else: # release
212
                abs_quantity = -quantity
213
                operations.finalize(Release, th, abs_quantity)
214

    
215
            reason = 'ACCEPT:' + reason[-121:]
216
            self._log_provision(c, pv, log_time, reason)
217
            pv.delete()
218
        c.delete()
219
        return True
220

    
221
    def reject_commission(self,
222
                          context=None, clientkey=None,
223
                          serial=None, reason=''):
224
        log_time = now()
225

    
226
        try:
227
            c = db_get_commission(clientkey=clientkey, serial=serial,
228
                                  for_update=True)
229
        except Commission.DoesNotExist:
230
            return False
231

    
232
        operations = Operations()
233

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

    
243
            quantity = pv.quantity
244

    
245
            if quantity >= 0:
246
                operations.undo(Import, th, quantity)
247
            else: # release
248
                abs_quantity = -quantity
249
                operations.undo(Release, th, abs_quantity)
250

    
251
            reason = 'REJECT:' + reason[-121:]
252
            self._log_provision(c, pv, log_time, reason)
253
            pv.delete()
254
        c.delete()
255
        return True
256

    
257
    def get_pending_commissions(self, context=None, clientkey=None):
258
        pending = Commission.objects.filter(clientkey=clientkey)
259
        pending_list = pending.values_list('serial', flat=True)
260
        return list(pending_list)
261

    
262
    def get_commission(self, clientkey=None, serial=None):
263
        try:
264
            commission = Commission.objects.get(clientkey=clientkey,
265
                                                serial=serial)
266
        except Commission.DoesNotExist:
267
            raise NoCommissionError(serial)
268

    
269
        objs = Provision.objects.select_related('holding')
270
        provisions = objs.filter(serial=commission)
271

    
272
        ps = [p.todict() for p in provisions]
273

    
274
        response = {'serial':     serial,
275
                    'provisions': ps,
276
                    'issue_time': commission.issue_time,
277
                    }
278
        return response
279

    
280
    def _resolve(self, include, exclude, operation):
281
        done = []
282
        failed = []
283
        for serial in include:
284
            if serial in exclude:
285
                failed.append((serial, 'CONFLICT'))
286
            else:
287
                response = operation(serial=serial)
288
                if response:
289
                    done.append(serial)
290
                else:
291
                    failed.append((serial, 'NOTFOUND'))
292
        return done, failed
293

    
294
    def resolve_pending_commissions(self,
295
                                    context=None, clientkey=None,
296
                                    accept_set=[], reject_set=[]):
297
        accept_set = set(accept_set)
298
        reject_set = set(reject_set)
299

    
300
        accept = partial(self.accept_commission, clientkey=clientkey)
301
        reject = partial(self.reject_commission, clientkey=clientkey)
302

    
303
        accepted, failed_ac = self._resolve(accept_set, reject_set, accept)
304
        rejected, failed_re = self._resolve(reject_set, accept_set, reject)
305

    
306
        failed = list(set(failed_ac + failed_re))
307
        return accepted, rejected, failed
308

    
309

    
310
API_Callpoint = QuotaholderDjangoDBCallpoint