From e746d2ad50f30d550cc449d18e861a301d21e360 Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Thu, 21 Nov 2024 14:06:12 +0000 Subject: [PATCH 1/4] Fix typo/redundancy in docstring --- python/nav/maintengine.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/python/nav/maintengine.py b/python/nav/maintengine.py index 55204da143..606019e1e1 100644 --- a/python/nav/maintengine.py +++ b/python/nav/maintengine.py @@ -86,10 +86,8 @@ def check_tasks_without_end(): @transaction.atomic() def do_state_transitions(): - """ - Finds active or scheduled tasks that have run out and sets them as passed, - and finds scheduled scheduled tasks in the current window and sets them - as active. + """Finds active or scheduled tasks that have run out and sets them as passed, + and finds scheduled tasks in the current window and sets them as active. """ tasks = MaintenanceTask.objects.past().filter( state__in=[MaintenanceTask.STATE_ACTIVE, MaintenanceTask.STATE_SCHEDULED] From bd30b6638d1df1ad73caaac0af7525fbe8cf8e5e Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Thu, 21 Nov 2024 14:43:31 +0000 Subject: [PATCH 2/4] Cancel empty maintenance tasks During task state transition updates, if there are active maintenance tasks that don't reference any non-deleted components, take care to cancel it. It only makes sense to cancel active tasks. Scheduled tasks could still be in the process of being edited by an admin, and we don't want to alter them under the users' nose. --- python/nav/maintengine.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/python/nav/maintengine.py b/python/nav/maintengine.py index 606019e1e1..5176d043c7 100644 --- a/python/nav/maintengine.py +++ b/python/nav/maintengine.py @@ -103,6 +103,20 @@ def do_state_transitions(): _logger.debug("Tasks transitioned to active state: %r", tasks) + cancel_tasks_without_components() + + +def cancel_tasks_without_components(): + """Cancels active tasks where all components are missing""" + tasks = MaintenanceTask.objects.filter( + state=MaintenanceTask.STATE_ACTIVE + ).prefetch_related('maintenance_components') + for task in tasks: + if not any(task.get_components()): + task.state = MaintenanceTask.STATE_CANCELED + task.save() + _logger.debug("Task %r canceled because all components were missing", task) + def check_state_differences(): """ From 5cddd5deec499aeba17d5aa5bfb9e6375b2a258a Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Thu, 21 Nov 2024 14:43:53 +0000 Subject: [PATCH 3/4] Test empty task cancellations in maintengine --- tests/integration/maintengine_test.py | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/integration/maintengine_test.py diff --git a/tests/integration/maintengine_test.py b/tests/integration/maintengine_test.py new file mode 100644 index 0000000000..ba6cbcf789 --- /dev/null +++ b/tests/integration/maintengine_test.py @@ -0,0 +1,61 @@ +from datetime import datetime, timedelta + +import pytest + +from nav import maintengine +from nav.models.msgmaint import MaintenanceTask, MaintenanceComponent + + +class TestCancelTasksWithoutComponents: + def test_it_should_cancel_active_empty_tasks(self, empty_task): + assert empty_task.state == MaintenanceTask.STATE_ACTIVE + maintengine.cancel_tasks_without_components() + empty_task.refresh_from_db() + assert empty_task.state == MaintenanceTask.STATE_CANCELED + + def test_it_should_not_cancel_scheduled_empty_tasks(self, scheduled_empty_task): + assert scheduled_empty_task.state == MaintenanceTask.STATE_SCHEDULED + maintengine.cancel_tasks_without_components() + scheduled_empty_task.refresh_from_db() + assert scheduled_empty_task.state == MaintenanceTask.STATE_SCHEDULED + + def test_it_should_not_cancel_nonempty_tasks(self, half_empty_task): + assert half_empty_task.state == MaintenanceTask.STATE_ACTIVE + maintengine.cancel_tasks_without_components() + half_empty_task.refresh_from_db() + assert half_empty_task.state == MaintenanceTask.STATE_ACTIVE + + +@pytest.fixture +def empty_task(db): + task = MaintenanceTask( + start_time=datetime.now() - timedelta(minutes=30), + end_time=datetime.now() + timedelta(minutes=30), + description="Test task", + state=MaintenanceTask.STATE_ACTIVE, + ) + task.save() + component = MaintenanceComponent( + maintenance_task=task, + key="netbox", + value=99999, + ) + component.save() + + yield task + + +@pytest.fixture +def scheduled_empty_task(empty_task): + empty_task.state = MaintenanceTask.STATE_SCHEDULED + empty_task.start_time = datetime.now() + timedelta(minutes=30) + empty_task.end_time = datetime.now() + timedelta(minutes=60) + empty_task.save() + yield empty_task + + +@pytest.fixture +def half_empty_task(empty_task, localhost): + component = MaintenanceComponent(maintenance_task=empty_task, component=localhost) + component.save() + yield empty_task From fa8994bf6da298ab18a58c1088ace53b5fb87c9e Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Thu, 21 Nov 2024 14:45:41 +0000 Subject: [PATCH 4/4] Add news fragment --- changelog.d/3229.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3229.added.md diff --git a/changelog.d/3229.added.md b/changelog.d/3229.added.md new file mode 100644 index 0000000000..3c65d655f1 --- /dev/null +++ b/changelog.d/3229.added.md @@ -0,0 +1 @@ +Active maintenance tasks that only reference deleted components will be automatically cancelled