From b501b30db4f67ec6be15e402b54210aff8078735 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 15 Sep 2022 14:09:01 -0400 Subject: [PATCH] Changing label functions to account for new relationships Removing unreferenced get_orphaned_labels Forcing forks and job_slice_count to be >=0 --- awx/api/serializers.py | 8 +- awx/main/models/label.py | 28 +++-- awx/main/tests/unit/models/test_label.py | 139 +++++++++++++++++------ 3 files changed, 127 insertions(+), 48 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index cd3ef7a63a..ce0d7ce4ca 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3633,8 +3633,8 @@ class LaunchConfigurationBaseSerializer(BaseSerializer): skip_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None) diff_mode = serializers.BooleanField(required=False, allow_null=True, default=None) verbosity = serializers.ChoiceField(allow_null=True, required=False, default=None, choices=VERBOSITY_CHOICES) - forks = serializers.IntegerField(required=False, allow_null=True, default=None) - job_slice_count = serializers.IntegerField(required=False, allow_null=True, default=None) + forks = serializers.IntegerField(required=False, allow_null=True, min_value=0, default=None) + job_slice_count = serializers.IntegerField(required=False, allow_null=True, min_value=0, default=None) timeout = serializers.IntegerField(required=False, allow_null=True, default=None) exclude_errors = () @@ -4141,8 +4141,8 @@ class JobLaunchSerializer(BaseSerializer): verbosity = serializers.ChoiceField(required=False, choices=VERBOSITY_CHOICES, write_only=True) execution_environment = serializers.PrimaryKeyRelatedField(queryset=ExecutionEnvironment.objects.all(), required=False) labels = serializers.PrimaryKeyRelatedField(many=True, queryset=Label.objects.all(), required=False) - forks = serializers.IntegerField(required=False, write_only=True, default=1) - job_slice_count = serializers.IntegerField(required=False, write_only=True, default=0) + forks = serializers.IntegerField(required=False, write_only=True, min_value=0, default=1) + job_slice_count = serializers.IntegerField(required=False, write_only=True, min_value=0, default=0) timeout = serializers.IntegerField(required=False, write_only=True, default=0) instance_groups = serializers.PrimaryKeyRelatedField(many=True, queryset=InstanceGroup.objects.all(), required=False) diff --git a/awx/main/models/label.py b/awx/main/models/label.py index 7ca92d4ff2..b66db2a590 100644 --- a/awx/main/models/label.py +++ b/awx/main/models/label.py @@ -10,6 +10,8 @@ from awx.api.versioning import reverse from awx.main.models.base import CommonModelNameNotUnique from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob from awx.main.models.inventory import Inventory +from awx.main.models.schedules import Schedule +from awx.main.models.workflow import WorkflowJobTemplateNode, WorkflowJobNode __all__ = ('Label',) @@ -34,16 +36,22 @@ class Label(CommonModelNameNotUnique): def get_absolute_url(self, request=None): return reverse('api:label_detail', kwargs={'pk': self.pk}, request=request) - @staticmethod - def get_orphaned_labels(): - return Label.objects.filter(organization=None, unifiedjobtemplate_labels__isnull=True, inventory_labels__isnull=True) - def is_detached(self): - return Label.objects.filter(id=self.id, unifiedjob_labels__isnull=True, unifiedjobtemplate_labels__isnull=True, inventory_labels__isnull=True).exists() + return Label.objects.filter( + id=self.id, + unifiedjob_labels__isnull=True, + unifiedjobtemplate_labels__isnull=True, + inventory_labels__isnull=True, + schedule_labels__isnull=True, + workflowjobtemplatenode_labels__isnull=True, + workflowjobnode_labels=True, + ).exists() def is_candidate_for_detach(self): - - c1 = UnifiedJob.objects.filter(labels__in=[self.id]).count() - c2 = UnifiedJobTemplate.objects.filter(labels__in=[self.id]).count() - c3 = Inventory.objects.filter(labels__in=[self.id]).count() - return (c1 + c2 + c3 - 1) == 0 + count = UnifiedJob.objects.filter(labels__in=[self.id]).count() # Both Jobs and WFJobs + count += UnifiedJobTemplate.objects.filter(labels__in=[self.id]).count() # Both JTs and WFJT + count += Inventory.objects.filter(labels__in=[self.id]).count() + count += Schedule.objects.filter(labels__in=[self.id]).count() + count += WorkflowJobTemplateNode.objects.filter(labels__in=[self.id]).count() + count += WorkflowJobNode.objects.filter(labels__in=[self.id]).count() + return (count - 1) == 0 diff --git a/awx/main/tests/unit/models/test_label.py b/awx/main/tests/unit/models/test_label.py index 0d5b5b76c0..43e890e0f5 100644 --- a/awx/main/tests/unit/models/test_label.py +++ b/awx/main/tests/unit/models/test_label.py @@ -1,9 +1,15 @@ import pytest from unittest import mock -from awx.main.models.label import Label -from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob -from awx.main.models.inventory import Inventory +from awx.main.models import ( + Label, + UnifiedJobTemplate, + UnifiedJob, + Inventory, + Schedule, + WorkflowJobTemplateNode, + WorkflowJobNode, +) mock_query_set = mock.MagicMock() @@ -14,12 +20,6 @@ mock_objects = mock.MagicMock(filter=mock.MagicMock(return_value=mock_query_set) @pytest.mark.django_db @mock.patch('awx.main.models.label.Label.objects', mock_objects) class TestLabelFilterMocked: - def test_get_orphaned_labels(self, mocker): - ret = Label.get_orphaned_labels() - - assert mock_query_set == ret - Label.objects.filter.assert_called_with(organization=None, unifiedjobtemplate_labels__isnull=True, inventory_labels__isnull=True) - def test_is_detached(self, mocker): mock_query_set.exists.return_value = True @@ -27,7 +27,15 @@ class TestLabelFilterMocked: ret = label.is_detached() assert ret is True - Label.objects.filter.assert_called_with(id=37, unifiedjob_labels__isnull=True, unifiedjobtemplate_labels__isnull=True, inventory_labels__isnull=True) + Label.objects.filter.assert_called_with( + id=37, + unifiedjob_labels__isnull=True, + unifiedjobtemplate_labels__isnull=True, + inventory_labels__isnull=True, + schedule_labels__isnull=True, + workflowjobtemplatenode_labels__isnull=True, + workflowjobnode_labels=True, + ) mock_query_set.exists.assert_called_with() def test_is_detached_not(self, mocker): @@ -37,39 +45,102 @@ class TestLabelFilterMocked: ret = label.is_detached() assert ret is False - Label.objects.filter.assert_called_with(id=37, unifiedjob_labels__isnull=True, unifiedjobtemplate_labels__isnull=True, inventory_labels__isnull=True) + Label.objects.filter.assert_called_with( + id=37, + unifiedjob_labels__isnull=True, + unifiedjobtemplate_labels__isnull=True, + inventory_labels__isnull=True, + schedule_labels__isnull=True, + workflowjobtemplatenode_labels__isnull=True, + workflowjobnode_labels=True, + ) + mock_query_set.exists.assert_called_with() @pytest.mark.parametrize( - "jt_count,j_count,inv_count,expected", + "jt_count,j_count,inv_count,sched_count,wfnode_count,wfnodej_count,expected", [ - (1, 0, 0, True), - (0, 1, 0, True), - (0, 0, 1, True), - (1, 1, 1, False), + (1, 0, 0, 0, 0, 0, True), + (0, 1, 0, 0, 0, 0, True), + (1, 1, 0, 0, 0, 0, False), + (0, 0, 1, 0, 0, 0, True), + (1, 0, 1, 0, 0, 0, False), + (0, 1, 1, 0, 0, 0, False), + (1, 1, 1, 0, 0, 0, False), + (0, 0, 0, 1, 0, 0, True), + (1, 0, 0, 1, 0, 0, False), + (0, 1, 0, 1, 0, 0, False), + (1, 1, 0, 1, 0, 0, False), + (0, 0, 1, 1, 0, 0, False), + (1, 0, 1, 1, 0, 0, False), + (0, 1, 1, 1, 0, 0, False), + (1, 1, 1, 1, 0, 0, False), + (0, 0, 0, 0, 1, 0, True), + (1, 0, 0, 0, 1, 0, False), + (0, 1, 0, 0, 1, 0, False), + (1, 1, 0, 0, 1, 0, False), + (0, 0, 1, 0, 1, 0, False), + (1, 0, 1, 0, 1, 0, False), + (0, 1, 1, 0, 1, 0, False), + (1, 1, 1, 0, 1, 0, False), + (0, 0, 0, 1, 1, 0, False), + (1, 0, 0, 1, 1, 0, False), + (0, 1, 0, 1, 1, 0, False), + (1, 1, 0, 1, 1, 0, False), + (0, 0, 1, 1, 1, 0, False), + (1, 0, 1, 1, 1, 0, False), + (0, 1, 1, 1, 1, 0, False), + (1, 1, 1, 1, 1, 0, False), + (0, 0, 0, 0, 0, 1, True), + (1, 0, 0, 0, 0, 1, False), + (0, 1, 0, 0, 0, 1, False), + (1, 1, 0, 0, 0, 1, False), + (0, 0, 1, 0, 0, 1, False), + (1, 0, 1, 0, 0, 1, False), + (0, 1, 1, 0, 0, 1, False), + (1, 1, 1, 0, 0, 1, False), + (0, 0, 0, 1, 0, 1, False), + (1, 0, 0, 1, 0, 1, False), + (0, 1, 0, 1, 0, 1, False), + (1, 1, 0, 1, 0, 1, False), + (0, 0, 1, 1, 0, 1, False), + (1, 0, 1, 1, 0, 1, False), + (0, 1, 1, 1, 0, 1, False), + (1, 1, 1, 1, 0, 1, False), + (0, 0, 0, 0, 1, 1, False), + (1, 0, 0, 0, 1, 1, False), + (0, 1, 0, 0, 1, 1, False), + (1, 1, 0, 0, 1, 1, False), + (0, 0, 1, 0, 1, 1, False), + (1, 0, 1, 0, 1, 1, False), + (0, 1, 1, 0, 1, 1, False), + (1, 1, 1, 0, 1, 1, False), + (0, 0, 0, 1, 1, 1, False), + (1, 0, 0, 1, 1, 1, False), + (0, 1, 0, 1, 1, 1, False), + (1, 1, 0, 1, 1, 1, False), + (0, 0, 1, 1, 1, 1, False), + (1, 0, 1, 1, 1, 1, False), + (0, 1, 1, 1, 1, 1, False), + (1, 1, 1, 1, 1, 1, False), ], ) - def test_is_candidate_for_detach(self, mocker, jt_count, j_count, inv_count, expected): - mock_job_qs = mocker.MagicMock() - mock_job_qs.count = mocker.MagicMock(return_value=j_count) - mocker.patch.object(UnifiedJob, 'objects', mocker.MagicMock(filter=mocker.MagicMock(return_value=mock_job_qs))) - - mock_jt_qs = mocker.MagicMock() - mock_jt_qs.count = mocker.MagicMock(return_value=jt_count) - mocker.patch.object(UnifiedJobTemplate, 'objects', mocker.MagicMock(filter=mocker.MagicMock(return_value=mock_jt_qs))) - - mock_inv_qs = mocker.MagicMock() - mock_inv_qs.count = mocker.MagicMock(return_value=inv_count) - mocker.patch.object(Inventory, 'objects', mocker.MagicMock(filter=mocker.MagicMock(return_value=mock_inv_qs))) + def test_is_candidate_for_detach(self, mocker, jt_count, j_count, inv_count, sched_count, wfnode_count, wfnodej_count, expected): + counts = [jt_count, j_count, inv_count, sched_count, wfnode_count, wfnodej_count] + models = [UnifiedJobTemplate, UnifiedJob, Inventory, Schedule, WorkflowJobTemplateNode, WorkflowJobNode] + mockers = [] + for index in range(0, len(models)): + a_mocker = mocker.MagicMock() + a_mocker.count = mocker.MagicMock(return_value=counts[index]) + mocker.patch.object(models[index], 'objects', mocker.MagicMock(filter=mocker.MagicMock(return_value=a_mocker))) + mockers.append(a_mocker) label = Label(id=37) ret = label.is_candidate_for_detach() - UnifiedJob.objects.filter.assert_called_with(labels__in=[label.id]) - UnifiedJobTemplate.objects.filter.assert_called_with(labels__in=[label.id]) - Inventory.objects.filter.assert_called_with(labels__in=[label.id]) - mock_job_qs.count.assert_called_with() - mock_jt_qs.count.assert_called_with() - mock_inv_qs.count.assert_called_with() + for index in range(0, len(models)): + models[index].objects.filter.assert_called_with(labels__in=[label.id]) + for index in range(0, len(mockers)): + mockers[index].count.assert_called_with() assert ret is expected