From 14addae813c1994b9ef6c871237378226765c9f6 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 1 Mar 2017 11:13:26 -0500 Subject: [PATCH] delete inventories in the background via a celery task see: #4382 see: #6279 --- awx/api/serializers.py | 2 +- awx/api/views.py | 15 +++++++++++---- awx/main/migrations/0038_v320_release.py | 7 +++++++ awx/main/models/inventory.py | 14 +++++++++++++- awx/main/tasks.py | 19 ++++++++++++++++++- 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 7e50066e39..36e72e51ca 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1130,7 +1130,7 @@ class InventorySerializer(BaseSerializerWithVariables): 'total_hosts', 'hosts_with_active_failures', 'total_groups', 'groups_with_active_failures', 'has_inventory_sources', 'total_inventory_sources', 'inventory_sources_with_failures', - 'insights_credential',) + 'insights_credential', 'pending_deletion',) def get_related(self, obj): res = super(InventorySerializer, self).get_related(obj) diff --git a/awx/api/views.py b/awx/api/views.py index 1fb87ff7d7..65848364cf 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -59,7 +59,7 @@ import ansiconv from social.backends.utils import load_backends # AWX -from awx.main.tasks import send_notifications, update_host_smart_inventory_memberships +from awx.main.tasks import send_notifications, update_host_smart_inventory_memberships, delete_inventory from awx.main.access import get_user_queryset from awx.main.ha import is_ha_environment from awx.api.authentication import TaskAuthentication, TokenGetAuthentication @@ -1838,9 +1838,16 @@ class InventoryDetail(ControlledByScmMixin, RetrieveUpdateDestroyAPIView): return super(InventoryDetail, self).update(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): - with ignore_inventory_computed_fields(): - with ignore_inventory_group_removal(): - return super(InventoryDetail, self).destroy(request, *args, **kwargs) + obj = self.get_object() + if obj.pending_deletion is True: + return Response(dict(error=_("Inventory is already being deleted.")), status=status.HTTP_400_BAD_REQUEST) + if not request.user.can_access(self.model, 'delete', obj): + raise PermissionDenied() + obj.websocket_emit_status('pending_deletion') + delete_inventory.delay(obj.id) + obj.pending_deletion = True + obj.save(update_fields=['pending_deletion']) + return Response(status=status.HTTP_202_ACCEPTED) class InventoryActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): diff --git a/awx/main/migrations/0038_v320_release.py b/awx/main/migrations/0038_v320_release.py index 16f9ce1688..049151f709 100644 --- a/awx/main/migrations/0038_v320_release.py +++ b/awx/main/migrations/0038_v320_release.py @@ -70,6 +70,13 @@ class Migration(migrations.Migration): unique_together=set([('host', 'inventory')]), ), + # Background Inventory deletion + migrations.AddField( + model_name='inventory', + name='pending_deletion', + field=models.BooleanField(default=False, help_text='Flag indicating the inventory is being deleted.', editable=False), + ), + # Facts migrations.AlterField( model_name='fact', diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 3c92b31391..e22f336805 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -11,7 +11,7 @@ import os.path # Django from django.conf import settings -from django.db import models +from django.db import models, connection from django.utils.translation import ugettext_lazy as _ from django.db import transaction from django.core.exceptions import ValidationError @@ -20,6 +20,7 @@ from django.utils.timezone import now # AWX from awx.api.versioning import reverse from awx.main.constants import CLOUD_PROVIDERS +from awx.main.consumers import emit_channel_notification from awx.main.fields import ( ImplicitRoleField, JSONBField, @@ -153,6 +154,11 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): null=True, default=None, ) + pending_deletion = models.BooleanField( + default=False, + editable=False, + help_text=_('Flag indicating the inventory is being deleted.'), + ) def get_absolute_url(self, request=None): @@ -351,6 +357,12 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): iobj.save(update_fields=computed_fields.keys()) logger.debug("Finished updating inventory computed fields") + def websocket_emit_status(self, status): + connection.on_commit(lambda: emit_channel_notification( + 'inventories-status_changed', + {'group_name': 'inventories', 'inventory_id': self.id, 'status': status} + )) + @property def root_groups(self): group_pks = self.groups.values_list('pk', flat=True) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 3508255abb..dd4c889f77 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -52,7 +52,7 @@ from awx.main.isolated import run, isolated_manager from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url, check_proot_installed, build_proot_temp_dir, wrap_args_with_proot, get_system_task_capacity, OutputEventFilter, - parse_yaml_or_json) + parse_yaml_or_json, ignore_inventory_computed_fields, ignore_inventory_group_removal) from awx.main.utils.reload import restart_local_services, stop_local_services from awx.main.utils.handlers import configure_external_logger from awx.main.consumers import emit_channel_notification @@ -356,6 +356,23 @@ def update_host_smart_inventory_memberships(): return +@task(queue='tower') +def delete_inventory(inventory_id): + i = Inventory.objects.filter(id=inventory_id) + if not i.exists(): + logger.error("Delete Inventory failed due to missing inventory: " + str(inventory_id)) + return + i = i[0] + with ignore_inventory_computed_fields(), \ + ignore_inventory_group_removal(): + i.delete() + emit_channel_notification( + 'inventories-status_changed', + {'group_name': 'inventories', 'inventory_id': inventory_id, 'status': 'deleted'} + ) + logger.debug('Deleted inventory: %s' % inventory_id) + + class BaseTask(Task): name = None model = None