1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-01 16:51:11 +03:00

Deref deleted group immediately and then pass off the children and hosts

to be processed in the background.

Get rid of the child relative delete mechanism and merge the old concept
of delete on the /group/n endpoint with our new concept
This commit is contained in:
Matthew Jones 2014-05-21 16:08:51 -04:00
parent 2b4014cf4d
commit 3d90c6db76
5 changed files with 66 additions and 150 deletions

View File

@ -65,7 +65,6 @@ inventory_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/$', 'inventory_detail'),
url(r'^(?P<pk>[0-9]+)/hosts/$', 'inventory_hosts_list'),
url(r'^(?P<pk>[0-9]+)/groups/$', 'inventory_groups_list'),
url(r'^(?P<pk>[0-9]+)/groups/(?P<group_pk>[0-9]+)/$', 'inventory_root_group_remove'),
url(r'^(?P<pk>[0-9]+)/root_groups/$', 'inventory_root_groups_list'),
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'inventory_variable_data'),
url(r'^(?P<pk>[0-9]+)/script/$', 'inventory_script_view'),
@ -90,7 +89,6 @@ group_urls = patterns('awx.api.views',
url(r'^$', 'group_list'),
url(r'^(?P<pk>[0-9]+)/$', 'group_detail'),
url(r'^(?P<pk>[0-9]+)/children/$', 'group_children_list'),
url(r'^(?P<pk>[0-9]+)/children/(?P<subgroup_pk>[0-9]+)/$', 'group_children_remove'),
url(r'^(?P<pk>[0-9]+)/hosts/$', 'group_hosts_list'),
url(r'^(?P<pk>[0-9]+)/all_hosts/$', 'group_all_hosts_list'),
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'group_variable_data'),

View File

@ -788,42 +788,7 @@ class GroupChildrenList(SubListCreateAPIView):
return Response(status=status.HTTP_204_NO_CONTENT)
class GroupChildrenRemove(DestroyAPIView):
model = Group
serializer_class = GroupSerializer
view_name = 'Remove a subgroup recursively'
def destroy(self, request, *args, **kwargs):
parent_group = self.get_object()
group = Group.objects.get(id=kwargs['subgroup_pk'])
if group not in parent_group.children.all():
return Response(status=status.HTTP_404_NOT_FOUND)
group.mark_inactive_recursive(parent_group)
return Response()
def post(self, request, *args, **kwargs):
if not isinstance(request.DATA, dict):
return Response('invalid type for post data',
status=status.HTTP_400_BAD_REQUEST)
if 'disassociate' in request.DATA:
parent_group = self.get_object()
group = Group.objects.get(id=kwargs['subgroup_pk'])
if group not in parent_group.children.all():
return Response(status=status.HTTP_404_NOT_FOUND)
parent_group.children.remove(group)
for host in group.hosts.all():
parent_group.hosts.add(host)
for subgroup in group.children.all():
parent_group.children.add(subgroup)
if group.parents.count() < 1:
group.mark_inactive()
return Response()
else:
return Response()
class GroupPotentialChildrenList(SubListAPIView):
lass GroupPotentialChildrenList(SubListAPIView):
model = Group
serializer_class = GroupSerializer
@ -890,6 +855,19 @@ class GroupDetail(RetrieveUpdateDestroyAPIView):
model = Group
serializer_class = GroupSerializer
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
# FIXME: Why isn't the active check being caught earlier by RBAC?
if getattr(obj, 'active', True) == False:
raise Http404()
if getattr(obj, 'is_active', True) == False:
raise Http404()
if not request.user.can_access(self.model, 'delete', obj):
raise PermissionDenied()
if hasattr(obj, 'mark_inactive'):
obj.mark_inactive_recursive()
return Response(status=status.HTTP_204_NO_CONTENT)
class InventoryGroupsList(SubListCreateAPIView):
model = Group
@ -898,17 +876,6 @@ class InventoryGroupsList(SubListCreateAPIView):
relationship = 'groups'
parent_key = 'inventory'
class InventoryRootGroupRemove(DestroyAPIView):
model = Group
serializer_class = GroupSerializer
view_name = 'Inventory Group Subgroup'
def destroy(self, request, *args, **kwargs):
group = Group.objects.get(id=kwargs['group_pk'])
group.mark_inactive_recursive()
return Response()
class InventoryRootGroupsList(SubListCreateAPIView):
model = Group

