Revision ee45eb81 snf-astakos-app/astakos/im/models.py
b/snf-astakos-app/astakos/im/models.py | ||
---|---|---|
71 | 71 |
SITENAME, SERVICES, MODERATION_ENABLED) |
72 | 72 |
from astakos.im import settings as astakos_settings |
73 | 73 |
from astakos.im.endpoints.qh import ( |
74 |
register_users, send_quota, register_resources, add_quota, QuotaLimits) |
|
74 |
register_users, send_quota, register_resources, qh_add_quota, QuotaLimits, |
|
75 |
qh_query_serials, qh_ack_serials) |
|
75 | 76 |
from astakos.im import auth_providers |
76 | 77 |
#from astakos.im.endpoints.aquarium.producer import report_user_event |
77 | 78 |
#from astakos.im.tasks import propagate_groupmembers_quota |
78 | 79 |
|
79 | 80 |
import astakos.im.messages as astakos_messages |
81 |
from .managers import ForUpdateManager |
|
80 | 82 |
|
81 | 83 |
logger = logging.getLogger(__name__) |
82 | 84 |
|
... | ... | |
459 | 461 |
p = m.project |
460 | 462 |
if not p.is_active: |
461 | 463 |
continue |
462 |
grants = p.current_application.projectresourcegrant_set.all()
|
|
464 |
grants = p.application.projectresourcegrant_set.all() |
|
463 | 465 |
for g in grants: |
464 | 466 |
d[str(g.resource)] += g.member_capacity or inf |
465 | 467 |
# TODO set default for remaining |
... | ... | |
1431 | 1433 |
pass |
1432 | 1434 |
project = Project(creation_date=now) |
1433 | 1435 |
|
1434 |
project.latest_application = self |
|
1435 |
project.set_membership_replaced() |
|
1436 |
project.application = self |
|
1436 | 1437 |
|
1437 |
with exclusive_or_raise: |
|
1438 |
project.status_set_flag(Project.SYNC_PENDING_DEFINITION) |
|
1438 |
# This will block while syncing, |
|
1439 |
# but unblock before setting the membership state. |
|
1440 |
# See ProjectMembership.set_sync() |
|
1441 |
project.set_membership_pending_sync() |
|
1439 | 1442 |
|
1440 | 1443 |
project.last_approval_date = now |
1441 | 1444 |
project.save() |
... | ... | |
1475 | 1478 |
|
1476 | 1479 |
class Project(models.Model): |
1477 | 1480 |
|
1478 |
synced_application = models.OneToOneField(
|
|
1481 |
application = models.OneToOneField(
|
|
1479 | 1482 |
ProjectApplication, |
1480 |
related_name='project', |
|
1481 |
null=True) |
|
1482 |
latest_application = models.OneToOneField( |
|
1483 |
ProjectApplication, |
|
1484 |
related_name='last_project') |
|
1483 |
related_name='project') |
|
1485 | 1484 |
last_approval_date = models.DateTimeField(null=True) |
1486 | 1485 |
|
1487 | 1486 |
members = models.ManyToManyField( |
... | ... | |
1497 | 1496 |
db_index=True, |
1498 | 1497 |
unique=True) |
1499 | 1498 |
|
1500 |
status = models.IntegerField(db_index=True) |
|
1501 |
|
|
1502 |
SYNCHRONIZED = 0 |
|
1503 |
SYNC_PENDING_MEMBERSHIP = (1 << 0) |
|
1504 |
SYNC_PENDING_DEFINITION = (1 << 1) |
|
1505 |
# SYNC_PENDING = (SYNC_PENDING_DEFINITION | |
|
1506 |
# SYNC_PENDING_MEMBERSHIP) |
|
1507 |
|
|
1508 |
|
|
1509 |
def status_set_flag(self, s): |
|
1510 |
self.status |= s |
|
1511 |
|
|
1512 |
def status_unset_flag(self, s): |
|
1513 |
self.status &= ~s |
|
1514 |
|
|
1515 |
def status_is_set_flag(self, s): |
|
1516 |
return self.status & s == s |
|
1517 |
|
|
1518 |
@property |
|
1519 |
def current_application(self): |
|
1520 |
return self.synced_application or self.latest_application |
|
1521 |
|
|
1522 | 1499 |
@property |
1523 | 1500 |
def violated_resource_grants(self): |
1524 |
if self.synced_application is None: |
|
1525 |
return True |
|
1526 |
# do something |
|
1527 | 1501 |
return False |
1528 | 1502 |
|
1529 | 1503 |
@property |
1530 | 1504 |
def violated_members_number_limit(self): |
1531 |
application = self.synced_application |
|
1532 |
if application is None: |
|
1533 |
return True |
|
1505 |
application = self.application |
|
1534 | 1506 |
return len(self.approved_members) > application.limit_on_members_number |
1535 | 1507 |
|
1536 | 1508 |
@property |
... | ... | |
1578 | 1550 |
|
1579 | 1551 |
@property |
1580 | 1552 |
def approved_memberships(self): |
1553 |
ACCEPTED = ProjectMembership.ACCEPTED |
|
1554 |
PENDING = ProjectMembership.PENDING |
|
1581 | 1555 |
return self.projectmembership_set.filter( |
1582 |
synced_state=ProjectMembership.ACCEPTED)
|
|
1556 |
Q(state=ACCEPTED) | Q(state=PENDING))
|
|
1583 | 1557 |
|
1584 | 1558 |
@property |
1585 | 1559 |
def approved_members(self): |
1586 | 1560 |
return [m.person for m in self.approved_memberships] |
1587 | 1561 |
|
1588 |
def check_sync(self, hint=None): |
|
1589 |
if self.status != self.SYNCHRONIZED: |
|
1590 |
self.sync() |
|
1591 |
|
|
1592 |
def set_membership_replaced(self): |
|
1593 |
members = [m for m in self.approved_memberships |
|
1594 |
if m.sync_is_synced()] |
|
1562 |
def set_membership_pending_sync(self): |
|
1563 |
ACCEPTED = ProjectMembership.ACCEPTED |
|
1564 |
PENDING = ProjectMembership.PENDING |
|
1565 |
sfu = self.projectmembership_set.select_for_update() |
|
1566 |
members = sfu.filter(Q(state=ACCEPTED) | Q(state=PENDING)) |
|
1595 | 1567 |
|
1596 | 1568 |
for member in members: |
1597 |
member.sync_set_new_state(member.REPLACED)
|
|
1569 |
member.state = member.PENDING
|
|
1598 | 1570 |
member.save() |
1599 | 1571 |
|
1600 |
def sync_membership(self): |
|
1601 |
pending_members = self.projectmembership.filter( |
|
1602 |
sync_status=ProjectMembership.STATUS_PENDING) |
|
1603 |
for member in members: |
|
1604 |
try: |
|
1605 |
member.sync() |
|
1606 |
except Exception: |
|
1607 |
raise |
|
1608 |
|
|
1609 |
still_pending_members = self.members.filter( |
|
1610 |
sync_status=ProjectMembership.STATUS_PENDING) |
|
1611 |
if not still_pending_members: |
|
1612 |
with exclusive_or_raise: |
|
1613 |
self.status_unset_flag(self.SYNC_PENDING_MEMBERSHIP) |
|
1614 |
self.save() |
|
1615 |
|
|
1616 |
def sync_definition(self): |
|
1617 |
try: |
|
1618 |
self.sync_membership() |
|
1619 |
except Exception: |
|
1620 |
raise |
|
1621 |
else: |
|
1622 |
with exclusive_or_raise: |
|
1623 |
self.status_unset_flag(self.SYNC_PENDING_DEFINITION) |
|
1624 |
self.synced_application = self.latest_application |
|
1625 |
self.save() |
|
1626 |
|
|
1627 |
def sync(self): |
|
1628 |
if self.status_is_set_flag(self.SYNC_PENDING_DEFINITION): |
|
1629 |
self.sync_definition() |
|
1630 |
if self.status_is_set_flag(self.SYNC_PENDING_MEMBERSHIP): |
|
1631 |
self.sync_membership() |
|
1632 |
|
|
1633 | 1572 |
def add_member(self, user): |
1634 | 1573 |
""" |
1635 | 1574 |
Raises: |
... | ... | |
1745 | 1684 |
acceptance_date = models.DateField(null=True, db_index=True) |
1746 | 1685 |
leave_request_date = models.DateField(null=True) |
1747 | 1686 |
|
1687 |
objects = ForUpdateManager() |
|
1688 |
|
|
1748 | 1689 |
REQUESTED = 0 |
1749 | 1690 |
PENDING = 1 |
1750 | 1691 |
ACCEPTED = 2 |
1751 | 1692 |
REMOVING = 3 |
1752 | 1693 |
REMOVED = 4 |
1753 |
REJECTED = 5 # never seen, because of .delete() |
|
1754 |
REPLACED = 6 # when the project definition is replaced |
|
1755 |
# spontaneously goes back to ACCEPTED when synced |
|
1756 | 1694 |
|
1757 | 1695 |
class Meta: |
1758 | 1696 |
unique_together = ("person", "project") |
... | ... | |
1765 | 1703 |
__repr__ = __str__ |
1766 | 1704 |
|
1767 | 1705 |
def __init__(self, *args, **kwargs): |
1768 |
self.sync_init_state(self.REQUEST)
|
|
1706 |
self.state = self.REQUESTED
|
|
1769 | 1707 |
super(ProjectMembership, self).__init__(*args, **kwargs) |
1770 | 1708 |
|
1771 | 1709 |
def _set_history_item(self, reason, date=None): |
... | ... | |
1791 | 1729 |
self._set_history_item(reason='ACCEPT', date=now) |
1792 | 1730 |
self.state = self.PENDING |
1793 | 1731 |
self.save() |
1732 |
trigger_sync() |
|
1794 | 1733 |
|
1795 | 1734 |
def remove(self): |
1796 | 1735 |
if state != self.ACCEPTED: |
1797 | 1736 |
m = _("%s: attempt to remove in state '%s'") % (self, state) |
1798 | 1737 |
raise AssertionError(m) |
1799 | 1738 |
|
1800 |
serial = self._set_history_item(reason='REMOVE')
|
|
1739 |
self._set_history_item(reason='REMOVE') |
|
1801 | 1740 |
self.state = self.REMOVING |
1802 | 1741 |
self.save() |
1742 |
trigger_sync() |
|
1803 | 1743 |
|
1804 | 1744 |
def reject(self): |
1805 | 1745 |
if state != self.REQUESTED: |
... | ... | |
1850 | 1790 |
import_limit = cur_grant.import_limit |
1851 | 1791 |
export_limit = cur_grant.export_limit |
1852 | 1792 |
|
1853 |
capacity += factor * new_grant.member_capacity
|
|
1854 |
import_limit += factor * new_grant.member_import_limit
|
|
1855 |
export_limit += factor * new_grant.member_export_limit
|
|
1793 |
capacity += new_grant.member_capacity |
|
1794 |
import_limit += new_grant.member_import_limit |
|
1795 |
export_limit += new_grant.member_export_limit |
|
1856 | 1796 |
|
1857 | 1797 |
append(QuotaLimits(holder = holder, |
1858 | 1798 |
key = key, |
... | ... | |
1865 | 1805 |
limits_list.extend(tmp_grants.itervalues()) |
1866 | 1806 |
return limits_list |
1867 | 1807 |
|
1808 |
def set_sync(self): |
|
1809 |
state = self.state |
|
1810 |
if state == self.PENDING: |
|
1811 |
pending_application = self.pending_application |
|
1812 |
if pending_application is None: |
|
1813 |
m = _("%s: attempt to sync an empty pending application") % ( |
|
1814 |
self, state) |
|
1815 |
raise AssertionError(m) |
|
1816 |
self.application = pending_application |
|
1817 |
self.pending_application = None |
|
1818 |
self.pending_serial = None |
|
1819 |
|
|
1820 |
# project.application may have changed in the meantime, |
|
1821 |
# in which case we stay PENDING; |
|
1822 |
# we are safe to check due to select_for_update |
|
1823 |
if self.application == self.project.application: |
|
1824 |
self.state = self.ACCEPTED |
|
1825 |
self.save() |
|
1826 |
elif state == self.REMOVING: |
|
1827 |
self.delete() |
|
1828 |
else: |
|
1829 |
m = _("%s: attempt to sync in state '%s'") % (self, state) |
|
1830 |
raise AssertionError(m) |
|
1831 |
|
|
1832 |
class Serial(models.Model): |
|
1833 |
serial = models.AutoField(primary_key=True) |
|
1834 |
|
|
1835 |
def new_serial(): |
|
1836 |
s = Serial.create() |
|
1837 |
return s.serial |
|
1868 | 1838 |
|
1869 | 1839 |
def sync_finish_serials(): |
1870 |
serials_to_ack = set(quotaholder_query_sync_serials([]))
|
|
1840 |
serials_to_ack = set(qh_query_serials([]))
|
|
1871 | 1841 |
sfu = ProjectMembership.objects.select_for_update() |
1872 | 1842 |
memberships = sfu.filter(pending_serial__isnull=False) |
1873 | 1843 |
|
... | ... | |
1879 | 1849 |
membership.set_sync() |
1880 | 1850 |
|
1881 | 1851 |
transaction.commit() |
1882 |
quotaholder_ack_sync_serials(list(serials_to_ack)) |
|
1883 |
|
|
1852 |
qh_ack_serials(list(serials_to_ack)) |
|
1884 | 1853 |
|
1885 | 1854 |
def sync_projects(): |
1886 | 1855 |
sync_finish_serials() |
... | ... | |
1889 | 1858 |
REMOVING = ProjectMembership.REMOVING |
1890 | 1859 |
objects = ProjectMembership.objects.select_for_update() |
1891 | 1860 |
|
1892 |
projects = set() |
|
1893 | 1861 |
quotas = [] |
1894 | 1862 |
|
1895 |
serial = get_serial()
|
|
1863 |
serial = new_serial()
|
|
1896 | 1864 |
|
1897 | 1865 |
pending = objects.filter(state=PENDING) |
1898 | 1866 |
for membership in pending: |
... | ... | |
1906 | 1874 |
membership, membership.pending_serial) |
1907 | 1875 |
raise AssertionError(m) |
1908 | 1876 |
|
1909 |
membership.pending_application = membership.project.latest_application
|
|
1877 |
membership.pending_application = membership.project.application |
|
1910 | 1878 |
membership.pending_serial = serial |
1911 | 1879 |
membership.get_diff_quotas(quotas) |
1912 | 1880 |
membership.save() |
... | ... | |
1928 | 1896 |
membership.save() |
1929 | 1897 |
|
1930 | 1898 |
transaction.commit() |
1931 |
quotaholder_add_quotas(serial, quotas) |
|
1899 |
# ProjectApplication.approve() unblocks here |
|
1900 |
# and can set PENDING an already PENDING membership |
|
1901 |
# which has been scheduled to sync with the old project.application |
|
1902 |
# Need to check in ProjectMembership.set_sync() |
|
1903 |
|
|
1904 |
qh_add_quota(serial, quotas) |
|
1932 | 1905 |
sync_finish_serials() |
1933 | 1906 |
|
1934 | 1907 |
|
Also available in: Unified diff