From 68c14976dba0bf18b899f27654638615b8165ee5 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 5 Apr 2016 12:24:13 -0400 Subject: [PATCH] label cleanup periodic job --- .../migrations/0013_v300_label_changes.py | 21 +++++++++++++++++++ awx/main/models/label.py | 13 +++++++++++- awx/main/tasks.py | 8 +++++++ awx/main/tests/unit/models/test_label.py | 10 +++++++++ awx/main/tests/unit/settings/test_defaults.py | 18 ++++++++++++++++ awx/main/tests/unit/test_tasks.py | 12 +++++++++++ awx/settings/defaults.py | 4 ++++ 7 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 awx/main/migrations/0013_v300_label_changes.py create mode 100644 awx/main/tests/unit/models/test_label.py create mode 100644 awx/main/tests/unit/settings/test_defaults.py create mode 100644 awx/main/tests/unit/test_tasks.py diff --git a/awx/main/migrations/0013_v300_label_changes.py b/awx/main/migrations/0013_v300_label_changes.py new file mode 100644 index 0000000000..e8cfda14cd --- /dev/null +++ b/awx/main/migrations/0013_v300_label_changes.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0012_v300_create_labels'), + ] + + operations = [ + migrations.AlterField( + model_name='label', + name='organization', + field=models.ForeignKey(related_name='labels', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Organization', help_text='Organization this label belongs to.', null=True), + ), + ] diff --git a/awx/main/models/label.py b/awx/main/models/label.py index e4b1b1809c..d21de8622e 100644 --- a/awx/main/models/label.py +++ b/awx/main/models/label.py @@ -24,10 +24,21 @@ class Label(CommonModelNameNotUnique): organization = models.ForeignKey( 'Organization', related_name='labels', + blank=True, + null=True, + default=None, help_text=_('Organization this label belongs to.'), - on_delete=models.CASCADE, + on_delete=models.SET_NULL, ) def get_absolute_url(self): return reverse('api:label_detail', args=(self.pk,)) + @staticmethod + def get_orphaned_labels(): + return \ + Label.objects.filter( + organization=None, + jobtemplate_labels__isnull=True + ) + diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 0fbd322199..c9233683e7 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -46,6 +46,7 @@ from django.contrib.auth.models import User from awx.lib.metrics import task_timer from awx.main.constants import CLOUD_PROVIDERS from awx.main.models import * # noqa +from awx.main.models.label import Label from awx.main.queue import FifoQueue from awx.main.conf import tower_settings from awx.main.task_engine import TaskSerializer, TASK_TIMEOUT_INTERVAL @@ -109,6 +110,13 @@ def run_administrative_checks(self): tower_admin_emails, fail_silently=True) +@task(bind=True) +def run_label_cleanup(self): + qs = Label.get_orphaned_labels() + labels_count = qs.count() + qs.delete() + return labels_count + @task(bind=True) def tower_periodic_scheduler(self): def get_last_run(): diff --git a/awx/main/tests/unit/models/test_label.py b/awx/main/tests/unit/models/test_label.py new file mode 100644 index 0000000000..c5c6e50b91 --- /dev/null +++ b/awx/main/tests/unit/models/test_label.py @@ -0,0 +1,10 @@ +from awx.main.models.label import Label + +def test_get_orphaned_labels(mocker): + mock_query_set = mocker.MagicMock() + Label.objects.filter = mocker.MagicMock(return_value=mock_query_set) + + ret = Label.get_orphaned_labels() + + assert mock_query_set == ret + Label.objects.filter.assert_called_with(organization=None, jobtemplate_labels__isnull=True) diff --git a/awx/main/tests/unit/settings/test_defaults.py b/awx/main/tests/unit/settings/test_defaults.py new file mode 100644 index 0000000000..f59ddfb60b --- /dev/null +++ b/awx/main/tests/unit/settings/test_defaults.py @@ -0,0 +1,18 @@ +import pytest + +from django.conf import settings +from datetime import timedelta + +@pytest.mark.parametrize("job_name,function_path", [ + ('label_cleanup', 'awx.main.tasks.run_label_cleanup'), + ('admin_checks', 'awx.main.tasks.run_administrative_checks'), + ('tower_scheduler', 'awx.main.tasks.tower_periodic_scheduler'), +]) +def test_CELERYBEAT_SCHEDULE(mocker, job_name, function_path): + assert job_name in settings.CELERYBEAT_SCHEDULE + assert 'schedule' in settings.CELERYBEAT_SCHEDULE[job_name] + assert type(settings.CELERYBEAT_SCHEDULE[job_name]['schedule']) is timedelta + assert settings.CELERYBEAT_SCHEDULE[job_name]['task'] == function_path + + # Ensures that the function exists + mocker.patch(function_path) diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py new file mode 100644 index 0000000000..1cbd9fddf8 --- /dev/null +++ b/awx/main/tests/unit/test_tasks.py @@ -0,0 +1,12 @@ +from awx.main.tasks import run_label_cleanup + +def test_run_label_cleanup(mocker): + qs = mocker.Mock(**{'count.return_value': 3, 'delete.return_value': None}) + mock_label = mocker.patch('awx.main.models.label.Label.get_orphaned_labels',return_value=qs) + + ret = run_label_cleanup() + + mock_label.assert_called_with() + qs.delete.assert_called_with() + assert 3 == ret + diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index f663bab11b..d9cbbbf2b0 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -345,6 +345,10 @@ CELERYBEAT_SCHEDULE = { 'task': 'awx.main.tasks.run_administrative_checks', 'schedule': timedelta(days=30) }, + 'label_cleanup': { + 'task': 'awx.main.tasks.run_label_cleanup', + 'schedule': timedelta(days=7) + }, } # Social Auth configuration.