Revision d2b32360

b/snf-astakos-app/astakos/im/endpoints/qh.py
118 118
                                         'import_limit',
119 119
                                         'export_limit'))
120 120

  
121
def qh_add_quota(serial, quotalimits_list):
121
def qh_add_quota(serial, sub_list, add_list):
122 122
    if not QUOTAHOLDER_URL:
123 123
        return ()
124 124

  
125 125
    context = {}
126 126
    c = get_client()
127 127

  
128
    data = []
129
    append = data.append
130
    for ql in quotalimits_list:
128
    sub_quota = []
129
    sub_append = sub_quota.append
130
    add_quota = []
131
    add_append = add_quota.append
132

  
133
    for ql in sub_quota:
131 134
        args = (ql.holder, ql.resource, ENTITY_KEY,
132 135
                0, ql.capacity, ql.import_limit, ql.export_limit)
133
        append(args)
136
        sub_append(args)
137

  
138
    for ql in add_quota:
139
        args = (ql.holder, ql.resource, ENTITY_KEY,
140
                0, ql.capacity, ql.import_limit, ql.export_limit)
141
        add_append(args)
134 142

  
135 143
    result = c.add_quota(context=context,
136 144
                         clientkey=clientkey,
137 145
                         serial=serial,
138
                         add_quota=data)
146
                         sub_quota=sub_quota,
147
                         add_quota=add_quota)
139 148

  
140 149
    return result
141 150

  
b/snf-astakos-app/astakos/im/models.py
1501 1501
        self.state = APPROVED
1502 1502
        self.save()
1503 1503

  
1504
        transaction.commit()
1505
        trigger_sync()
1506

  
1507 1504

  
1508 1505
class ProjectResourceGrant(models.Model):
1509 1506

  
......
1682 1679
#             logger.error(e.messages)
1683 1680

  
1684 1681

  
1685

  
1686
class ExclusiveOrRaise(object):
1687
    """Context Manager to exclusively execute a critical code section.
1688
       The exclusion must be global.
1689
       (IPC semaphores will not protect across OS,
1690
        DB locks will if it's the same DB)
1691
    """
1692

  
1693
    class Busy(Exception):
1694
        pass
1695

  
1696
    def __init__(self, locked=False):
1697
        init = 0 if locked else 1
1698
        from multiprocessing import Semaphore
1699
        self._sema = Semaphore(init)
1700

  
1701
    def enter(self):
1702
        acquired = self._sema.acquire(False)
1703
        if not acquired:
1704
            raise self.Busy()
1705

  
1706
    def leave(self):
1707
        self._sema.release()
1708

  
1709
    def __enter__(self):
1710
        self.enter()
1711
        return self
1712

  
1713
    def __exit__(self, exc_type, exc_value, exc_traceback):
1714
        self.leave()
1715

  
1716

  
1717
exclusive_or_raise = ExclusiveOrRaise(locked=False)
1718

  
1719

  
1720 1682
class ProjectMembership(models.Model):
1721 1683

  
1722 1684
    person              =   models.ForeignKey(AstakosUser)
......
1783 1745
        self._set_history_item(reason='ACCEPT', date=now)
1784 1746
        self.state = self.PENDING
1785 1747
        self.save()
1786
        trigger_sync()
1787 1748

  
1788 1749
    def remove(self):
1789 1750
        if state != self.ACCEPTED:
......
1793 1754
        self._set_history_item(reason='REMOVE')
1794 1755
        self.state = self.REMOVING
1795 1756
        self.save()
1796
        trigger_sync()
1797 1757

  
1798 1758
    def reject(self):
1799 1759
        if state != self.REQUESTED:
......
1805 1765
        self._set_history_item(reason='REJECT')
1806 1766
        self.delete()
1807 1767

  
1808
    def get_diff_quotas(self, limits_list=None, remove=False):
1809
        if limits_list is None:
1810
            limits_list = []
1768
    def get_diff_quotas(self, sub_list=None, add_list=None, remove=False):
1769
        if sub_list is None:
1770
            sub_list = []
1771

  
1772
        if add_list is None:
1773
            add_list = []
