From ca2964b8028e5e9a920b459dffafab83c11c9748 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Thu, 30 Jun 2022 23:37:28 -0400 Subject: [PATCH 1/2] add debug views for task manager(s) implement https://github.com/ansible/awx/issues/12446 in development environment, enable set of views that run the task manager(s). Also introduce a setting that disables any calls to schedule() that do not originate from the debug views when in the development environment. With guards around both if we are in the development environment and the setting, I think we're pretty safe this won't get triggered unintentionally. --- awx/api/urls/debug.py | 17 ++++++++ awx/api/urls/urls.py | 4 ++ awx/api/views/debug.py | 68 ++++++++++++++++++++++++++++++ awx/main/scheduler/task_manager.py | 7 ++- awx/settings/development.py | 7 +++ 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 awx/api/urls/debug.py create mode 100644 awx/api/views/debug.py diff --git a/awx/api/urls/debug.py b/awx/api/urls/debug.py new file mode 100644 index 0000000000..30eb6d08b3 --- /dev/null +++ b/awx/api/urls/debug.py @@ -0,0 +1,17 @@ +from django.urls import re_path + +from awx.api.views.debug import ( + DebugRootView, + TaskManagerDebugView, + DependencyManagerDebugView, + WorkflowManagerDebugView, +) + +urls = [ + re_path(r'^$', DebugRootView.as_view(), name='debug'), + re_path(r'^task_manager/$', TaskManagerDebugView.as_view(), name='task_manager'), + re_path(r'^dependency_manager/$', DependencyManagerDebugView.as_view(), name='dependency_manager'), + re_path(r'^workflow_manager/$', WorkflowManagerDebugView.as_view(), name='workflow_manager'), +] + +__all__ = ['urls'] diff --git a/awx/api/urls/urls.py b/awx/api/urls/urls.py index c092696d24..0485a78e00 100644 --- a/awx/api/urls/urls.py +++ b/awx/api/urls/urls.py @@ -149,3 +149,7 @@ if settings.SETTINGS_MODULE == 'awx.settings.development': from awx.api.swagger import SwaggerSchemaView urlpatterns += [re_path(r'^swagger/$', SwaggerSchemaView.as_view(), name='swagger_view')] + + from awx.api.urls.debug import urls as debug_urls + + urlpatterns += [re_path(r'^debug/', include(debug_urls))] diff --git a/awx/api/views/debug.py b/awx/api/views/debug.py new file mode 100644 index 0000000000..c26448e300 --- /dev/null +++ b/awx/api/views/debug.py @@ -0,0 +1,68 @@ +from collections import OrderedDict + +from django.conf import settings + +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from rest_framework.views import APIView + +from awx.main.scheduler import TaskManager, DependencyManager, WorkflowManager + + +class TaskManagerDebugView(APIView): + _ignore_model_permissions = True + exclude_from_schema = True + permission_classes = [AllowAny] + prefix = 'Task' + + def get(self, request): + TaskManager().schedule(debug=True) + if not settings.AWX_DISABLE_TASK_MANAGERS: + msg = f"Running {self.prefix} manager. To disable other triggers to the {self.prefix} manager, set AWX_DISABLE_TASK_MANAGERS to True" + else: + msg = f"AWX_DISABLE_TASK_MANAGERS is True, this view is the only way to trigger the {self.prefix} manager" + return Response(msg) + + +class DependencyManagerDebugView(APIView): + _ignore_model_permissions = True + exclude_from_schema = True + permission_classes = [AllowAny] + prefix = 'Dependency' + + def get(self, request): + DependencyManager().schedule(debug=True) + if not settings.AWX_DISABLE_TASK_MANAGERS: + msg = f"Running {self.prefix} manager. To disable other triggers to the {self.prefix} manager, set AWX_DISABLE_TASK_MANAGERS to True" + else: + msg = f"AWX_DISABLE_TASK_MANAGERS is True, this view is the only way to trigger the {self.prefix} manager" + return Response(msg) + + +class WorkflowManagerDebugView(APIView): + _ignore_model_permissions = True + exclude_from_schema = True + permission_classes = [AllowAny] + prefix = 'Workflow' + + def get(self, request): + WorkflowManager().schedule(debug=True) + if not settings.AWX_DISABLE_TASK_MANAGERS: + msg = f"Running {self.prefix} manager. To disable other triggers to the {self.prefix} manager, set AWX_DISABLE_TASK_MANAGERS to True" + else: + msg = f"AWX_DISABLE_TASK_MANAGERS is True, this view is the only way to trigger the {self.prefix} manager" + return Response(msg) + + +class DebugRootView(APIView): + _ignore_model_permissions = True + exclude_from_schema = True + permission_classes = [AllowAny] + + def get(self, request, format=None): + '''List of available debug urls''' + data = OrderedDict() + data['task_manager'] = '/api/debug/task_manager/' + data['dependency_manager'] = '/api/debug/dependency_manager/' + data['workflow_manager'] = '/api/debug/workflow_manager/' + return Response(data) diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index b85bfb6982..f8fc28307d 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -109,7 +109,12 @@ class TaskBase: self.record_aggregate_metrics() sys.exit(1) - def schedule(self): + def schedule(self, debug=False): + + if settings.SETTINGS_MODULE == 'awx.settings.development' and settings.AWX_DISABLE_TASK_MANAGERS and not debug: + logger.debug(f"Not running {self.prefix} scheduler, AWX_DISABLE_TASK_MANAGERS is True. Trigger with GET to /api/{self.prefix}_manager_debug/") + return + # Lock with advisory_lock(f"{self.prefix}_lock", wait=False) as acquired: with transaction.atomic(): diff --git a/awx/settings/development.py b/awx/settings/development.py index be1c115606..c5b5ab1a36 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -110,5 +110,12 @@ CLUSTER_HOST_ID = socket.gethostname() AWX_CALLBACK_PROFILE = True +# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!================================= +# Disable normal scheduled/triggered task managers (DependencyManager, TaskManager, WorkflowManager). +# Allows user to trigger task managers directly for debugging and profiling purposes. +# Only works in combination with settings.SETTINGS_MODULE == 'awx.settings.development' +AWX_DISABLE_TASK_MANAGERS = os.getenv('AWX_DISABLE_TASK_MANAGERS', False) +# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!================================= + if 'sqlite3' not in DATABASES['default']['ENGINE']: # noqa DATABASES['default'].setdefault('OPTIONS', dict()).setdefault('application_name', f'{CLUSTER_HOST_ID}-{os.getpid()}-{" ".join(sys.argv)}'[:63]) # noqa From 6bd4e9c816162c87bdf4468e86053984779942d2 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Fri, 1 Jul 2022 10:53:28 -0400 Subject: [PATCH 2/2] use MODE to determine if we are in devel env Also, move test for skipping task managers to the tasks file --- awx/api/urls/urls.py | 4 +++- awx/api/views/debug.py | 6 +++--- awx/main/scheduler/task_manager.py | 7 +------ awx/main/scheduler/tasks.py | 20 ++++++++++++++++++++ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/awx/api/urls/urls.py b/awx/api/urls/urls.py index 0485a78e00..96c57e97c9 100644 --- a/awx/api/urls/urls.py +++ b/awx/api/urls/urls.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, unicode_literals from django.conf import settings from django.urls import include, re_path +from awx import MODE from awx.api.generics import LoggedLoginView, LoggedLogoutView from awx.api.views import ( ApiRootView, @@ -145,7 +146,8 @@ urlpatterns = [ re_path(r'^logout/$', LoggedLogoutView.as_view(next_page='/api/', redirect_field_name='next'), name='logout'), re_path(r'^o/', include(oauth2_root_urls)), ] -if settings.SETTINGS_MODULE == 'awx.settings.development': +if MODE == 'development': + # Only include these if we are in the development environment from awx.api.swagger import SwaggerSchemaView urlpatterns += [re_path(r'^swagger/$', SwaggerSchemaView.as_view(), name='swagger_view')] diff --git a/awx/api/views/debug.py b/awx/api/views/debug.py index c26448e300..13dfc4a604 100644 --- a/awx/api/views/debug.py +++ b/awx/api/views/debug.py @@ -16,7 +16,7 @@ class TaskManagerDebugView(APIView): prefix = 'Task' def get(self, request): - TaskManager().schedule(debug=True) + TaskManager().schedule() if not settings.AWX_DISABLE_TASK_MANAGERS: msg = f"Running {self.prefix} manager. To disable other triggers to the {self.prefix} manager, set AWX_DISABLE_TASK_MANAGERS to True" else: @@ -31,7 +31,7 @@ class DependencyManagerDebugView(APIView): prefix = 'Dependency' def get(self, request): - DependencyManager().schedule(debug=True) + DependencyManager().schedule() if not settings.AWX_DISABLE_TASK_MANAGERS: msg = f"Running {self.prefix} manager. To disable other triggers to the {self.prefix} manager, set AWX_DISABLE_TASK_MANAGERS to True" else: @@ -46,7 +46,7 @@ class WorkflowManagerDebugView(APIView): prefix = 'Workflow' def get(self, request): - WorkflowManager().schedule(debug=True) + WorkflowManager().schedule() if not settings.AWX_DISABLE_TASK_MANAGERS: msg = f"Running {self.prefix} manager. To disable other triggers to the {self.prefix} manager, set AWX_DISABLE_TASK_MANAGERS to True" else: diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index f8fc28307d..b85bfb6982 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -109,12 +109,7 @@ class TaskBase: self.record_aggregate_metrics() sys.exit(1) - def schedule(self, debug=False): - - if settings.SETTINGS_MODULE == 'awx.settings.development' and settings.AWX_DISABLE_TASK_MANAGERS and not debug: - logger.debug(f"Not running {self.prefix} scheduler, AWX_DISABLE_TASK_MANAGERS is True. Trigger with GET to /api/{self.prefix}_manager_debug/") - return - + def schedule(self): # Lock with advisory_lock(f"{self.prefix}_lock", wait=False) as acquired: with transaction.atomic(): diff --git a/awx/main/scheduler/tasks.py b/awx/main/scheduler/tasks.py index 83d53185a2..307f0a7d69 100644 --- a/awx/main/scheduler/tasks.py +++ b/awx/main/scheduler/tasks.py @@ -1,7 +1,11 @@ # Python import logging +# Django +from django.conf import settings + # AWX +from awx import MODE from awx.main.scheduler import TaskManager, DependencyManager, WorkflowManager from awx.main.dispatch.publish import task from awx.main.dispatch import get_local_queuename @@ -11,20 +15,36 @@ logger = logging.getLogger('awx.main.scheduler') @task(queue=get_local_queuename) def task_manager(): + prefix = 'task' + if MODE == 'development' and settings.AWX_DISABLE_TASK_MANAGERS: + logger.debug(f"Not running {prefix} manager, AWX_DISABLE_TASK_MANAGERS is True. Trigger with GET to /api/debug/{prefix}_manager/") + return + TaskManager().schedule() @task(queue=get_local_queuename) def dependency_manager(): + prefix = 'dependency' + if MODE == 'development' and settings.AWX_DISABLE_TASK_MANAGERS: + logger.debug(f"Not running {prefix} manager, AWX_DISABLE_TASK_MANAGERS is True. Trigger with GET to /api/debug/{prefix}_manager/") + return DependencyManager().schedule() @task(queue=get_local_queuename) def workflow_manager(): + prefix = 'workflow' + if MODE == 'development' and settings.AWX_DISABLE_TASK_MANAGERS: + logger.debug(f"Not running {prefix} manager, AWX_DISABLE_TASK_MANAGERS is True. Trigger with GET to /api/debug/{prefix}_manager/") + return WorkflowManager().schedule() def run_task_manager(): + if MODE == 'development' and settings.AWX_DISABLE_TASK_MANAGERS: + logger.debug(f"Not running task managers, AWX_DISABLE_TASK_MANAGERS is True. Trigger with GET to /api/debug/{prefix}_manager/") + return task_manager() dependency_manager() workflow_manager()