diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 1f6cf2818d..7c353a9ba5 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -4883,6 +4883,15 @@ class InstanceGroupSerializer(BaseSerializer): read_only=True ) instances = serializers.SerializerMethodField() + is_controller = serializers.BooleanField( + help_text=_('Indicates whether instance group controls any other group'), + read_only=True + ) + is_isolated = serializers.BooleanField( + help_text=_('Indicates whether instances in this group are isolated.' + 'Isolated groups have a designated controller group.'), + read_only=True + ) # NOTE: help_text is duplicated from field definitions, no obvious way of # both defining field details here and also getting the field's help_text policy_instance_percentage = serializers.IntegerField( @@ -4908,7 +4917,7 @@ class InstanceGroupSerializer(BaseSerializer): fields = ("id", "type", "url", "related", "name", "created", "modified", "capacity", "committed_capacity", "consumed_capacity", "percent_capacity_remaining", "jobs_running", "jobs_total", - "instances", "controller", + "instances", "controller", "is_controller", "is_isolated", "policy_instance_percentage", "policy_instance_minimum", "policy_instance_list") def get_related(self, obj): diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py index 43e713b2bc..fbd52f2a1e 100644 --- a/awx/main/models/ha.py +++ b/awx/main/models/ha.py @@ -209,6 +209,14 @@ class InstanceGroup(HasPolicyEditsMixin, BaseModel, RelatedJobsMixin): def jobs_total(self): return UnifiedJob.objects.filter(instance_group=self).count() + @property + def is_controller(self): + return self.controlled_groups.exists() + + @property + def is_isolated(self): + return bool(self.controller) + ''' RelatedJobsMixin ''' diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index 77c3997bd8..a6ea786639 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -78,6 +78,26 @@ def instance_group_jobs_successful(instance_group, create_job_factory, create_pr return jobs_successful + project_updates_successful +@pytest.mark.django_db +def test_instance_group_is_controller(instance_group, isolated_instance_group, non_iso_instance): + assert not isolated_instance_group.is_controller + assert instance_group.is_controller + + instance_group.instances.set([non_iso_instance]) + + assert instance_group.is_controller + + +@pytest.mark.django_db +def test_instance_group_is_isolated(instance_group, isolated_instance_group): + assert not instance_group.is_isolated + assert isolated_instance_group.is_isolated + + isolated_instance_group.instances = [] + + assert isolated_instance_group.is_isolated + + @pytest.mark.django_db def test_delete_instance_group_jobs(delete, instance_group_jobs_successful, instance_group, admin): url = reverse("api:instance_group_detail", kwargs={'pk': instance_group.pk}) diff --git a/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js b/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js index 766f968442..25888a18f7 100644 --- a/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js +++ b/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js @@ -83,7 +83,7 @@ export default ['$scope', '$filter', '$state', 'Alert', 'resolvedModels', 'Datas vm.rowAction = { trash: instance_group => { - return vm.isSuperuser && instance_group.name !== 'tower'; + return vm.isSuperuser && instance_group.name !== 'tower' && !instance_group.is_controller && !instance_group.is_isolated; } };