From 838b723c73c8b316e1f970069a19ef5928016d8c Mon Sep 17 00:00:00 2001 From: chris meyers Date: Wed, 28 Mar 2018 09:10:39 -0400 Subject: [PATCH] add all instances to special tower instance group * All instances except isolated instances * Also, prevent any tower attributes from being modified via the API --- awx/api/permissions.py | 5 +--- awx/main/managers.py | 3 +++ awx/main/models/ha.py | 4 +++ awx/main/tasks.py | 8 ++++-- .../functional/api/test_instance_group.py | 18 +++++++++---- .../task_management/test_rampart_groups.py | 3 ++- awx/main/tests/functional/test_instances.py | 27 +++++++++++++++++++ 7 files changed, 56 insertions(+), 12 deletions(-) diff --git a/awx/api/permissions.py b/awx/api/permissions.py index 3224b555e9..6c7fee85bf 100644 --- a/awx/api/permissions.py +++ b/awx/api/permissions.py @@ -231,10 +231,7 @@ class IsSuperUser(permissions.BasePermission): class InstanceGroupTowerPermission(ModelAccessPermission): def has_object_permission(self, request, view, obj): - if request.method == 'DELETE' and obj.name == "tower": - return False - if request.method in ['PATCH', 'PUT'] and obj.name == 'tower' and \ - request and request.data and request.data.get('name', '') != 'tower': + if request.method not in permissions.SAFE_METHODS and obj.name == "tower": return False return super(InstanceGroupTowerPermission, self).has_object_permission(request, view, obj) diff --git a/awx/main/managers.py b/awx/main/managers.py index 8748a71c5b..ce5a3a1971 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -113,6 +113,9 @@ class InstanceManager(models.Manager): # NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing return "tower" + def all_non_isolated(self): + return self.filter(rampart_groups__controller__isnull=True).distinct() + class InstanceGroupManager(models.Manager): """A custom manager class for the Instance model. diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py index 3356dc3a5d..181b3f768b 100644 --- a/awx/main/models/ha.py +++ b/awx/main/models/ha.py @@ -159,6 +159,10 @@ class InstanceGroup(models.Model, RelatedJobsMixin): def _get_related_jobs(self): return UnifiedJob.objects.filter(instance_group=self) + def add_all_non_iso_instances(self): + self.instances = Instance.objects.all_non_isolated() + self.save() + class Meta: app_label = 'main' diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 2c5c1afa7e..3aeaadd786 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -143,9 +143,13 @@ def apply_cluster_membership_policies(self): actual_instances = [] Group = namedtuple('Group', ['obj', 'instances']) Node = namedtuple('Instance', ['obj', 'groups']) + + # Add every instnace to the special 'tower' group + InstanceGroup.objects.get(name='tower').add_all_non_iso_instances() + # Process policy instance list first, these will represent manually managed instances # that will not go through automatic policy determination - for ig in InstanceGroup.objects.all(): + for ig in InstanceGroup.objects.exclude(name='tower'): logger.info(six.text_type("Considering group {}").format(ig.name)) ig.instances.clear() group_actual = Group(obj=ig, instances=[]) @@ -312,7 +316,7 @@ def purge_old_stdout_files(self): def cluster_node_heartbeat(self): logger.debug("Cluster node heartbeat task.") nowtime = now() - instance_list = list(Instance.objects.filter(rampart_groups__controller__isnull=True).distinct()) + instance_list = list(Instance.objects.all_non_isolated()) this_inst = None lost_instances = [] diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index 038950927c..f7978ca8da 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -80,12 +80,20 @@ def test_delete_instance_group_jobs_running(delete, instance_group_jobs_running, @pytest.mark.django_db -def test_delete_tower_instance_group_prevented(delete, options, tower_instance_group, user): +def test_modify_delete_tower_instance_group_prevented(delete, options, tower_instance_group, user, patch, put): url = reverse("api:instance_group_detail", kwargs={'pk': tower_instance_group.pk}) super_user = user('bob', True) + + # DELETE tower group not allowed delete(url, None, super_user, expect=403) + + # OPTIONS should just be "GET" resp = options(url, None, super_user, expect=200) - actions = ['GET', 'PUT',] - assert 'DELETE' not in resp.data['actions'] - for action in actions: - assert action in resp.data['actions'] + assert len(resp.data['actions'].keys()) == 1 + assert 'GET' in resp.data['actions'] + + # Updating tower group fields not allowed + patch(url, {'name': 'foobar'}, super_user, expect=403) + patch(url, {'policy_instance_percentage': 40}, super_user, expect=403) + put(url, {'name': 'foobar'}, super_user, expect=403) + diff --git a/awx/main/tests/functional/task_management/test_rampart_groups.py b/awx/main/tests/functional/task_management/test_rampart_groups.py index 9b4b3eac44..e71afa09db 100644 --- a/awx/main/tests/functional/task_management/test_rampart_groups.py +++ b/awx/main/tests/functional/task_management/test_rampart_groups.py @@ -162,6 +162,7 @@ def test_instance_group_basic_policies(instance_factory, instance_group_factory) i2 = instance_factory("i2") i3 = instance_factory("i3") i4 = instance_factory("i4") + instance_group_factory("tower") ig0 = instance_group_factory("ig0") ig1 = instance_group_factory("ig1", minimum=2) ig2 = instance_group_factory("ig2", percentage=50) @@ -174,7 +175,7 @@ def test_instance_group_basic_policies(instance_factory, instance_group_factory) ig2 = InstanceGroup.objects.get(id=ig2.id) ig3 = InstanceGroup.objects.get(id=ig3.id) assert len(ig0.instances.all()) == 1 - assert i0 in ig0.instances.all() + assert i0 in ig0.instances.all() assert len(InstanceGroup.objects.get(id=ig1.id).instances.all()) == 2 assert i1 in ig1.instances.all() assert i2 in ig1.instances.all() diff --git a/awx/main/tests/functional/test_instances.py b/awx/main/tests/functional/test_instances.py index 11484dfc6e..e2d02a5df4 100644 --- a/awx/main/tests/functional/test_instances.py +++ b/awx/main/tests/functional/test_instances.py @@ -31,15 +31,32 @@ def test_instance_dup(org_admin, organization, project, instance_factory, instan assert api_num_instances_oa == (actual_num_instances - 1) +@pytest.mark.django_db +@mock.patch('awx.main.tasks.handle_ha_toplogy_changes', return_value=None) +def test_policy_instance_tower_group(mock, instance_factory, instance_group_factory): + instance_factory("i1") + instance_factory("i2") + instance_factory("i3") + ig_t = instance_group_factory("tower") + instance_group_factory("ig1", percentage=25) + instance_group_factory("ig2", percentage=25) + instance_group_factory("ig3", percentage=25) + instance_group_factory("ig4", percentage=25) + apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 3 + + @pytest.mark.django_db @mock.patch('awx.main.tasks.handle_ha_toplogy_changes', return_value=None) def test_policy_instance_few_instances(mock, instance_factory, instance_group_factory): i1 = instance_factory("i1") + ig_t = instance_group_factory("tower") ig_1 = instance_group_factory("ig1", percentage=25) ig_2 = instance_group_factory("ig2", percentage=25) ig_3 = instance_group_factory("ig3", percentage=25) ig_4 = instance_group_factory("ig4", percentage=25) apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 1 assert len(ig_1.instances.all()) == 1 assert i1 in ig_1.instances.all() assert len(ig_2.instances.all()) == 1 @@ -50,6 +67,7 @@ def test_policy_instance_few_instances(mock, instance_factory, instance_group_fa assert i1 in ig_4.instances.all() i2 = instance_factory("i2") apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 2 assert len(ig_1.instances.all()) == 1 assert i1 in ig_1.instances.all() assert len(ig_2.instances.all()) == 1 @@ -66,11 +84,13 @@ def test_policy_instance_distribution_uneven(mock, instance_factory, instance_gr i1 = instance_factory("i1") i2 = instance_factory("i2") i3 = instance_factory("i3") + ig_t = instance_group_factory("tower") ig_1 = instance_group_factory("ig1", percentage=25) ig_2 = instance_group_factory("ig2", percentage=25) ig_3 = instance_group_factory("ig3", percentage=25) ig_4 = instance_group_factory("ig4", percentage=25) apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 3 assert len(ig_1.instances.all()) == 1 assert i1 in ig_1.instances.all() assert len(ig_2.instances.all()) == 1 @@ -88,11 +108,13 @@ def test_policy_instance_distribution_even(mock, instance_factory, instance_grou i2 = instance_factory("i2") i3 = instance_factory("i3") i4 = instance_factory("i4") + ig_t = instance_group_factory("tower") ig_1 = instance_group_factory("ig1", percentage=25) ig_2 = instance_group_factory("ig2", percentage=25) ig_3 = instance_group_factory("ig3", percentage=25) ig_4 = instance_group_factory("ig4", percentage=25) apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 4 assert len(ig_1.instances.all()) == 1 assert i1 in ig_1.instances.all() assert len(ig_2.instances.all()) == 1 @@ -104,6 +126,7 @@ def test_policy_instance_distribution_even(mock, instance_factory, instance_grou ig_1.policy_instance_minimum = 2 ig_1.save() apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 4 assert len(ig_1.instances.all()) == 2 assert i1 in ig_1.instances.all() assert i2 in ig_1.instances.all() @@ -122,11 +145,13 @@ def test_policy_instance_distribution_simultaneous(mock, instance_factory, insta i2 = instance_factory("i2") i3 = instance_factory("i3") i4 = instance_factory("i4") + ig_t = instance_group_factory("tower") ig_1 = instance_group_factory("ig1", percentage=25, minimum=2) ig_2 = instance_group_factory("ig2", percentage=25) ig_3 = instance_group_factory("ig3", percentage=25) ig_4 = instance_group_factory("ig4", percentage=25) apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 4 assert len(ig_1.instances.all()) == 2 assert i1 in ig_1.instances.all() assert i2 in ig_1.instances.all() @@ -143,11 +168,13 @@ def test_policy_instance_distribution_simultaneous(mock, instance_factory, insta def test_policy_instance_list_manually_managed(mock, instance_factory, instance_group_factory): i1 = instance_factory("i1") i2 = instance_factory("i2") + ig_t = instance_group_factory("tower") ig_1 = instance_group_factory("ig1", percentage=100, minimum=2) ig_2 = instance_group_factory("ig2") ig_2.policy_instance_list = [i2.hostname] ig_2.save() apply_cluster_membership_policies() + assert len(ig_t.instances.all()) == 2 assert len(ig_1.instances.all()) == 1 assert i1 in ig_1.instances.all() assert i2 not in ig_1.instances.all()