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 0000000000000000000000000000000000000000..13dbe15627c24fa28c4abc8c337d8960c4c3fe18 --- /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 c9d3e109f8818d65ee4ed5e03059cf7f51c7a4b4..e77e10df10c44acead36174613d930f84b16ae54 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 e43cc217ae4d2690c4862846aa66ae0ed79842ed..f4c8adbe30a6260adb86d1c18ff89f9943af60e6 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 97029d9324ec065e5005327b9975a48cf7aabc22..762a7fd31cac76e51dfad964cc3aee684902708c 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]),