diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 506c439544..00f74c9e73 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -175,9 +175,6 @@ from awx.api.views.root import ( # noqa from awx.api.views.webhooks import WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver # noqa -EPOCH = datetime.datetime.utcfromtimestamp(0) - - logger = logging.getLogger('awx.api.views') @@ -889,7 +886,7 @@ class ProjectUpdateEventsList(SubListAPIView): def get_queryset(self): return super(ProjectUpdateEventsList, self).get_queryset().filter( - job_created__in=(self.get_parent_object().created, EPOCH) + job_created=self.get_parent_object().created_or_epoch ) class SystemJobEventsList(SubListAPIView): @@ -907,7 +904,7 @@ class SystemJobEventsList(SubListAPIView): def get_queryset(self): return super(SystemJobEventsList, self).get_queryset().filter( - job_created__in=(self.get_parent_object().created, EPOCH) + job_created=self.get_parent_object().created_or_epoch ) class ProjectUpdateCancel(RetrieveAPIView): @@ -3828,7 +3825,7 @@ class JobJobEventsList(BaseJobEventsList): job = self.get_parent_object() self.check_parent_access(job) qs = job.job_events.filter( - job_created__in=(job.created, EPOCH) + job_created=self.get_parent_object().created_or_epoch ).select_related('host').order_by('start_line') return qs.all() @@ -4011,7 +4008,7 @@ class BaseAdHocCommandEventsList(NoTruncateMixin, SubListAPIView): def get_queryset(self): return super(BaseAdHocCommandEventsList, self).get_queryset().filter( - job_created__in=(self.get_parent_object().created, EPOCH) + job_created=self.get_parent_object().created_or_epoch ) diff --git a/awx/api/views/inventory.py b/awx/api/views/inventory.py index a812f757d0..845f352e64 100644 --- a/awx/api/views/inventory.py +++ b/awx/api/views/inventory.py @@ -41,8 +41,6 @@ from awx.api.views.mixin import RelatedJobsPreventDeleteMixin, ControlledByScmMi logger = logging.getLogger('awx.api.views.organization') -EPOCH = datetime.datetime.utcfromtimestamp(0) - class InventoryUpdateEventsList(SubListAPIView): @@ -55,7 +53,7 @@ class InventoryUpdateEventsList(SubListAPIView): def get_queryset(self): return super(InventoryUpdateEventsList, self).get_queryset().filter( - job_created__in=(self.get_parent_object().created, EPOCH) + job_created=self.get_parent_object().created_or_epoch ) def finalize_response(self, request, response, *args, **kwargs): diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 6627826923..880cac6e1c 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -49,6 +49,7 @@ from awx.main.utils import ( getattr_dne, polymorphic, schedule_task_manager, + get_event_partition_epoch ) from awx.main.constants import ACTIVE_STATES, CAN_CANCEL from awx.main.redact import UriCleaner, REPLACE_STR @@ -735,6 +736,18 @@ class UnifiedJob( def _get_task_class(cls): raise NotImplementedError # Implement in subclasses. + @property + def created_or_epoch(self): + # returns self.created *unless* the job was created *prior* + # to the datetime the event partition migration is applied + # (in that case, it returns the epoch, which is the date + # which is automatically applied to all events rows that predate + # that migration) + applied = get_event_partition_epoch() + if applied and self.created < applied: + return datetime.datetime.utcfromtimestamp(0) + return self.created + @property def can_run_containerized(self): return False diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index a45dff34b8..3d463441fc 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -90,7 +90,7 @@ __all__ = [ 'create_temporary_fifo', 'truncate_stdout', 'deepmerge', - 'cleanup_new_process', + 'get_event_partition_epoch', ] @@ -208,6 +208,28 @@ def memoize_delete(function_name): return cache.delete(function_name) +@memoize(ttl=3600 * 24) # in practice, we only need this to load once at process startup time +def get_event_partition_epoch(): + from django.db.migrations.recorder import MigrationRecorder + return MigrationRecorder.Migration.objects.filter( + app='main', name='0124_event_partitions' + ).first().applied + + +@memoize() +def get_ansible_version(): + """ + Return Ansible version installed. + Ansible path needs to be provided to account for custom virtual environments + """ + try: + proc = subprocess.Popen(['ansible', '--version'], stdout=subprocess.PIPE) + result = smart_str(proc.communicate()[0]) + return result.split('\n')[0].replace('ansible', '').strip() + except Exception: + return 'unknown' + + def get_awx_version(): """ Return AWX version as reported by setuptools.