From 764d2ff94d1ae9c721b87801f717232caeaad7ee Mon Sep 17 00:00:00 2001 From: Chris Church Date: Sat, 27 Jul 2013 01:06:09 -0400 Subject: [PATCH] Implemented AC-22, hosts and child groups are migrated to a group's parent when the group is deleted or marked inactive. --- awx/main/signals.py | 44 +++++++++++++++++++++++++++++++- awx/main/tests/inventory.py | 50 ++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/awx/main/signals.py b/awx/main/signals.py index 92333875eb..a54c44ff91 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -8,7 +8,7 @@ import threading # Django from django.contrib.auth.models import User from django.db import DatabaseError -from django.db.models.signals import post_save, post_delete, m2m_changed +from django.db.models.signals import post_save, pre_delete, post_delete, m2m_changed from django.dispatch import receiver # Django-REST-Framework @@ -75,3 +75,45 @@ m2m_changed.connect(update_inventory_has_active_failures, sender=Group.hosts.thr m2m_changed.connect(update_inventory_has_active_failures, sender=Group.parents.through) post_save.connect(update_inventory_has_active_failures, sender=Job) post_delete.connect(update_inventory_has_active_failures, sender=Job) + +# Migrate hosts, groups to parent group(s) whenever a group is deleted or +# marked as inactive. + +@receiver(pre_delete, sender=Group) +def save_related_pks_before_group_delete(sender, **kwargs): + instance = kwargs['instance'] + instance._saved_parents_pks = set(instance.parents.values_list('pk', flat=True)) + instance._saved_hosts_pks = set(instance.hosts.values_list('pk', flat=True)) + instance._saved_children_pks = set(instance.children.values_list('pk', flat=True)) + +@receiver(post_delete, sender=Group) +def migrate_children_from_deleted_group_to_parent_groups(sender, **kwargs): + instance = kwargs['instance'] + parents_pks = getattr(instance, '_saved_parents_pks', []) + hosts_pks = getattr(instance, '_saved_hosts_pks', []) + children_pks = getattr(instance, '_saved_children_pks', []) + for parent_group in Group.objects.filter(pk__in=parents_pks): + for child_host in Host.objects.filter(pk__in=hosts_pks): + logger.debug('adding host %s to parent %s after group deletion', + child_host, parent_group) + parent_group.hosts.add(child_host) + for child_group in Group.objects.filter(pk__in=children_pks): + logger.debug('adding group %s to parent %s after group deletion', + child_group, parent_group) + parent_group.children.add(child_group) + +@receiver(post_save, sender=Group) +def migrate_children_from_inactive_group_to_parent_groups(sender, **kwargs): + instance = kwargs['instance'] + if instance.active: + return + for parent_group in instance.parents.all(): + for child_host in instance.hosts.all(): + logger.debug('moving host %s to parent %s after making group %s inactive', + child_host, parent_group, instance) + parent_group.hosts.add(child_host) + for child_group in instance.children.all(): + logger.debug('moving group %s to parent %s after making group %s inactive', + child_group, parent_group, instance) + parent_group.children.add(child_group) + parent_group.children.remove(instance) diff --git a/awx/main/tests/inventory.py b/awx/main/tests/inventory.py index 00dce0053f..3151c73bb1 100644 --- a/awx/main/tests/inventory.py +++ b/awx/main/tests/inventory.py @@ -585,7 +585,55 @@ class InventoryTest(BaseTest): # and these work # on a group resource, I can see related resources for variables, inventories, and children # and these work - + + def test_migrate_children_when_group_removed(self): + # Group A is parent of B, B is parent of C, C is parent of D. + g_a = self.inventory_a.groups.create(name='A') + g_b = self.inventory_a.groups.create(name='B') + g_b.parents.add(g_a) + g_c = self.inventory_a.groups.create(name='C') + g_c.parents.add(g_b) + g_d = self.inventory_a.groups.create(name='D') + g_d.parents.add(g_c) + # Each group "X" contains one host "x". + h_a = self.inventory_a.hosts.create(name='a') + h_a.groups.add(g_a) + h_b = self.inventory_a.hosts.create(name='b') + h_b.groups.add(g_b) + h_c = self.inventory_a.hosts.create(name='c') + h_c.groups.add(g_c) + h_d = self.inventory_a.hosts.create(name='d') + h_d.groups.add(g_d) + + # Verify that grand-child groups/hosts are not direct children of the + # parent groups. + self.assertFalse(g_c in g_a.children.all()) + self.assertFalse(g_d in g_a.children.all()) + self.assertFalse(g_d in g_b.children.all()) + self.assertFalse(h_b in g_a.hosts.all()) + self.assertFalse(h_c in g_a.hosts.all()) + self.assertFalse(h_c in g_b.hosts.all()) + self.assertFalse(h_d in g_a.hosts.all()) + self.assertFalse(h_d in g_b.hosts.all()) + self.assertFalse(h_d in g_c.hosts.all()) + + # Delete group B. Its child groups and hosts should now be attached to + # group A. Group C and D hosts and child groups should be unchanged. + g_b.delete() + self.assertTrue(g_c in g_a.children.all()) + self.assertTrue(h_b in g_a.hosts.all()) + self.assertFalse(g_d in g_a.children.all()) + self.assertFalse(h_c in g_a.hosts.all()) + self.assertFalse(h_d in g_a.hosts.all()) + self.assertFalse(h_d in g_c.hosts.all()) + + # Mark group C inactive. Its child groups and hosts should now also be + # attached to group A. Group D hosts should be unchanged. + g_c.mark_inactive() + self.assertTrue(g_d in g_a.children.all()) + self.assertTrue(h_c in g_a.hosts.all()) + self.assertFalse(h_d in g_a.hosts.all()) + def test_group_parents_and_children(self): # Test for various levels of group parent/child relations, with hosts, # to verify that helper properties return the correct querysets.