Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / quotaholder / callpoint.py @ 8cff5e41

History | View | Annotate | Download (29.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
    CorruptedError, InvalidDataError,
37
    NoQuantityError, NoCapacityError,
38
    ExportLimitError, ImportLimitError,
39
    DuplicateError)
40

    
41
from astakos.quotaholder.utils.newname import newname
42
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
43

    
44
from django.db.models import Q, Count
45
from django.db.models import Q
46
from .models import (Policy, Holding,
47
                     Commission, Provision, ProvisionLog,
48
                     now,
49
                     db_get_holding, db_get_policy,
50
                     db_get_commission, db_filter_provision)
51

    
52

    
53
class QuotaholderDjangoDBCallpoint(object):
54

    
55
    def get_limits(self, context=None, get_limits=()):
56
        limits = []
57
        append = limits.append
58

    
59
        for policy in get_limits:
60
            try:
61
                p = Policy.objects.get(policy=policy)
62
            except Policy.DoesNotExist:
63
                continue
64

    
65
            append((policy, p.quantity, p.capacity,
66
                    p.import_limit, p.export_limit))
67

    
68
        return limits
69

    
70
    def set_limits(self, context=None, set_limits=()):
71

    
72
        for (policy, quantity, capacity,
73
             import_limit, export_limit) in set_limits:
74

    
75
            try:
76
                policy = db_get_policy(policy=policy, for_update=True)
77
            except Policy.DoesNotExist:
78
                Policy.objects.create(policy=policy,
79
                                      quantity=quantity,
80
                                      capacity=capacity,
81
                                      import_limit=import_limit,
82
                                      export_limit=export_limit)
83
            else:
84
                policy.quantity = quantity
85
                policy.capacity = capacity
86
                policy.export_limit = export_limit
87
                policy.import_limit = import_limit
88
                policy.save()
89

    
90
        return ()
91

    
92
    def get_holding(self, context=None, get_holding=()):
93
        holdings = []
94
        append = holdings.append
95

    
96
        for holder, resource in get_holding:
97
            try:
98
                h = Holding.objects.get(holder=holder, resource=resource)
99
            except Holding.DoesNotExist:
100
                continue
101

    
102
            append((h.holder, h.resource, h.policy.policy,
103
                    h.imported, h.exported,
104
                    h.returned, h.released, h.flags))
105

    
106
        return holdings
107

    
108
    def set_holding(self, context=None, set_holding=()):
109
        rejected = []
110
        append = rejected.append
111

    
112
        for holder, resource, policy, flags in set_holding:
113
            try:
114
                p = Policy.objects.get(policy=policy)
115
            except Policy.DoesNotExist:
116
                append((holder, resource, policy))
117
                continue
118

    
119
            try:
120
                h = db_get_holding(holder=holder, resource=resource,
121
                                   for_update=True)
122
                h.policy = p
123
                h.flags = flags
124
                h.save()
125
            except Holding.DoesNotExist:
126
                h = Holding.objects.create(holder=holder, resource=resource,
127
                                           policy=p, flags=flags)
128

    
129
        if rejected:
130
            raise QuotaholderError(rejected)
131
        return rejected
132

    
133
    def _init_holding(self,
134
                      holder, resource, policy,
135
                      imported, exported, returned, released,
136
                      flags):
137
        try:
138
            h = db_get_holding(holder=holder, resource=resource,
139
                               for_update=True)
140
        except Holding.DoesNotExist:
141
            h = Holding(holder=holder, resource=resource)
142

    
143
        h.policy = policy
144
        h.flags = flags
145
        h.imported = imported
146
        h.importing = imported
147
        h.exported = exported
148
        h.exporting = exported
149
        h.returned = returned
150
        h.returning = returned
151
        h.released = released
152
        h.releasing = released
153
        h.save()
154

    
155
    def init_holding(self, context=None, init_holding=()):
156
        rejected = []
157
        append = rejected.append
158

    
159
        for idx, sfh in enumerate(init_holding):
160
            (holder, resource, policy,
161
             imported, exported, returned, released,
162
             flags) = sfh
163

    
164
            try:
165
                p = Policy.objects.get(policy=policy)
166
            except Policy.DoesNotExist:
167
                append(idx)
168
                continue