1811 1774

  
1812
        append = limits_list.append
1775
        sub_append = sub_list.append
1776
        add_append = add_list.append
1813 1777
        holder = self.person.username
1814
        key = "1"
1815 1778

  
1816
        tmp_grants = {}
1817 1779
        synced_application = self.application
1818 1780
        if synced_application is not None:
1819 1781
            # first, inverse all current limits, and index them by resource name
1820 1782
            cur_grants = synced_application.resource_grants.all()
1821
            f = -1
1822 1783
            for grant in cur_grants:
1823
                name = grant.resource.name
1824
                tmp_grants[name] = QuotaLimits(
1825
                                holder       = holder,
1826
                                resource     = name,
1827
                                capacity     = f * grant.member_capacity,
1828
                                import_limit = f * grant.member_import_limit,
1829
                                export_limit = f * grant.member_export_limit)
1784
                sub_append(QuotaLimits(
1785
                               holder       = holder,
1786
                               resource     = grant.resource.name,
1787
                               capacity     = grant.member_capacity,
1788
                               import_limit = grant.member_import_limit,
1789
                               export_limit = grant.member_export_limit))
1830 1790

  
1831 1791
        if not remove:
1832 1792
            # second, add each new limit to its inverted current
1833 1793
            new_grants = self.pending_application.projectresourcegrant_set.all()
1834 1794
            for new_grant in new_grants:
1835
                name = new_grant.resource.name
1836
                cur_grant = tmp_grants.pop(name, None)
1837
                if cur_grant is None:
1838
                    # if limits on a new resource, set 0 current values
1839
                    capacity = 0
1840
                    import_limit = 0
1841
                    export_limit = 0
1842
                else:
1843
                    capacity = cur_grant.capacity
1844
                    import_limit = cur_grant.import_limit
1845
                    export_limit = cur_grant.export_limit
1846

  
1847
                capacity += new_grant.member_capacity
1848
                import_limit += new_grant.member_import_limit
1849
                export_limit += new_grant.member_export_limit
1795
                add_append(QuotaLimits(
1796
                               holder       = holder,
1797
                               resource     = new_grant.resource.name,
1798
                               capacity     = new_grant.capacity,
1799
                               import_limit = new_grant.import_limit,
1800
                               export_limit = new_grant.export_limit))
1850 1801

  
1851
                append(QuotaLimits(holder       = holder,
1852
                                   key          = key,
1853
                                   resource     = name,
1854
                                   capacity     = capacity,
1855
                                   import_limit = import_limit,
1856
                                   export_limit = export_limit))
1857

  
1858
        # third, append all the inverted current limits for removed resources
1859
        limits_list.extend(tmp_grants.itervalues())
1860
        return limits_list
1802
        return (sub_list, add_list)
1861 1803

  
1862 1804
    def set_sync(self):
1863 1805
        state = self.state
......
1914 1856
    REMOVING = ProjectMembership.REMOVING
1915 1857
    objects = ProjectMembership.objects.select_for_update()
1916 1858

  
1917
    quotas = []
1859
    sub_quota, add_quota = [], []
1918 1860

  
1919 1861
    serial = new_serial()
1920 1862

  
......
1932 1874

  
1933 1875
        membership.pending_application = membership.project.application
1934 1876
        membership.pending_serial = serial
1935
        membership.get_diff_quotas(quotas)
1877
        membership.get_diff_quotas(sub_quota, add_quota)
1936 1878
        membership.save()
1937 1879

  
1938 1880
    removing = objects.filter(state=REMOVING)
......
1948 1890
            raise AssertionError(m)
1949 1891

  
1950 1892
        membership.pending_serial = serial
1951
        membership.get_diff_quotas(quotas, remove=True)
1893
        membership.get_diff_quotas(sub_quota, add_quota, remove=True)
1952 1894
        membership.save()
1953 1895

  
1954 1896
    transaction.commit()
......
1957 1899
    # which has been scheduled to sync with the old project.application
1958 1900
    # Need to check in ProjectMembership.set_sync()
1959 1901

  
1960
    qh_add_quota(serial, quotas)
