mirror of
https://github.com/ansible/awx.git
synced 2026-05-24 09:07:45 -02:30
Switched to a nicer contextmanager implemenation for role hierarchy rebuild batching
#1206
This commit is contained in:
@@ -23,7 +23,7 @@ from django.db.transaction import TransactionManagementError
|
|||||||
|
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.rbac import RolePermission, Role
|
from awx.main.models.rbac import RolePermission, Role, batch_role_ancestor_rebuilding
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AutoOneToOneField', 'ImplicitRoleField']
|
__all__ = ['AutoOneToOneField', 'ImplicitRoleField']
|
||||||
@@ -280,12 +280,11 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
for parent in parents:
|
for parent in parents:
|
||||||
new_parents.add(parent)
|
new_parents.add(parent)
|
||||||
|
|
||||||
Role.pause_role_ancestor_rebuilding()
|
with batch_role_ancestor_rebuilding():
|
||||||
for role in original_parents - new_parents:
|
for role in original_parents - new_parents:
|
||||||
this_role.parents.remove(role)
|
this_role.parents.remove(role)
|
||||||
for role in new_parents - original_parents:
|
for role in new_parents - original_parents:
|
||||||
this_role.parents.add(role)
|
this_role.parents.add(role)
|
||||||
Role.unpause_role_ancestor_rebuilding()
|
|
||||||
|
|
||||||
setattr(self, '__original_parent_roles', new_parents)
|
setattr(self, '__original_parent_roles', new_parents)
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class Command(BaseCommand):
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
Role.pause_role_ancestor_rebuilding()
|
with batch_role_ancestor_rebuilding():
|
||||||
|
|
||||||
print('# Creating %d organizations' % n_organizations)
|
print('# Creating %d organizations' % n_organizations)
|
||||||
for i in xrange(n_organizations):
|
for i in xrange(n_organizations):
|
||||||
@@ -344,10 +344,6 @@ class Command(BaseCommand):
|
|||||||
if n:
|
if n:
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Role.unpause_role_ancestor_rebuilding()
|
|
||||||
|
|
||||||
if options['pretend']:
|
if options['pretend']:
|
||||||
raise Rollback()
|
raise Rollback()
|
||||||
except Rollback:
|
except Rollback:
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
# Python
|
# Python
|
||||||
import logging
|
import logging
|
||||||
|
import threading
|
||||||
|
import contextlib
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.aggregates import Max
|
from django.db.models.aggregates import Max
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
@@ -19,6 +21,7 @@ from awx.main.models.base import * # noqa
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'Role',
|
'Role',
|
||||||
'RolePermission',
|
'RolePermission',
|
||||||
|
'batch_role_ancestor_rebuilding',
|
||||||
'get_user_permissions_on_resource',
|
'get_user_permissions_on_resource',
|
||||||
'get_role_permissions_on_resource',
|
'get_role_permissions_on_resource',
|
||||||
'ROLE_SINGLETON_SYSTEM_ADMINISTRATOR',
|
'ROLE_SINGLETON_SYSTEM_ADMINISTRATOR',
|
||||||
@@ -30,12 +33,43 @@ logger = logging.getLogger('awx.main.models.rbac')
|
|||||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator'
|
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator'
|
||||||
ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor'
|
ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor'
|
||||||
|
|
||||||
role_rebuilding_paused = False
|
|
||||||
roles_needing_rebuilding = set()
|
|
||||||
|
|
||||||
ALL_PERMISSIONS = {'create': True, 'read': True, 'update': True, 'delete': True,
|
ALL_PERMISSIONS = {'create': True, 'read': True, 'update': True, 'delete': True,
|
||||||
'write': True, 'scm_update': True, 'use': True, 'execute': True}
|
'write': True, 'scm_update': True, 'use': True, 'execute': True}
|
||||||
|
|
||||||
|
|
||||||
|
tls = threading.local() # thread local storage
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def batch_role_ancestor_rebuilding(allow_nesting=False):
|
||||||
|
'''
|
||||||
|
Batches the role ancestor rebuild work necessary whenever role-role
|
||||||
|
relations change. This can result in a big speedup when performing
|
||||||
|
any bulk manipulation.
|
||||||
|
|
||||||
|
WARNING: Calls to anything related to checking access/permissions
|
||||||
|
while within the context of the batch_role_ancestor_rebuilding will
|
||||||
|
likely not work.
|
||||||
|
'''
|
||||||
|
|
||||||
|
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
setattr(tls, 'batch_role_rebuilding', True)
|
||||||
|
if not batch_role_rebuilding:
|
||||||
|
setattr(tls, 'roles_needing_rebuilding', set())
|
||||||
|
yield
|
||||||
|
|
||||||
|
finally:
|
||||||
|
setattr(tls, 'batch_role_rebuilding', batch_role_rebuilding)
|
||||||
|
if not batch_role_rebuilding:
|
||||||
|
rebuild_set = getattr(tls, 'roles_needing_rebuilding')
|
||||||
|
with transaction.atomic():
|
||||||
|
for role in Role.objects.filter(id__in=list(rebuild_set)).all():
|
||||||
|
# TODO: We can reduce this to one rebuild call with our new upcoming rebuild method.. do this
|
||||||
|
role.rebuild_role_ancestor_list()
|
||||||
|
delattr(tls, 'roles_needing_rebuilding')
|
||||||
|
|
||||||
|
|
||||||
class Role(CommonModelNameNotUnique):
|
class Role(CommonModelNameNotUnique):
|
||||||
'''
|
'''
|
||||||
Role model
|
Role model
|
||||||
@@ -61,35 +95,6 @@ class Role(CommonModelNameNotUnique):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:role_detail', args=(self.pk,))
|
return reverse('api:role_detail', args=(self.pk,))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pause_role_ancestor_rebuilding():
|
|
||||||
'''
|
|
||||||
Pauses role ancestor list updating. This is useful when you're making
|
|
||||||
many changes to the same roles, for example doing bulk inserts or
|
|
||||||
making many changes to the same object in succession.
|
|
||||||
|
|
||||||
Note that the unpause_role_ancestor_rebuilding MUST be called within
|
|
||||||
the same execution context (preferably within the same transaction),
|
|
||||||
otherwise the RBAC role ancestor hierarchy will not be properly
|
|
||||||
updated.
|
|
||||||
'''
|
|
||||||
|
|
||||||
global role_rebuilding_paused
|
|
||||||
role_rebuilding_paused = True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unpause_role_ancestor_rebuilding():
|
|
||||||
'''
|
|
||||||
Unpauses the role ancestor list updating. This will will rebuild all
|
|
||||||
roles that need updating since the last call to
|
|
||||||
pause_role_ancestor_rebuilding and bring everything back into sync.
|
|
||||||
'''
|
|
||||||
global role_rebuilding_paused
|
|
||||||
global roles_needing_rebuilding
|
|
||||||
role_rebuilding_paused = False
|
|
||||||
for role in Role.objects.filter(id__in=list(roles_needing_rebuilding)).all():
|
|
||||||
role.rebuild_role_ancestor_list()
|
|
||||||
roles_needing_rebuilding = set()
|
|
||||||
|
|
||||||
def rebuild_role_ancestor_list(self):
|
def rebuild_role_ancestor_list(self):
|
||||||
'''
|
'''
|
||||||
@@ -100,9 +105,11 @@ class Role(CommonModelNameNotUnique):
|
|||||||
|
|
||||||
Note that this method relies on any parents' ancestor list being correct.
|
Note that this method relies on any parents' ancestor list being correct.
|
||||||
'''
|
'''
|
||||||
global role_rebuilding_paused, roles_needing_rebuilding
|
global tls
|
||||||
|
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
||||||
|
|
||||||
if role_rebuilding_paused:
|
if batch_role_rebuilding:
|
||||||
|
roles_needing_rebuilding = getattr(tls, 'roles_needing_rebuilding')
|
||||||
roles_needing_rebuilding.add(self.id)
|
roles_needing_rebuilding.add(self.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user