Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancel scheduled maintenance tasks when all their components are missing #3235

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/3229.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Active maintenance tasks that only reference deleted components will be automatically cancelled
20 changes: 16 additions & 4 deletions python/nav/maintengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -105,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():
"""
Expand Down
61 changes: 61 additions & 0 deletions tests/integration/maintengine_test.py
Original file line number Diff line number Diff line change
@@ -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
Loading