1902
    qh_add_quota(serial, sub_quota, add_quota)
1961 1903
    sync_finish_serials()
1962 1904

  
1963 1905

  
......
2136 2078
        instance.renew_token()
2137 2079
pre_save.connect(renew_token, sender=AstakosUser)
2138 2080
pre_save.connect(renew_token, sender=Service)
2081

  
b/snf-common/synnefo/lib/quotaholder/api/quotaholder.py
239 239
                context     =   Context,
240 240
                clientkey   =   ClientKey,
241 241
                serial      =   Serial,
242
                sub_quota   =   ListOf( Entity, Resource, Key,
243
                                        QuantityDelta, CapacityDelta,
244
                                        ImportLimitDelta, ExportLimitDelta ),
242 245
                add_quota   =   ListOf( Entity, Resource, Key,
243 246
                                        QuantityDelta, CapacityDelta,
244 247
                                        ImportLimitDelta, ExportLimitDelta )
b/snf-quotaholder-app/quotaholder_django/quotaholder_app/callpoint.py
506 506
            raise ReturnButFail(rejected)
507 507
        return rejected
508 508

  
509
    def add_quota(self, context={}, clientkey=None, serial=None, add_quota=()):
509
    def add_quota(self, context={}, clientkey=None, serial=None,
510
                  sub_quota=(), add_quota=()):
510 511
        rejected = []
511 512
        append = rejected.append
512
        all_pairs = [(q[0], q[1]) for q in add_quota]
513 513

  
514 514
        if serial is not None:
515 515
            if clientkey is None:
516
                all_pairs = [(q[0], q[1]) for q in sub_quota + add_quota]
516 517
                raise ReturnButFail(all_pairs)
517 518
            try:
518 519
                cs = CallSerial.objects.get(serial=serial, clientkey=clientkey)
520
                all_pairs = [(q[0], q[1]) for q in sub_quota + add_quota]
519 521
                raise ReturnButFail(all_pairs)
520 522
            except CallSerial.DoesNotExist:
521 523
                pass
522 524

  
523
        for (   entity, resource, key,
524
                quantity, capacity,
525
                import_limit, export_limit ) in add_quota:
526

  
527
                try:
528
                    e = Entity.objects.get(entity=entity, key=key)
529
                except Entity.DoesNotExist:
530
                    append((entity, resource))
531
                    continue
525
        for removing, source in [(True, sub_quota), (False, add_quota)]:
526
            for (   entity, resource, key,
527
                    quantity, capacity,
528
                    import_limit, export_limit ) in source:
532 529

  
533
                try:
534
                    h = db_get_holding(entity=entity, resource=resource,
535
                                       for_update=True)
536
                    p = h.policy
537
                except Holding.DoesNotExist:
538
                    h = Holding(entity=e, resource=resource, flags=0)
539
                    p = None
530
                    try:
531
                        e = Entity.objects.get(entity=entity, key=key)
532
                    except Entity.DoesNotExist:
533
                        append((entity, resource))
534
                        continue
540 535

  
541
                policy = newname('policy_')
542
                newp = Policy(policy=policy)
543

  
544
                newp.quantity = _add(p.quantity if p else 0, quantity)
545
                newp.capacity = _add(p.capacity if p else 0, capacity)
546
                newp.import_limit = _add(p.import_limit if p else 0,
547
                                              import_limit)
548
                newp.export_limit = _add(p.export_limit if p else 0,
549
                                              export_limit)
550

  
551
                new_values = [newp.capacity,
552
                              newp.import_limit, newp.export_limit]
553
                if any(map(_isneg, new_values)):
554
                    append((entity, resource))
555
                    continue
536
                    try:
537
                        h = db_get_holding(entity=entity, resource=resource,
538
                                           for_update=True)
539
                        p = h.policy
540
                    except Holding.DoesNotExist:
541
                        if removing:
542
                            append((entity, resource))
543
                            continue
544
                        h = Holding(entity=e, resource=resource, flags=0)
545
                        p = None
546

  
547
                    policy = newname('policy_')
548
                    newp = Policy(policy=policy)
