Refactor of accessible_objects for polymorphic model

query simplification, with break-out into an intermediary
state that can be used in access methods
This commit is contained in:
AlanCoding
2016-12-20 09:55:08 -05:00
parent 8650b69c9e
commit b2d0871a5e
5 changed files with 110 additions and 68 deletions

View File

@@ -1825,25 +1825,22 @@ class JobEventAccess(BaseAccess):
class UnifiedJobTemplateAccess(BaseAccess): class UnifiedJobTemplateAccess(BaseAccess):
''' '''
I can see a unified job template whenever I can see the same project, I can see a unified job template whenever I can see the same project,
inventory source or job template. Unified job templates do not include inventory source, WFJT, or job template. Unified job templates do not include
projects without SCM configured or inventory sources without a cloud inventory sources without a cloud source.
source.
''' '''
model = UnifiedJobTemplate model = UnifiedJobTemplate
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.all() if self.user.is_superuser or self.user.is_system_auditor:
project_qs = self.user.get_queryset(Project).filter(scm_type__in=[s[0] for s in Project.SCM_TYPE_CHOICES]) qs = self.model.objects.all()
inventory_source_qs = self.user.get_queryset(InventorySource).filter(source__in=CLOUD_INVENTORY_SOURCES) else:
job_template_qs = self.user.get_queryset(JobTemplate) qs = self.model.objects.filter(
system_job_template_qs = self.user.get_queryset(SystemJobTemplate) Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role')) |
workflow_job_template_qs = self.user.get_queryset(WorkflowJobTemplate) Q(inventorysource__inventory__id__in=Inventory._accessible_pk_qs(
qs = qs.filter(Q(Project___in=project_qs) | Inventory, self.user, 'read_role')))
Q(InventorySource___in=inventory_source_qs) | qs = qs.exclude(inventorysource__source="")
Q(JobTemplate___in=job_template_qs) |
Q(systemjobtemplate__in=system_job_template_qs) |
Q(workflowjobtemplate__in=workflow_job_template_qs))
qs = qs.select_related( qs = qs.select_related(
'created_by', 'created_by',
'modified_by', 'modified_by',
@@ -1882,25 +1879,25 @@ class UnifiedJobAccess(BaseAccess):
model = UnifiedJob model = UnifiedJob
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.all() if self.user.is_superuser or self.user.is_system_auditor:
project_update_qs = self.user.get_queryset(ProjectUpdate) qs = self.model.objects.all()
inventory_update_qs = self.user.get_queryset(InventoryUpdate).filter(source__in=CLOUD_INVENTORY_SOURCES) else:
job_qs = self.user.get_queryset(Job) inv_pk_qs = Inventory._accessible_pk_qs(Inventory, self.user, 'read_role')
ad_hoc_command_qs = self.user.get_queryset(AdHocCommand) inv_update_qs = InventoryUpdate.objects.filter(inventory_source__inventory__id__in=inv_pk_qs)
system_job_qs = self.user.get_queryset(SystemJob) ad_hoc_command_qs = AdHocCommand.objects.filter(inventory__id__in=inv_pk_qs)
workflow_job_qs = self.user.get_queryset(WorkflowJob) org_auditor_qs = Organization.objects.filter(
qs = qs.filter(Q(ProjectUpdate___in=project_update_qs) | Q(admin_role__members=self.user) | Q(auditor_role__members=self.user))
Q(InventoryUpdate___in=inventory_update_qs) | qs = self.model.objects.filter(
Q(Job___in=job_qs) | Q(unified_job_template__id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role')) |
Q(AdHocCommand___in=ad_hoc_command_qs) | Q(inventoryupdate__in=inv_update_qs) |
Q(SystemJob___in=system_job_qs) | Q(adhoccommand__in=ad_hoc_command_qs) |
Q(WorkflowJob___in=workflow_job_qs)) Q(job__inventory__organization__in=org_auditor_qs) |
qs = qs.select_related( Q(job__project__organization__in=org_auditor_qs)
)
qs = qs.prefetch_related(
'created_by', 'created_by',
'modified_by', 'modified_by',
'unified_job_node__workflow_job', 'unified_job_node__workflow_job',
)
qs = qs.prefetch_related(
'unified_job_template', 'unified_job_template',
) )
@@ -1922,6 +1919,9 @@ class UnifiedJobAccess(BaseAccess):
# 'job_template__credential', # 'job_template__credential',
# 'job_template__cloud_credential', # 'job_template__cloud_credential',
#) #)
# Maybe we can do these, like:
# 'projectupdate__project',
# 'inventoryupdate__inventory'
return qs.all() return qs.all()

View File

@@ -3,6 +3,7 @@ import json
# Django # Django
from django.db import models from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User # noqa from django.contrib.auth.models import User # noqa
@@ -37,8 +38,9 @@ class ResourceMixin(models.Model):
''' '''
return ResourceMixin._accessible_objects(cls, accessor, role_field) return ResourceMixin._accessible_objects(cls, accessor, role_field)
@staticmethod @staticmethod
def _accessible_objects(cls, accessor, role_field): def _accessible_pk_qs(cls, accessor, role_field, content_types=None):
if type(accessor) == User: if type(accessor) == User:
ancestor_roles = accessor.roles.all() ancestor_roles = accessor.roles.all()
elif type(accessor) == Role: elif type(accessor) == Role:
@@ -47,14 +49,24 @@ class ResourceMixin(models.Model):
accessor_type = ContentType.objects.get_for_model(accessor) accessor_type = ContentType.objects.get_for_model(accessor)
ancestor_roles = Role.objects.filter(content_type__pk=accessor_type.id, ancestor_roles = Role.objects.filter(content_type__pk=accessor_type.id,
object_id=accessor.id) object_id=accessor.id)
qs = cls.objects.filter(pk__in =
RoleAncestorEntry.objects.filter( if content_types is not None:
ancestor__in=ancestor_roles, return RoleAncestorEntry.objects.filter(
content_type_id = ContentType.objects.get_for_model(cls).id, ancestor__in = ancestor_roles,
role_field = role_field content_type_id__in = content_types,
).values_list('object_id').distinct() role_field = role_field
) ).values_list('object_id').distinct()
return qs
return RoleAncestorEntry.objects.filter(
ancestor__in = ancestor_roles,
content_type_id = ContentType.objects.get_for_model(cls).id,
role_field = role_field
).values_list('object_id').distinct()
@staticmethod
def _accessible_objects(cls, accessor, role_field):
return cls.objects.filter(pk__in = ResourceMixin._accessible_pk_qs(cls, accessor, role_field))
def get_permissions(self, accessor): def get_permissions(self, accessor):

View File

@@ -20,6 +20,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.apps import apps from django.apps import apps
from django.contrib.contenttypes.models import ContentType
# Django-Polymorphic # Django-Polymorphic
from polymorphic import PolymorphicModel from polymorphic import PolymorphicModel
@@ -30,6 +31,7 @@ from djcelery.models import TaskMeta
# AWX # AWX
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.models.schedules import Schedule from awx.main.models.schedules import Schedule
from awx.main.models.mixins import ResourceMixin
from awx.main.utils import ( from awx.main.utils import (
decrypt_field, _inventory_updates, decrypt_field, _inventory_updates,
copy_model_by_class, copy_m2m_relationships copy_model_by_class, copy_m2m_relationships
@@ -166,6 +168,22 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
else: else:
return super(UnifiedJobTemplate, self).unique_error_message(model_class, unique_check) return super(UnifiedJobTemplate, self).unique_error_message(model_class, unique_check)
@classmethod
def accessible_pk_qs(cls, accessor, role_field):
'''
A re-implementation of accessible pk queryset for the "normal" unified JTs.
Does not return inventory sources or system JTs, these should
be handled inside of get_queryset where it is utilized.
'''
# Algorithmic option, if we don't want hardcoded class names
# ujt_name_list = [c.__name__.lower() for c in cls.__subclasses__()]
# ujt_name_list.remove('inventorysource')
subclass_content_types = ContentType.objects.filter(
model__in=['project', 'jobtemplate', 'systemjobtemplate', 'workflowjobtemplate']
).values_list('id', flat=True)
return ResourceMixin._accessible_pk_qs(cls, accessor, role_field, content_types=subclass_content_types)
def _perform_unique_checks(self, unique_checks): def _perform_unique_checks(self, unique_checks):
# Handle the list of unique fields returned above. Replace with an # Handle the list of unique fields returned above. Replace with an
# appropriate error message for the remaining field(s) in the unique # appropriate error message for the remaining field(s) in the unique

View File

@@ -1,15 +1,15 @@
resource medium resource medium jan2017
organizations 500 organizations 500 1
users 5000 users 5000 3
teams 500 teams 500 2
projects 1000 projects 1000 30
job-templates 2000 job-templates 2000 127
credentials 2000 credentials 2000 50
inventories 2000 inventories 2000 6
inventory-groups 500 inventory-groups 500 15
inventory-hosts 2500 inventory-hosts 2500 15
wfjts 100 wfjts 100 0
nodes 1000 nodes 1000 0
labels 1000 labels 1000 0
jobs 1000 jobs 1000 157208
job-events 1000 job-events 1000 3370942
1 resource medium jan2017
2 organizations 500 1
3 users 5000 3
4 teams 500 2
5 projects 1000 30
6 job-templates 2000 127
7 credentials 2000 50
8 inventories 2000 6
9 inventory-groups 500 15
10 inventory-hosts 2500 15
11 wfjts 100 0
12 nodes 1000 0
13 labels 1000 0
14 jobs 1000 157208
15 job-events 1000 3370942

View File

@@ -7,6 +7,7 @@ import sys
# Python # Python
from collections import defaultdict from collections import defaultdict
from optparse import make_option, OptionParser from optparse import make_option, OptionParser
import logging
# Django # Django
@@ -84,6 +85,7 @@ options = vars(options)
if options['preset']: if options['preset']:
print ' Using preset data numbers set ' + str(options['preset'])
# Read the numbers of resources from presets file, if provided # Read the numbers of resources from presets file, if provided
presets_filename = os.path.abspath(os.path.join( presets_filename = os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'presets.tsv')) os.path.dirname(os.path.abspath(__file__)), 'presets.tsv'))
@@ -603,22 +605,28 @@ try:
wfjt.labels.add(next(label_gen)) wfjt.labels.add(next(label_gen))
wfjt_idx += 1 wfjt_idx += 1
# Disable logging here, because it will mess up output format
logger = logging.getLogger('awx.main')
logger.propagate = False
print('# Creating %d jobs' % n_jobs) print('# Creating %d jobs' % n_jobs)
group_idx = 0 group_idx = 0
job_template_idx = 0 job_template_idx = 0
job_i = 0
for n in spread(n_jobs, n_job_templates): for n in spread(n_jobs, n_job_templates):
job_template = job_templates[job_template_idx] job_template = job_templates[job_template_idx]
for i in range(n): for i in range(n):
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, job_template.name, i+ 1)) sys.stdout.write('\r Assigning %d to %s: %d ' % (n, job_template.name, i+ 1))
sys.stdout.flush() sys.stdout.flush()
job_stat = 'successful'
if len(jobs) % 4 == 0: if len(jobs) % 4 == 0:
job_stat = 'failed' job_stat = 'failed'
elif len(jobs) % 11 == 0: elif len(jobs) % 11 == 0:
job_stat = 'canceled' job_stat = 'canceled'
else:
job_stat = 'successful'
job, _ = Job.objects.get_or_create( job, _ = Job.objects.get_or_create(
job_template=job_template, job_template=job_template,
status=job_stat, name=job_template.name, status=job_stat, name="%s-%d" % (job_template.name, job_i),
project=job_template.project, inventory=job_template.inventory, project=job_template.project, inventory=job_template.inventory,
credential=job_template.credential, credential=job_template.credential,
cloud_credential=job_template.cloud_credential, cloud_credential=job_template.cloud_credential,
@@ -626,25 +634,29 @@ try:
) )
job._is_new = _ job._is_new = _
jobs.append(job) jobs.append(job)
job_i += 1
if not job._is_new: if not job._is_new:
job_template_idx += 1
group_idx += 1
continue continue
if i == n: if i+1 == n:
job_template.last_job = job job_template.last_job = job
if job_template.pk % 5 == 0: if job_template.pk % 5 == 0:
job_template.current_job = job job_template.current_job = job
job_template.save() job_template.save()
with transaction.atomic(): if job._is_new:
if job_template.inventory: with transaction.atomic():
inv_groups = [g for g in job_template.inventory.groups.all()] if job_template.inventory:
if len(inv_groups): inv_groups = [g for g in job_template.inventory.groups.all()]
JobHostSummary.objects.bulk_create([ if len(inv_groups):
JobHostSummary( JobHostSummary.objects.bulk_create([
job=job, host=h, host_name=h.name, processed=1, JobHostSummary(
created=now(), modified=now() job=job, host=h, host_name=h.name, processed=1,
) created=now(), modified=now()
for h in inv_groups[group_idx % len(inv_groups)].hosts.all()[:100] )
]) for h in inv_groups[group_idx % len(inv_groups)].hosts.all()[:100]
])
group_idx += 1 group_idx += 1
job_template_idx += 1 job_template_idx += 1
if n: if n: