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
This commit is contained in:
Matthew Jones
2015-07-22 15:08:37 -04:00
parent 55da712a95
commit b1b49ba286
3 changed files with 17 additions and 3 deletions

View File

@@ -25,6 +25,8 @@ from django.utils.safestring import mark_safe
from django.utils.timezone import now from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.core.servers.basehttp import FileWrapper
from django.http import HttpResponse
# Django REST Framework # Django REST Framework
from rest_framework.exceptions import PermissionDenied, ParseError from rest_framework.exceptions import PermissionDenied, ParseError
@@ -2844,9 +2846,10 @@ class UnifiedJobStdout(RetrieveAPIView):
elif request.accepted_renderer.format == 'ansi': elif request.accepted_renderer.format == 'ansi':
return Response(unified_job.result_stdout_raw) return Response(unified_job.result_stdout_raw)
elif request.accepted_renderer.format == 'txt_download': elif request.accepted_renderer.format == 'txt_download':
content = unified_job.result_stdout content_fd = open(unified_job.dump_result_stdout(), 'r')
headers = {"Content-Disposition": 'attachment; filename="job_%s.txt"' % str(unified_job.id)} response = HttpResponse(FileWrapper(content_fd), content_type='text/plain')
return Response(content, headers=headers) response["Content-Disposition"] = 'attachment; filename="job_%s.txt"' % str(unified_job.id)
return response
else: else:
return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs) return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs)

View File

@@ -5,6 +5,7 @@
import codecs import codecs
import json import json
import logging import logging
import uuid
import re import re
import os import os
import os.path import os.path
@@ -664,6 +665,15 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
record_size = cursor.fetchone()[0] record_size = cursor.fetchone()[0]
return record_size 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): def _result_stdout_raw_limited(self, start_line=0, end_line=None, redact_sensitive=True, escape_ascii=False):
return_buffer = u"" return_buffer = u""
if end_line is not None: if end_line is not None:

View File

@@ -563,6 +563,7 @@ FACT_CACHE_PORT = 6564
ORG_ADMINS_CAN_SEE_ALL_USERS = True ORG_ADMINS_CAN_SEE_ALL_USERS = True
STDOUT_MAX_BYTES_DISPLAY = 1000000 STDOUT_MAX_BYTES_DISPLAY = 1000000
STDOUT_TEMP_DIR = "/tmp"
# Logging configuration. # Logging configuration.
LOGGING = { LOGGING = {