169

    
170
            self._init_holding(holder, resource, p,
171
                               imported, exported,
172
                               returned, released,
173
                               flags)
174
        if rejected:
175
            raise QuotaholderError(rejected)
176
        return rejected
177

    
178
    def reset_holding(self, context=None, reset_holding=()):
179
        rejected = []
180
        append = rejected.append
181

    
182
        for idx, tpl in enumerate(reset_holding):
183
            (holder, resource,
184
             imported, exported, returned, released) = tpl
185

    
186
            try:
187
                h = db_get_holding(holder=holder, resource=resource,
188
                                   for_update=True)
189
                h.imported = imported
190
                h.importing = imported
191
                h.exported = exported
192
                h.exporting = exported
193
                h.returned = returned
194
                h.returning = returned
195
                h.released = released
196
                h.releasing = released
197
                h.save()
198
            except Holding.DoesNotExist:
199
                append(idx)
200
                continue
201

    
202
        if rejected:
203
            raise QuotaholderError(rejected)
204
        return rejected
205

    
206
    def _check_pending(self, holder, resource):
207
        cs = Commission.objects.filter(holder=holder)
208
        cs = [c for c in cs if c.provisions.filter(resource=resource)]
209
        as_target = [c.serial for c in cs]
210

    
211
        ps = Provision.objects.filter(holder=holder, resource=resource)
212
        as_source = [p.serial.serial for p in ps]
213

    
214
        return as_target + as_source
215

    
216
    def _actual_quantity(self, holding):
217
        hp = holding.policy
218
        return hp.quantity + (holding.imported + holding.returned -
219
                              holding.exported - holding.released)
220

    
221
    def release_holding(self, context=None, release_holding=()):
222
        rejected = []
223
        append = rejected.append
224

    
225
        for idx, (holder, resource) in enumerate(release_holding):
226
            try:
227
                h = db_get_holding(holder=holder, resource=resource,
228
                                   for_update=True)
229
            except Holding.DoesNotExist:
230
                append(idx)
231
                continue
232

    
233
            if self._check_pending(holder, resource):
234
                append(idx)
235
                continue
236

    
237
            q = self._actual_quantity(h)
238
            if q > 0:
239
                append(idx)
240
                continue
241

    
242
            h.delete()
243

    
244
        if rejected:
245
            raise QuotaholderError(rejected)
246
        return rejected
247

    
248
    def list_resources(self, context=None, holder=None):
249
        holdings = Holding.objects.filter(holder=holder)
250
        resources = [h.resource for h in holdings]
251
        return resources
252

    
253
    def list_holdings(self, context=None, list_holdings=()):
254
        rejected = []
255
        reject = rejected.append
256
        holdings_list = []
257
        append = holdings_list.append
258

    
259
        for holder in list_holdings:
260
            holdings = list(Holding.objects.filter(holder=holder))
261
            if not holdings:
262
                reject(holder)
263
                continue
264

    
265
            append([(holder, h.resource,
266
                     h.imported, h.exported, h.returned, h.released)
267
                    for h in holdings])
268

    
269
        return holdings_list, rejected
270

    
271
    def get_quota(self, context=None, get_quota=()):
272
        quotas = []
273
        append = quotas.append
274

    
275
        holders = set(holder for holder, r in get_quota)
276
        hs = Holding.objects.select_related().filter(holder__in=holders)
277
        holdings = {}
278
        for h in hs:
279
            holdings[(h.holder, h.resource)] = h
280

    
281
        for holder, resource in get_quota:
282
            try:
283
                h = holdings[(holder, resource)]
284
            except:
285
                continue
286

    
287
            p = h.policy
288

    
289
            append((h.holder, h.resource, p.quantity, p.capacity,
290
                    p.import_limit, p.export_limit,
291
                    h.imported, h.exported,
292
                    h.returned, h.released,
293
                    h.flags))
294

    
295
        return quotas
296

    
297
    def set_quota(self, context=None, set_quota=()):
298
        rejected = []
299
        append = rejected.append
300

    
301
        q_holdings = Q()
302
        holders = []
303
        for (holder, resource, _, _, _, _, _) in set_quota:
304
            holders.append(holder)
305

    
306
        hs = Holding.objects.filter(holder__in=holders).select_for_update()
307
        holdings = {}
308
        for h in hs:
309
            holdings[(h.holder, h.resource)] = h
310

    
311
        old_policies = []
312

    
313
        for (holder, resource,
314
             quantity, capacity,
315
             import_limit, export_limit, flags) in set_quota:
316

    
317
            policy = newname('policy_')
318
            newp = Policy(policy=policy,
319
                          quantity=quantity,
320
                          capacity=capacity,
321
                          import_limit=import_limit,
322
                          export_limit=export_limit)
323

    
324
            try:
325
                h = holdings[(holder, resource)]
326
                old_policies.append(h.policy_id)
327
                h.policy = newp
328
                h.flags = flags
329
            except KeyError:
330
                h = Holding(holder=holder, resource=resource,
331
                            policy=newp, flags=flags)
332

    
333
            # the order is intentionally reversed so that it
334
            # would break if we are not within a transaction.
335
            # Has helped before.
336
            h.save()
337
            newp.save()
338
            holdings[(holder, resource)] = h
339

    
340
        objs = Policy.objects.annotate(refs=Count('holding'))
341
        objs.filter(policy__in=old_policies, refs=0).delete()
342

    
343
        if rejected:
344
            raise QuotaholderError(rejected)
345
        return rejected
346

    
347
    def add_quota(self,
348
                  context=None,
349
                  sub_quota=(), add_quota=()):
350
        rejected = []
351
        append = rejected.append
352

    
353
        sources = sub_quota + add_quota
354
        q_holdings = Q()
355
        holders = []
356
        for (holder, resource, _, _, _, _) in sources:
357
            holders.append(holder)
358

    
359
        hs = Holding.objects.filter(holder__in=holders).select_for_update()
360
        holdings = {}
361
        for h in hs:
362
            holdings[(h.holder, h.resource)] = h
363

    
364
        pids = [h.policy_id for h in hs]
365
        policies = Policy.objects.in_bulk(pids)
366

    
367
        old_policies = []
368

    
369
        for removing, source in [(True, sub_quota), (False, add_quota)]:
370
            for (holder, resource,
371
                 quantity, capacity,
372
                 import_limit, export_limit) in source:
373

    
374
                try:
375
                    h = holdings[(holder, resource)]
376
                    old_policies.append(h.policy_id)
377
                    try:
378
                        p = policies[h.policy_id]
379
                    except KeyError:
380
                        raise AssertionError("no policy %s" % h.policy_id)
381
                except KeyError:
382
                    if removing:
383
                        append((holder, resource))
384
                        continue
385

    
386
                    h = Holding(holder=holder, resource=resource, flags=0)
387
                    p = None
388

    
389
                policy = newname('policy_')
390
                newp = Policy(policy=policy)
391

    
392
                newp.quantity = _add(p.quantity if p else 0, quantity,
393
                                     invert=removing)
394
                newp.capacity = _add(p.capacity if p else 0, capacity,
395
                                     invert=removing)
396
                newp.import_limit = _add(p.import_limit if p else 0,
397
                                         import_limit, invert=removing)
398
                newp.export_limit = _add(p.export_limit if p else 0,
399
                                         export_limit, invert=removing)
400

    
401
                new_values = [newp.capacity,
402
                              newp.import_limit, newp.export_limit]
403
                if any(map(_isneg, new_values)):
404
                    append((holder, resource))
405
                    continue
406

    
407
                h.policy = newp
408

    
409
                # the order is intentionally reversed so that it
410
                # would break if we are not within a transaction.
411
                # Has helped before.
412
                h.save()
413
                newp.save()
414
                policies[policy] = newp
415
                holdings[(holder, resource)] = h
416

    
417
        objs = Policy.objects.annotate(refs=Count('holding'))
418
        objs.filter(policy__in=old_policies, refs=0).delete()
419

    
420
        if rejected:
421
            raise QuotaholderError(rejected)
422

    
423
        return rejected
424

    
425
    def issue_commission(self,
426
                         context=None,
427
                         clientkey=None,
428
                         target=None,
429
                         name=None,
430
                         provisions=()):
431

    
432
        create = Commission.objects.create
433
        commission = create(holder=target, clientkey=clientkey, name=name)
434
        serial = commission.serial
435

    
436
        checked = []
437
        for holder, resource, quantity in provisions:
438

    
439
            if holder == target:
440
                m = "Cannot issue commission from an holder to itself (%s)" % (
441
                    holder,)
442
                raise InvalidDataError(m)
443

    
444
            ent_res = holder, resource
445
            if ent_res in checked:
446
                m = "Duplicate provision for %s.%s" % ent_res
447
                raise DuplicateError(m)
448
            checked.append(ent_res)
449

    
450
            release = 0
451
            if quantity < 0:
452
                release = 1
453

    
454
            # Source limits checks
455
            try:
456
                h = db_get_holding(holder=holder, resource=resource,
457
                                   for_update=True)
458
            except Holding.DoesNotExist:
459
                m = ("There is no quantity "
460
                     "to allocate from in %s.%s" % (holder, resource))
461
                raise NoQuantityError(m,
462
                                      source=holder, target=target,
463
                                      resource=resource, requested=quantity,
464
                                      current=0, limit=0)
465

    
466
            hp = h.policy
467

    
468
            if not release:
469
                current = h.exporting
470
                limit = hp.export_limit
471
                if current + quantity > limit:
472
                    m = ("Export limit reached for %s.%s" % (holder, resource))
473
                    raise ExportLimitError(m,
474
                                           source=holder,
475
                                           target=target,
476
                                           resource=resource,
477
                                           requested=quantity,
478
                                           current=current,
479
                                           limit=limit)
480

    
481
                limit = hp.quantity + h.imported - h.releasing
482
                unavailable = h.exporting - h.returned
483
                available = limit - unavailable
484

    
485
                if quantity > available:
486
                    m = ("There is not enough quantity "
487
                         "to allocate from in %s.%s" % (holder, resource))
488
                    raise NoQuantityError(m,
489
                                          source=holder,
490
                                          target=target,
491
                                          resource=resource,
492
                                          requested=quantity,
493
                                          current=unavailable,
494
                                          limit=limit)
495
            else:
496
                current = (+ h.importing + h.returning
497
                           - h.exported - h.returned)
498
                limit = hp.capacity
499
                if current - quantity > limit:
500
                    m = ("There is not enough capacity "
501
                         "to release to in %s.%s" % (holder, resource))
502
                    raise NoQuantityError(m,
503
                                          source=holder,
504
                                          target=target,
505
                                          resource=resource,
506
                                          requested=quantity,
507
                                          current=current,
508
                                          limit=limit)
509

    
510
            # Target limits checks
511
            try:
512
                th = db_get_holding(holder=target, resource=resource,
513
                                    for_update=True)
514
            except Holding.DoesNotExist:
515
                m = ("There is no capacity "
516
                     "to allocate into in %s.%s" % (target, resource))
517
                raise NoCapacityError(m,
518
                                      source=holder,
519
                                      target=target,
520
                                      resource=resource,
521
                                      requested=quantity,
522
                                      current=0,
523
                                      limit=0)
524

    
525
            tp = th.policy
526

    
527
            if not release:
528
                limit = tp.import_limit
529
                current = th.importing
530
                if current + quantity > limit:
531
                    m = ("Import limit reached for %s.%s" % (target, resource))
532
                    raise ImportLimitError(m,
533
                                           source=holder,
534
                                           target=target,
535
                                           resource=resource,
536
                                           requested=quantity,
537
                                           current=current,
538
                                           limit=limit)
539

    
540
                limit = tp.quantity + tp.capacity
541
                current = (+ th.importing + th.returning + tp.quantity
542
                           - th.exported - th.released)
543

    
544
                if current + quantity > limit:
545
                    m = ("There is not enough capacity "
546
                         "to allocate into in %s.%s" % (target, resource))
547
                    raise NoCapacityError(m,
548
                                          source=holder,
549
                                          target=target,
550
                                          resource=resource,
551
                                          requested=quantity,
552
                                          current=current,
553
                                          limit=limit)
554
            else:
555
                limit = tp.quantity + th.imported - th.releasing
556
                unavailable = th.exporting - th.returned
557
                available = limit - unavailable
558

    
559
                if available + quantity < 0:
560
                    m = ("There is not enough quantity "
561
                         "to release from in %s.%s" % (target, resource))
562
                    raise NoCapacityError(m,
563
                                          source=holder,
564
                                          target=target,
565
                                          resource=resource,
566
                                          requested=quantity,
567
                                          current=unavailable,
568
                                          limit=limit)
569

    
570
            Provision.objects.create(serial=commission,
571
                                     holder=holder,
572
                                     resource=resource,
573
                                     quantity=quantity)
574
            if release:
575
                h.returning -= quantity
576
                th.releasing -= quantity
577
            else:
578
                h.exporting += quantity
579
                th.importing += quantity
580

    
581
            h.save()
582
            th.save()
583

    
584
        return serial
585

    
586
    def _log_provision(self,
587
                       commission, s_holding, t_holding,
588
                       provision, log_time, reason):
589

    
590
        s_holder = s_holding.holder
591
        s_policy = s_holding.policy
592
        t_holder = t_holding.holder
593
        t_policy = t_holding.policy
594

    
595
        kwargs = {
596
            'serial':              commission.serial,
597
            'name':                commission.name,
598
            'source':              s_holder,
599
            'target':              t_holder,
600
            'resource':            provision.resource,
601
            'source_quantity':     s_policy.quantity,
602
            'source_capacity':     s_policy.capacity,
603
            'source_import_limit': s_policy.import_limit,
604
            'source_export_limit': s_policy.export_limit,
605
            'source_imported':     s_holding.imported,
606
            'source_exported':     s_holding.exported,
607
            'source_returned':     s_holding.returned,
608
            'source_released':     s_holding.released,
609
            'target_quantity':     t_policy.quantity,
610
            'target_capacity':     t_policy.capacity,
611
            'target_import_limit': t_policy.import_limit,
612
            'target_export_limit': t_policy.export_limit,
613
            'target_imported':     t_holding.imported,
614
            'target_exported':     t_holding.exported,
615
            'target_returned':     t_holding.returned,
616
            'target_released':     t_holding.released,
617
            'delta_quantity':      provision.quantity,
618
            'issue_time':          commission.issue_time,
619
            'log_time':            log_time,
620
            'reason':              reason,
621
        }
622

    
623
        ProvisionLog.objects.create(**kwargs)
624

    
625
    def accept_commission(self,
626
                          context=None, clientkey=None,
627
                          serials=(), reason=''):
628
        log_time = now()
629

    
630
        for serial in serials:
631
            try:
632
                c = db_get_commission(clientkey=clientkey, serial=serial,
633
                                      for_update=True)
634
            except Commission.DoesNotExist:
635
                return
636

    
637
            t = c.holder
638

    
639
            provisions = db_filter_provision(serial=serial, for_update=True)
640
            for pv in provisions:
641
                try:
642
                    h = db_get_holding(holder=pv.holder,
643
                                       resource=pv.resource, for_update=True)
644
                    th = db_get_holding(holder=t, resource=pv.resource,
645
                                        for_update=True)
646
                except Holding.DoesNotExist:
647
                    m = "Corrupted provision"
648
                    raise CorruptedError(m)
649

    
650
                quantity = pv.quantity
651
                release = 0
652
                if quantity < 0:
653
                    release = 1
654

    
655
                if release:
656
                    h.returned -= quantity
657
                    th.released -= quantity
658
                else:
659
                    h.exported += quantity
660
                    th.imported += quantity
661

    
662
                reason = 'ACCEPT:' + reason[-121:]
663
                self._log_provision(c, h, th, pv, log_time, reason)
664
                h.save()
665
                th.save()
666
                pv.delete()
667
            c.delete()
668

    
669
        return
670

    
671
    def reject_commission(self,
672
                          context=None, clientkey=None,
673
                          serials=(), reason=''):
674
        log_time = now()
675

    
676
        for serial in serials:
677
            try:
678
                c = db_get_commission(clientkey=clientkey, serial=serial,
679
                                      for_update=True)
680
            except Commission.DoesNotExist:
681
                return
682

    
683
            t = c.holder
684

    
685
            provisions = db_filter_provision(serial=serial, for_update=True)
686
            for pv in provisions:
687
                try:
688
                    h = db_get_holding(holder=pv.holder,
689
                                       resource=pv.resource, for_update=True)
690
                    th = db_get_holding(holder=t, resource=pv.resource,
691
                                        for_update=True)
692
                except Holding.DoesNotExist:
693
                    m = "Corrupted provision"
694
                    raise CorruptedError(m)
695

    
696
                quantity = pv.quantity
697
                release = 0
698
                if quantity < 0:
699
                    release = 1
700

    
701
                if release:
702
                    h.returning += quantity
703
                    th.releasing += quantity
704
                else:
705
                    h.exporting -= quantity
706
                    th.importing -= quantity
707

    
708
                reason = 'REJECT:' + reason[-121:]
709
                self._log_provision(c, h, th, pv, log_time, reason)
710
                h.save()
711
                th.save()
712
                pv.delete()
713
            c.delete()
714

    
715
        return
716

    
717
    def get_pending_commissions(self, context=None, clientkey=None):
718
        pending = Commission.objects.filter(clientkey=clientkey)
719
        pending_list = pending.values_list('serial', flat=True)
720
        return pending_list
721

    
722
    def resolve_pending_commissions(self,
723
                                    context=None, clientkey=None,
724
                                    max_serial=None, accept_set=()):
725
        accept_set = set(accept_set)
726
        pending = self.get_pending_commissions(context=context,
727
                                               clientkey=clientkey)
728
        pending = sorted(pending)
729

    
730
        accept = self.accept_commission
731
        reject = self.reject_commission
732

    
733
        for serial in pending:
734
            if serial > max_serial:
735
                break
736

    
737
            if serial in accept_set:
738
                accept(context=context, clientkey=clientkey, serials=[serial])
739
            else:
740
                reject(context=context, clientkey=clientkey, serials=[serial])
741

    
742
        return
743

    
744
    def get_timeline(self, context=None, after="", before="Z", get_timeline=()):
745
        holder_set = set()
746
        e_add = holder_set.add
747
        resource_set = set()
748
        r_add = resource_set.add
749

    
750
        for holder, resource in get_timeline:
751
            if holder not in holder_set:
752
                e_add(holder)
753

    
754
            r_add((holder, resource))
755

    
756
        chunk_size = 65536
757
        nr = 0
758
        timeline = []
759
        append = timeline.append
760
        filterlogs = ProvisionLog.objects.filter
761
        if holder_set:
762
            q_holder = Q(source__in=holder_set) | Q(target__in=holder_set)
763
        else:
764
            q_holder = Q()
765

    
766
        while 1:
767
            logs = filterlogs(q_holder,
768
                              issue_time__gt=after,
769
                              issue_time__lte=before,
770
                              reason__startswith='ACCEPT:')
771

    
772
            logs = logs.order_by('issue_time')
773
            #logs = logs.values()
774
            logs = logs[:chunk_size]
775
            nr += len(logs)
776
            if not logs:
777
                break
778
            for g in logs:
779
                if ((g.source, g.resource) not in resource_set
780
                    or (g.target, g.resource) not in resource_set):
781
                    continue
782

    
783
                o = {
784
                    'serial':                   g.serial,
785
                    'source':                   g.source,
786
                    'target':                   g.target,
787
                    'resource':                 g.resource,
788
                    'name':                     g.name,
789
                    'quantity':                 g.delta_quantity,
790
                    'source_allocated':         g.source_allocated(),
791
                    'source_allocated_through': g.source_allocated_through(),
792
                    'source_inbound':           g.source_inbound(),
793
                    'source_inbound_through':   g.source_inbound_through(),
794
                    'source_outbound':          g.source_outbound(),
795
                    'source_outbound_through':  g.source_outbound_through(),
796
                    'target_allocated':         g.target_allocated(),
797
                    'target_allocated_through': g.target_allocated_through(),
798
                    'target_inbound':           g.target_inbound(),
799
                    'target_inbound_through':   g.target_inbound_through(),
800
                    'target_outbound':          g.target_outbound(),
801
                    'target_outbound_through':  g.target_outbound_through(),
802
                    'issue_time':               g.issue_time,
803
                    'log_time':                 g.log_time,
804
                    'reason':                   g.reason,
805
                }
806

    
807
                append(o)
808

    
809
            after = g.issue_time
810
            if after >= before:
811
                break
812

    
813
        return timeline
814

    
815

    
816
def _add(x, y, invert=False):
817
    return x + y if not invert else x - y
818

    
819

    
820
def _isneg(x):
821
    return x < 0
822

    
823

    
824
API_Callpoint = QuotaholderDjangoDBCallpoint