549

  
550
                    newp.quantity = _add(p.quantity if p else 0, quantity,
551
                                         invert=removing)
552
                    newp.capacity = _add(p.capacity if p else 0, capacity,
553
                                         invert=removing)
554
                    newp.import_limit = _add(p.import_limit if p else 0,
555
                                                  import_limit, invert=removing)
556
                    newp.export_limit = _add(p.export_limit if p else 0,
557
                                                  export_limit, invert=removing)
558

  
559
                    new_values = [newp.capacity,
560
                                  newp.import_limit, newp.export_limit]
561
                    if any(map(_isneg, new_values)):
562
                        append((entity, resource))
563
                        continue
556 564

  
557
                h.policy = newp
565
                    h.policy = newp
558 566

  
559
                # the order is intentionally reversed so that it
560
                # would break if we are not within a transaction.
561
                # Has helped before.
562
                h.save()
563
                newp.save()
567
                    # the order is intentionally reversed so that it
568
                    # would break if we are not within a transaction.
569
                    # Has helped before.
570
                    h.save()
571
                    newp.save()
564 572

  
565
                if p is not None and p.holding_set.count() == 0:
566
                    p.delete()
573
                    if p is not None and p.holding_set.count() == 0:
574
                        p.delete()
567 575

  
568 576
        if rejected:
569 577
            raise ReturnButFail(rejected)
......
964 972

  
965 973
        return timeline
966 974

  
967
def _add(x, y):
975
def _add(x, y, invert=False):
976
    if invert and y is None:
977
        return 0
968 978
    if x is None or y is None:
969 979
        return None
970
    return x + y
980
    return x + y if not invert else x - y
971 981

  
972 982
def _update(dest, source, attr, delta):
973 983
    dest_attr = getattr(dest, attr)
b/snf-quotaholder-app/quotaholder_django/test/simpletests.py
230 230
        resource1 = self.rand_resource()
231 231

  
232 232
        r = self.qh.set_quota(
233
            set_quota=[(e0, resource0, k0) + (5, 5, 5, 5) + (0,),
233
            set_quota=[(e0, resource0, k0) + (5, None, 5, 6) + (0,),
234 234
                       (e1, resource0, k1) + (5, 5, 5, 5) + (0,)])
235 235
        self.assertEqual(r, [])
236 236

  
237 237
        r = self.qh.add_quota(clientkey=self.client,
238 238
                              serial=1,
239
                              add_quota=[(e0, resource0, k0, 0, (-2), None, 0),
239
                              sub_quota=[(e0, resource0, k0, 0, None, 1, 1)],
240
                              add_quota=[(e0, resource0, k0, 0, 3, None, 0),
241
                                         # new holding
240 242
                                         (e0, resource1, k0, 0, None, 5, 5)])
241 243
        self.assertEqual(r, [])
242 244

  
243 245
        r = self.qh.get_quota(get_quota=[(e0, resource0, k0),
244 246
                                         (e0, resource1, k0)])
245
        self.assertEqual(r, [(e0, resource0, 5, 5 - 2, None, 5)
247
        self.assertEqual(r, [(e0, resource0, 5, 3, None, 5)
246 248
                             + DEFAULT_HOLDING + (0,),
247 249
                             (e0, resource1, 0, None, 5, 5)
248 250
                             + DEFAULT_HOLDING + (0,)])
......
250 252
        # repeated serial
251 253
        r = self.qh.add_quota(clientkey=self.client,
252 254
                              serial=1,
253
                              add_quota=[(e0, resource0, k0, 0, 2, None, 0),
254
                                         (e0, resource1, k0, 0, None, (-5), 0)])
255
        self.assertEqual(r, [(e0, resource0), (e0, resource1)])
255
                              sub_quota=[(e0, resource1, k0, 0, None, (-5), 0)],
256
                              add_quota=[(e0, resource0, k0, 0, 2, None, 0)])
257
        self.assertEqual(r, [(e0, resource1), (e0, resource0)])
256 258

  
257 259
        r = self.qh.query_serials(clientkey=self.client, serials=[1, 2])
258 260
        self.assertEqual(r, [1])

Also available in: Unified diff