From b1b49ba2861779f0f113a936f794f6d0c2a38d83 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 22 Jul 2015 15:08:37 -0400 Subject: [PATCH] Efficient stdout downloader implementation * Temporarily dump stdout contents to a configurable temp location * Implement file streaming to the host via a new format specifier in the api view --- awx/api/views.py | 9 ++++++--- awx/main/models/unified_jobs.py | 10 ++++++++++ awx/settings/defaults.py | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 69eed277b0..9fced1e980 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -25,6 +25,8 @@ from django.utils.safestring import mark_safe from django.utils.timezone import now from django.views.decorators.csrf import csrf_exempt from django.template.loader import render_to_string +from django.core.servers.basehttp import FileWrapper +from django.http import HttpResponse # Django REST Framework from rest_framework.exceptions import PermissionDenied, ParseError @@ -2844,9 +2846,10 @@ class UnifiedJobStdout(RetrieveAPIView): elif request.accepted_renderer.format == 'ansi': return Response(unified_job.result_stdout_raw) elif request.accepted_renderer.format == 'txt_download': - content = unified_job.result_stdout - headers = {"Content-Disposition": 'attachment; filename="job_%s.txt"' % str(unified_job.id)} - return Response(content, headers=headers) + content_fd = open(unified_job.dump_result_stdout(), 'r') + response = HttpResponse(FileWrapper(content_fd), content_type='text/plain') + response["Content-Disposition"] = 'attachment; filename="job_%s.txt"' % str(unified_job.id) + return response else: return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index f496c91afd..9d89aa0179 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -5,6 +5,7 @@ import codecs import json import logging +import uuid import re import os import os.path @@ -664,6 +665,15 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique record_size = cursor.fetchone()[0] return record_size + def dump_result_stdout(self): + tower_file = "towerjob-%s" % str(uuid.uuid1())[:8] + out_path = os.path.join(settings.STDOUT_TEMP_DIR, tower_file) + tower_fd = open(out_path, 'w') + cursor = connection.cursor() + cursor.copy_expert("copy (select result_stdout_text from main_unifiedjob where id = %d) to stdout" % (self.pk), tower_fd) + tower_fd.close() + return out_path + def _result_stdout_raw_limited(self, start_line=0, end_line=None, redact_sensitive=True, escape_ascii=False): return_buffer = u"" if end_line is not None: diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 88b184346f..912fe25e1e 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -563,6 +563,7 @@ FACT_CACHE_PORT = 6564 ORG_ADMINS_CAN_SEE_ALL_USERS = True STDOUT_MAX_BYTES_DISPLAY = 1000000 +STDOUT_TEMP_DIR = "/tmp" # Logging configuration. LOGGING = {