From 0641c6b0a628d44c00a9e0deffb0219391cb3900 Mon Sep 17 00:00:00 2001 From: Aaron Tan Date: Thu, 16 Nov 2017 16:43:21 -0500 Subject: [PATCH] Supress exception with concurrent deletion Relates https://github.com/ansible/ansible-tower/issues/7768 This issue, as well as https://github.com/ansible/ansible-tower/issues/7622, both rooted in a concurrency issue of Django ORM: https://github.com/ansible/ansible-tower/issues/762://code.djangoproject.com/ticket/28806 The solution related deals specifically with the related issue, but is not a general solution. A general workaround can be found in https://github.com/ansible/tower/pull/500. Signed-off-by: Aaron Tan --- awx/api/serializers.py | 66 ++++++++++++++++++++++++++++++++---------- awx/main/access.py | 8 +++-- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index f4be8560bb..86fa3aee8a 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1106,8 +1106,13 @@ class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer): def get_related(self, obj): res = super(ProjectUpdateSerializer, self).get_related(obj) + try: + res.update(dict( + project = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}), + )) + except ObjectDoesNotExist: + pass res.update(dict( - project = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}), cancel = self.reverse('api:project_update_cancel', kwargs={'pk': obj.pk}), scm_inventory_updates = self.reverse('api:project_update_scm_inventory_updates', kwargs={'pk': obj.pk}), notifications = self.reverse('api:project_update_notifications_list', kwargs={'pk': obj.pk}), @@ -1728,8 +1733,15 @@ class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSeri def get_related(self, obj): res = super(InventoryUpdateSerializer, self).get_related(obj) + try: + res.update(dict( + inventory_source = self.reverse( + 'api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk} + ), + )) + except ObjectDoesNotExist: + pass res.update(dict( - inventory_source = self.reverse('api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk}), cancel = self.reverse('api:inventory_update_cancel', kwargs={'pk': obj.pk}), notifications = self.reverse('api:inventory_update_notifications_list', kwargs={'pk': obj.pk}), )) @@ -2339,14 +2351,30 @@ class JobOptionsSerializer(LabelsListMixin, BaseSerializer): def get_related(self, obj): res = super(JobOptionsSerializer, self).get_related(obj) res['labels'] = self.reverse('api:job_template_label_list', kwargs={'pk': obj.pk}) - if obj.inventory: - res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk}) - if obj.project: - res['project'] = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}) - if obj.credential: - res['credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.credential.pk}) - if obj.vault_credential: - res['vault_credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.vault_credential.pk}) + try: + if obj.inventory: + res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk}) + except ObjectDoesNotExist: + setattr(obj, 'inventory', None) + try: + if obj.project: + res['project'] = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}) + except ObjectDoesNotExist: + setattr(obj, 'project', None) + try: + if obj.credential: + res['credential'] = self.reverse( + 'api:credential_detail', kwargs={'pk': obj.credential.pk} + ) + except ObjectDoesNotExist: + setattr(obj, 'credential', None) + try: + if obj.vault_credential: + res['vault_credential'] = self.reverse( + 'api:credential_detail', kwargs={'pk': obj.vault_credential.pk} + ) + except ObjectDoesNotExist: + setattr(obj, 'vault_credential', None) if self.version > 1: if isinstance(obj, UnifiedJobTemplate): res['extra_credentials'] = self.reverse( @@ -2584,15 +2612,23 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer): notifications = self.reverse('api:job_notifications_list', kwargs={'pk': obj.pk}), labels = self.reverse('api:job_label_list', kwargs={'pk': obj.pk}), )) - if obj.job_template: - res['job_template'] = self.reverse('api:job_template_detail', - kwargs={'pk': obj.job_template.pk}) + try: + if obj.job_template: + res['job_template'] = self.reverse('api:job_template_detail', + kwargs={'pk': obj.job_template.pk}) + except ObjectDoesNotExist: + setattr(obj, 'job_template', None) if (obj.can_start or True) and self.version == 1: # TODO: remove in 3.3 res['start'] = self.reverse('api:job_start', kwargs={'pk': obj.pk}) if obj.can_cancel or True: res['cancel'] = self.reverse('api:job_cancel', kwargs={'pk': obj.pk}) - if obj.project_update: - res['project_update'] = self.reverse('api:project_update_detail', kwargs={'pk': obj.project_update.pk}) + try: + if obj.project_update: + res['project_update'] = self.reverse( + 'api:project_update_detail', kwargs={'pk': obj.project_update.pk} + ) + except ObjectDoesNotExist: + pass res['relaunch'] = self.reverse('api:job_relaunch', kwargs={'pk': obj.pk}) return res diff --git a/awx/main/access.py b/awx/main/access.py index 4d44dc2995..1de1e8fb3f 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -12,6 +12,7 @@ from django.db.models import Q, Prefetch from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ObjectDoesNotExist # Django REST Framework from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError @@ -1134,8 +1135,11 @@ class ProjectUpdateAccess(BaseAccess): def can_start(self, obj, validate_license=True): # for relaunching - if obj and obj.project: - return self.user in obj.project.update_role + try: + if obj and obj.project: + return self.user in obj.project.update_role + except ObjectDoesNotExist: + pass return False @check_superuser