From f2dbdd77928fe53caf8ad558d5dbc5cf9564dcd0 Mon Sep 17 00:00:00 2001 From: Erwan Rouchet <rouchet@teklia.com> Date: Tue, 7 Jan 2025 18:06:13 +0100 Subject: [PATCH] Only allow tasks in a final state to have a finished date --- ...task_task_finished_requires_final_state.py | 43 +++++++++++++++++++ arkindex/ponos/models.py | 5 +++ .../ponos/tests/tasks/test_partial_update.py | 2 + arkindex/ponos/tests/tasks/test_update.py | 2 + 4 files changed, 52 insertions(+) create mode 100644 arkindex/ponos/migrations/0014_task_task_finished_requires_final_state.py diff --git a/arkindex/ponos/migrations/0014_task_task_finished_requires_final_state.py b/arkindex/ponos/migrations/0014_task_task_finished_requires_final_state.py new file mode 100644 index 0000000000..13dbe15627 --- /dev/null +++ b/arkindex/ponos/migrations/0014_task_task_finished_requires_final_state.py @@ -0,0 +1,43 @@ +# Generated by Django 5.0.8 on 2025-01-07 10:11 + +from django.db import migrations, models + +from arkindex.ponos.models import State + +# Copy the FINAL_STATES here so that if we ever change them, +# Django will detect it and require a new migration +FINAL_STATES = ( + State.Completed, + State.Failed, + State.Error, + State.Stopped, + State.Cancelled, +) + +def clear_unexpected_finish_dates(apps, schema_editor): + Task = apps.get_model("ponos", "Task") + Task.objects.exclude(state__in=FINAL_STATES).exclude(finished=None).update(finished=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ("ponos", "0013_task_ttl"), + ("process", "0046_workerrun_ttl"), + ] + + operations = [ + migrations.RunPython( + clear_unexpected_finish_dates, + reverse_code=migrations.RunPython.noop, + elidable=True, + ), + migrations.AddConstraint( + model_name="task", + constraint=models.CheckConstraint( + check=models.Q(finished=None) | models.Q(state__in=FINAL_STATES), + name="task_finished_requires_final_state", + violation_error_message="Only tasks in a final state can have a finish date set.", + ), + ), + ] diff --git a/arkindex/ponos/models.py b/arkindex/ponos/models.py index c9d3e109f8..e77e10df10 100644 --- a/arkindex/ponos/models.py +++ b/arkindex/ponos/models.py @@ -394,6 +394,11 @@ class Task(models.Model): name="task_finished_after_started", violation_error_message="The task finish date must not be earlier than the task start date.", ), + models.CheckConstraint( + check=Q(finished=None) | Q(state__in=FINAL_STATES), + name="task_finished_requires_final_state", + violation_error_message="Only tasks in a final state can have a finish date set.", + ), ] def __str__(self) -> str: diff --git a/arkindex/ponos/tests/tasks/test_partial_update.py b/arkindex/ponos/tests/tasks/test_partial_update.py index e43cc217ae..f4c8adbe30 100644 --- a/arkindex/ponos/tests/tasks/test_partial_update.py +++ b/arkindex/ponos/tests/tasks/test_partial_update.py @@ -83,6 +83,7 @@ class TestTaskPartialUpdate(FixtureAPITestCase): for (state_from, state_to) in self.docker_task_transitions: with self.subTest(state_from=state_from, state_to=state_to): self.task1.state = state_from + self.task1.finished = None self.task1.save() resp = self.client.patch( reverse("api:task-details", args=[self.task1.id]), @@ -114,6 +115,7 @@ class TestTaskPartialUpdate(FixtureAPITestCase): for (state_from, state_to) in self.slurm_task_transitions: with self.subTest(state_from=state_from, state_to=state_to): self.task1.state = state_from + self.task1.finished = None self.task1.save() resp = self.client.patch( reverse("api:task-details", args=[self.task1.id]), diff --git a/arkindex/ponos/tests/tasks/test_update.py b/arkindex/ponos/tests/tasks/test_update.py index 97029d9324..762a7fd31c 100644 --- a/arkindex/ponos/tests/tasks/test_update.py +++ b/arkindex/ponos/tests/tasks/test_update.py @@ -501,6 +501,7 @@ class TestTaskUpdate(FixtureAPITestCase): for (state_from, state_to) in self.docker_task_transitions: with self.subTest(state_from=state_from, state_to=state_to): self.task1.state = state_from + self.task1.finished = None self.task1.save() resp = self.client.put( reverse("api:task-details", args=[self.task1.id]), @@ -532,6 +533,7 @@ class TestTaskUpdate(FixtureAPITestCase): for (state_from, state_to) in self.slurm_task_transitions: with self.subTest(state_from=state_from, state_to=state_to): self.task1.state = state_from + self.task1.finished = None self.task1.save() resp = self.client.put( reverse("api:task-details", args=[self.task1.id]), -- GitLab