From aeca21ab5b97305cce2126bbd3a4c778171e0bdb Mon Sep 17 00:00:00 2001 From: chris meyers Date: Fri, 6 Jul 2018 10:58:33 -0400 Subject: [PATCH 1/3] deny topology changes to iso instances via api --- awx/api/views.py | 5 +++++ awx/main/models/ha.py | 2 ++ .../functional/api/test_instance_group.py | 21 ++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/awx/api/views.py b/awx/api/views.py index bc6a785002..efb463b0c9 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -189,6 +189,11 @@ class InstanceGroupMembershipMixin(object): ig_obj.save() return response + def is_valid_relation(self, parent, sub, created=False): + if sub.is_isolated(): + return {'error': _('Isolated instances may not be added or removed from instances groups via the API.')} + return None + def unattach(self, request, *args, **kwargs): response = super(InstanceGroupMembershipMixin, self).unattach(request, *args, **kwargs) sub_id, res = self.attach_validate(request) diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py index 6386857f08..e95378bb93 100644 --- a/awx/main/models/ha.py +++ b/awx/main/models/ha.py @@ -120,6 +120,8 @@ class Instance(BaseModel): def is_controller(self): return Instance.objects.filter(rampart_groups__controller__instances=self).exists() + def is_isolated(self): + return self.rampart_groups.filter(controller__isnull=False).exists() def refresh_capacity(self): cpu = get_cpu_capacity() diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index bac1c9a806..4d91a888e9 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -2,6 +2,7 @@ import pytest from awx.api.versioning import reverse from awx.main.models import ( + Instance, InstanceGroup, ProjectUpdate, ) @@ -14,6 +15,12 @@ def tower_instance_group(): return ig +@pytest.fixture +def instance(): + instance = Instance.objects.create(hostname='iso') + return instance + + @pytest.fixture def instance_group(job_factory): ig = InstanceGroup(name="east") @@ -22,9 +29,11 @@ def instance_group(job_factory): @pytest.fixture -def isolated_instance_group(instance_group): +def isolated_instance_group(instance_group, instance): ig = InstanceGroup(name="iso", controller=instance_group) ig.save() + ig.instances.set([instance]) + ig.save() return ig @@ -113,3 +122,13 @@ def test_prevent_delete_iso_and_control_groups(delete, isolated_instance_group, controller_url = reverse("api:instance_group_detail", kwargs={'pk': isolated_instance_group.controller.pk}) delete(iso_url, None, admin, expect=403) delete(controller_url, None, admin, expect=403) + + +@pytest.mark.django_db +def test_prevent_isolated_instance_in_non_isolated_instance_group(post, admin, instance, instance_group, isolated_instance_group): + url = reverse("api:instance_group_instance_list", kwargs={'pk': instance_group.pk}) + + assert True is instance.is_isolated() + resp = post(url, {'associate': True, 'id': instance.id}, admin, expect=400) + assert u"Isolated instances may not be added or removed from instances groups via the API." == resp.data['error'] + From 2ffc4b256d17ded81cd3fa93f69f639c3def5baa Mon Sep 17 00:00:00 2001 From: chris meyers Date: Fri, 6 Jul 2018 15:12:57 -0400 Subject: [PATCH 2/3] fix copy paste error --- awx/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/api/views.py b/awx/api/views.py index efb463b0c9..f031eb8611 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -196,7 +196,7 @@ class InstanceGroupMembershipMixin(object): def unattach(self, request, *args, **kwargs): response = super(InstanceGroupMembershipMixin, self).unattach(request, *args, **kwargs) - sub_id, res = self.attach_validate(request) + sub_id, res = self.unattach_validate(request) if status.is_success(response.status_code): if self.parent_model is Instance: ig_obj = get_object_or_400(self.model, pk=sub_id) From 19e865e9a5e26e3917deae036bf481d8806af3c1 Mon Sep 17 00:00:00 2001 From: chris meyers Date: Fri, 6 Jul 2018 15:34:26 -0400 Subject: [PATCH 3/3] prevent remove iso instance from iso instance groups --- awx/api/views.py | 10 +++++++++- awx/main/tests/functional/api/test_instance_group.py | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index f031eb8611..9222b4dd57 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -194,9 +194,17 @@ class InstanceGroupMembershipMixin(object): return {'error': _('Isolated instances may not be added or removed from instances groups via the API.')} return None + def unattach_validate(self, request): + (sub_id, res) = super(InstanceGroupMembershipMixin, self).unattach_validate(request) + if res: + return res + sub = get_object_or_400(self.model, pk=sub_id) + attach_errors = self.is_valid_relation(None, sub) + if attach_errors: + return (sub_id, Response(attach_errors, status=status.HTTP_400_BAD_REQUEST)) + def unattach(self, request, *args, **kwargs): response = super(InstanceGroupMembershipMixin, self).unattach(request, *args, **kwargs) - sub_id, res = self.unattach_validate(request) if status.is_success(response.status_code): if self.parent_model is Instance: ig_obj = get_object_or_400(self.model, pk=sub_id) diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index 4d91a888e9..50c033b69b 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -125,10 +125,19 @@ def test_prevent_delete_iso_and_control_groups(delete, isolated_instance_group, @pytest.mark.django_db -def test_prevent_isolated_instance_in_non_isolated_instance_group(post, admin, instance, instance_group, isolated_instance_group): +def test_prevent_isolated_instance_added_to_non_isolated_instance_group(post, admin, instance, instance_group, isolated_instance_group): url = reverse("api:instance_group_instance_list", kwargs={'pk': instance_group.pk}) assert True is instance.is_isolated() resp = post(url, {'associate': True, 'id': instance.id}, admin, expect=400) assert u"Isolated instances may not be added or removed from instances groups via the API." == resp.data['error'] + +@pytest.mark.django_db +def test_prevent_isolated_instance_removal_from_isolated_instance_group(post, admin, instance, instance_group, isolated_instance_group): + url = reverse("api:instance_group_instance_list", kwargs={'pk': isolated_instance_group.pk}) + + assert True is instance.is_isolated() + resp = post(url, {'disassociate': True, 'id': instance.id}, admin, expect=400) + assert u"Isolated instances may not be added or removed from instances groups via the API." == resp.data['error'] +