diff mbox series

[layerindex-web,2/3] layerindex/views: fix the live log on the task status page

Message ID 20261114003041.15197-3-tim.orling@konsulko.com
State Under Review
Delegated to: Tim Orling
Headers show
Series Fix the Update Layer button | expand

Commit Message

Tim Orling Nov. 14, 2026, 12:30 a.m. UTC
The task status page showed the log as a Python bytes literal, because
escape() no longer decodes bytes since Django 3.0, and did not see the
task finish when the poll reached another process than the one that
started it, because the rpc:// result backend only returns results to
that process. Decode the log, and take whether the task finished and
its exit status from the update record, which the task already sets.

[YOCTO #15395]

AI-Generated: Claude Opus 5.5
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
 layerindex/views.py         | 24 ++++++++----------------
 tests/test_update_button.py | 22 ++++++++++++++++++++++
 2 files changed, 30 insertions(+), 16 deletions(-)
diff mbox series

Patch

diff --git a/layerindex/views.py b/layerindex/views.py
index 9f238fd..e7728ca 100644
--- a/layerindex/views.py
+++ b/layerindex/views.py
@@ -1671,7 +1671,6 @@  class TaskStatusView(TemplateView):
         return context
 
 def task_log_view(request, task_id):
-    from celery.result import AsyncResult
     if not request.user.is_authenticated:
         raise PermissionDenied
 
@@ -1679,7 +1678,11 @@  def task_log_view(request, task_id):
         # Block anything that looks like a path
         raise Http404
 
-    result = AsyncResult(task_id)
+    # The task records when it finished and its result in the Update. Read
+    # that before the log, which is complete by the time it is set. (The
+    # Celery result backend is no use here: with rpc:// only the process
+    # that started the task can read the result.)
+    updateobj = get_object_or_404(Update, task_id=task_id)
     start = int(request.GET.get('start', 0))
     try:
         f = open(os.path.join(settings.TASK_LOG_DIR, 'task_%s.log' % task_id), 'rb')
@@ -1693,24 +1696,13 @@  def task_log_view(request, task_id):
         datastr = re.sub(b'\n[^\n]+\r', b'\n', datastr)
         # We need to escape this or else things that look like tags in the output
         # will be interpreted as such by the browser
-        data = escape(datastr)
+        data = escape(datastr.decode('utf-8', errors='replace'))
         response = HttpResponse(data)
-        try:
-            ready = result.ready()
-        except ConnectionResetError:
-            # FIXME this shouldn't be happening so often, but ultimately we don't care -
-            # the frontend is polling so it'll likely succeed in a subsequent request
-            ready = False
-        if ready:
+        if updateobj.finished:
             response['Task-Done'] = '1'
-            updateobj = get_object_or_404(Update, task_id=task_id)
             response['Task-Duration'] = utils.timesince2(updateobj.started, updateobj.finished)
             response['Task-Progress'] = 100
-            if result.info:
-                if isinstance(result.info, dict):
-                    response['Task-Result'] = result.info.get('retcode', None)
-                else:
-                    response['Task-Result'] = -1
+            response['Task-Result'] = updateobj.retcode
         else:
             response['Task-Done'] = '0'
             preader = utils.ProgressReader(settings.TASK_LOG_DIR, task_id)
diff --git a/tests/test_update_button.py b/tests/test_update_button.py
index b518bc2..bb3b84a 100644
--- a/tests/test_update_button.py
+++ b/tests/test_update_button.py
@@ -60,3 +60,25 @@  def test_run_update_command_keeps_fields_set_by_command(settings, tmp_path, monk
     assert update.reload
     assert (update.log, update.retcode) == ('output', 0)
     assert update.finished is not None
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('finished, retcode, done, result', [
+    (False, 0, '0', None),
+    (True, 0, '1', '0'),
+    (True, 2, '1', '2'),
+])
+def test_task_log_reports_status_from_update(client, django_user_model, settings, tmp_path,
+                                             finished, retcode, done, result):
+    settings.TASK_LOG_DIR = str(tmp_path)
+    (tmp_path / 'task_test-task.log').write_text('line 1 <b>\n', encoding='utf-8')
+    Update.objects.create(started=datetime.now(), task_id='test-task', retcode=retcode,
+                          finished=datetime.now() if finished else None)
+    client.force_login(django_user_model.objects.create_user('user', 'user@example.com', 'pw'))
+
+    response = client.get(reverse('task_log', args=('test-task',)))
+
+    assert response.status_code == 200
+    assert response.content == b'line 1 &lt;b&gt;\n'
+    assert response['Task-Done'] == done
+    assert response.get('Task-Result') == result