View File

@ -530,36 +530,13 @@ class Group(CommonModelNameNotUnique):
def get_absolute_url(self):
return reverse('api:group_detail', args=(self.pk,))
def mark_inactive_recursive(self, parent=None):
from awx.main.tasks import update_inventory_computed_fields
def mark_actual(parent=parent):
linked_children = [(parent, self)]
marked_groups = []
marked_hosts = []
for subgroup in linked_children:
parent, group = subgroup
if parent is not None:
group.parents.remove(parent)
if group.parents.count() > 0:
continue
for host in group.hosts.all():
host.groups.remove(group)
host_inv_sources = host.inventory_sources.all()
for inv_source in group.inventory_sources.all():
if inv_source in host_inv_sources:
host.inventory_sources.remove(inv_source)
if host.groups.count() < 1:
marked_hosts.append(host)
for childgroup in group.children.all():
linked_children.append((group, childgroup))
marked_groups.append(group)
for group in marked_groups:
group.mark_inactive()
for host in marked_hosts:
host.mark_inactive()
with ignore_inventory_computed_fields():
mark_actual()
update_inventory_computed_fields.delay(self.id, True)
def mark_inactive_recursive(self):
from awx.main.tasks import update_inventory_computed_fields, bulk_inventory_element_delete
group_data = {'parent': self.id, 'inventory': self.inventory.id,
'children': [{'id': c.id} for c in self.children.all()],
'hosts': [{'id': h.id} for h in self.hosts.all()]}
self.mark_inactive()
bulk_inventory_element_delete.delay(group_data)
def mark_inactive(self, save=True, recompute=True, from_inventory_import=False):
'''

View File

@ -47,6 +47,46 @@ logger = logging.getLogger('awx.main.tasks')
# FIXME: Cleanly cancel task when celery worker is stopped.
@task()
def bulk_inventory_element_delete(group_details):
def remove_host_from_group(host, group):
host.groups.remove(group)
host_inv_sources = host.inventory_sources.all()
for inv_source in group.inventory_sources.all():
if inv_source in host_inv_sources:
host.inventory_sources.remove(inv_source)
return host.groups.count() < 1
def mark_actual(group_details):
overall_parent = Group.objects.get(id=group_details['parent'])
linked_children = [(overall_parent , Group.objects.get(id=g['id'])) for g in group_details['children']]
initial_hosts = [Host.objects.get(id=h['id']) for h in group_details['hosts']]
marked_hosts = []
marked_groups = []
for host in initial_hosts:
last_group = remove_host_from_group(host, overall_parent)
if last_group:
marked_hosts.append(host)
for subgroup in linked_children:
parent, group = subgroup
if parent is not None:
group.parents.remove(parent)
if group.parents.count() > 0:
continue
for host in group.hosts.all():
last_group = remove_host_from_group(host, group)
if last_group:
marked_hosts.append(host)
for childgroup in group.children.all():
linked_children.append((group, childgroup))
marked_groups.append(group)
for group in marked_groups:
group.mark_inactive()
for host in marked_hosts:
host.mark_inactive()
with ignore_inventory_computed_fields():
mark_actual(group_details)
update_inventory_computed_fields.delay(group_details['inventory'], True)
@task(bind=True)
def tower_periodic_scheduler(self):
def get_last_run():

View File

@ -898,20 +898,12 @@ class InventoryTest(BaseTest):
other_sub_group = self.inventory_a.groups.create(name='Sub2')
other_low_group = self.inventory_a.groups.create(name='Low2')
# Third hierarchy
third_top_group = self.inventory_a.groups.create(name='Top3')
third_sub_group = self.inventory_a.groups.create(name='Sub3')
third_low_group = self.inventory_a.groups.create(name='Low3')
sub_group.parents.add(top_group)
low_group.parents.add(sub_group)
other_sub_group.parents.add(other_top_group)
other_low_group.parents.add(other_sub_group)
third_sub_group.parents.add(third_top_group)
third_low_group.parents.add(third_sub_group)
t1 = self.inventory_a.hosts.create(name='t1')
t1.groups.add(top_group)
s1 = self.inventory_a.hosts.create(name='s1')
@ -926,75 +918,17 @@ class InventoryTest(BaseTest):
l2 = self.inventory_a.hosts.create(name='l2')
l2.groups.add(other_low_group)
t3 = self.inventory_a.hosts.create(name='t3')
t3.groups.add(third_top_group)
s3 = self.inventory_a.hosts.create(name='s3')
s3.groups.add(third_sub_group)
l3 = self.inventory_a.hosts.create(name='l3')
l3.groups.add(third_low_group)
# Copy second hierarchy subgroup under the first hierarchy subgroup
other_sub_group.parents.add(sub_group)
self.assertTrue(s2 in sub_group.all_hosts.all())
self.assertTrue(other_sub_group in sub_group.children.all())
# Now recursively remove it, the references in other_top_group should remain
other_sub_group.mark_inactive_recursive(parent=sub_group)
self.assertFalse(s2 in sub_group.all_hosts.all())
self.assertFalse(other_sub_group in sub_group.children.all())
self.assertTrue(s2 in other_top_group.all_hosts.all())
self.assertTrue(other_sub_group in other_top_group.children.all())
# Recursively remove the third hierarchy which has no links to others so should go inactive
third_top_group.mark_inactive_recursive(parent=None)
third_low_group = Group.objects.get(pk=third_low_group.pk)
third_sub_group = Group.objects.get(pk=third_sub_group.pk)
third_top_group = Group.objects.get(pk=third_top_group.pk)
t3 = Host.objects.get(pk=t3.pk)
s3 = Host.objects.get(pk=s3.pk)
l3 = Host.objects.get(pk=l3.pk)
self.assertFalse(third_low_group.active)
self.assertFalse(third_sub_group.active)
self.assertFalse(third_top_group.active)
self.assertFalse(l3.active)
self.assertFalse(s3.active)
self.assertFalse(t3.active)
# Add second hierarchy low group under the first hierarchy subgroup
other_low_group.parents.add(sub_group)
# Try to remove it with a regular user
with self.current_user(self.other_django_user):
url = reverse('api:group_children_remove', args=(sub_group.pk, other_low_group.pk))
self.delete(url, expect=403)
# Try to remove it with the admin user
with self.current_user(self.normal_django_user):
url = reverse('api:group_children_remove', args=(sub_group.pk, other_low_group.pk))
self.delete(url, expect=200)
# Admin user should have removed the reference
self.assertFalse(l2 in sub_group.all_hosts.all())
self.assertFalse(other_low_group in sub_group.children.all())
# Add the second hierarchy low group under the first hierarchy subgroup again
other_low_group.parents.add(sub_group)
with self.current_user(self.normal_django_user):
url = reverse('api:inventory_root_group_remove', args=(self.inventory_a.pk, other_top_group.pk,))
self.delete(url, expect=200)
# Entire hierarchy except for the low group should be gone
t2 = Host.objects.get(pk=t2.pk)
s2 = Host.objects.get(pk=s2.pk)
l2 = Host.objects.get(pk=l2.pk)
# Now recursively remove its parent and the reference from subgroup should remain
other_top_group.mark_inactive_recursive()
other_top_group = Group.objects.get(pk=other_top_group.pk)
other_sub_group = Group.objects.get(pk=other_sub_group.pk)
other_low_group = Group.objects.get(pk=other_low_group.pk)
self.assertFalse(t2.active or s2.active)
self.assertFalse(other_top_group.active or other_sub_group.active)
self.assertTrue(l2.active)
self.assertTrue(other_low_group.active)
self.assertTrue(s2 in sub_group.all_hosts.all())
self.assertTrue(other_sub_group in sub_group.children.all())
self.assertFalse(other_top_group.active)
def test_group_parents_and_children(self):
# Test for various levels of group parent/child relations, with hosts,