diff options
author | Matthew Jones <matburt@redhat.com> | 2016-07-15 17:46:42 +0200 |
---|---|---|
committer | Matthew Jones <matburt@redhat.com> | 2016-07-15 17:47:02 +0200 |
commit | eb97b8c91646a55e360eb5f2f8ea800f2f5a1696 (patch) | |
tree | 5e2fecb49d1d7d318ee2c4aa643d3c5ca1b258a6 | |
parent | Setting the local var CredentialList to the deep clone seems to be problemati... (diff) | |
download | awx-eb97b8c91646a55e360eb5f2f8ea800f2f5a1696.tar.xz awx-eb97b8c91646a55e360eb5f2f8ea800f2f5a1696.zip |
Switch disallowed object delete to 409
In the case of running job conflicts
-rw-r--r-- | awx/api/views.py | 8 | ||||
-rw-r--r-- | awx/main/access.py | 50 | ||||
-rw-r--r-- | awx/main/tests/functional/api/test_job_template.py | 12 |
3 files changed, 45 insertions, 25 deletions
diff --git a/awx/api/views.py b/awx/api/views.py index 64ea980f66..908cb1c95a 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2184,14 +2184,6 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView): serializer_class = JobTemplateSerializer always_allow_superuser = False - def destroy(self, request, *args, **kwargs): - obj = self.get_object() - can_delete = request.user.can_access(JobTemplate, 'delete', obj) - if not can_delete: - raise PermissionDenied("Cannot delete job template.") - return super(JobTemplateDetail, self).destroy(request, *args, **kwargs) - - class JobTemplateLaunch(RetrieveAPIView, GenericAPIView): model = JobTemplate diff --git a/awx/main/access.py b/awx/main/access.py index af05c58be6..f96f9baf26 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -12,7 +12,7 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType # Django REST Framework -from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError +from rest_framework.exceptions import ParseError, PermissionDenied, APIException # AWX from awx.main.utils import * # noqa @@ -25,7 +25,7 @@ from awx.main.conf import tower_settings __all__ = ['get_user_queryset', 'check_user_access', 'user_accessible_objects', - 'user_admin_role',] + 'user_admin_role', 'StateConflict',] PERMISSION_TYPES = [ PERM_INVENTORY_ADMIN, @@ -57,6 +57,9 @@ access_registry = { # ... } +class StateConflict(APIException): + status_code = 409 + default_detail = 'Object state is not correct' def register_access(model_class, access_class): access_classes = access_registry.setdefault(model_class, []) @@ -315,11 +318,15 @@ class OrganizationAccess(BaseAccess): if not is_change_possible: return False active_jobs = [] - active_jobs.extend(Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)) - active_jobs.extend(ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)) - active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES)) + active_jobs.extend([dict(type="job", id=o.id) + for o in Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)]) + active_jobs.extend([dict(type="project_update", id=o.id) + for o in ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)]) + active_jobs.extend([dict(type="inventory_update", id=o.id) + for o in InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs))) + raise StateConflict({"conflict": "Resource is being used by running jobs", + "active_jobs": active_jobs}) return True class InventoryAccess(BaseAccess): @@ -387,10 +394,13 @@ class InventoryAccess(BaseAccess): if not is_can_admin: return False active_jobs = [] - active_jobs.extend(Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES)) - active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES)) + active_jobs.extend([dict(type="job", id=o.id) + for o in Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES)]) + active_jobs.extend([dict(type="inventory_update", id=o.id) + for o in InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs))) + raise StateConflict({"conflict": "Resource is being used by running jobs", + "active_jobs": active_jobs}) return True def can_run_ad_hoc_commands(self, obj): @@ -508,9 +518,11 @@ class GroupAccess(BaseAccess): if not is_delete_allowed: return False active_jobs = [] - active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES)) + active_jobs.extend([dict(type="inventory_update", id=o.id) + for o in InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs))) + raise StateConflict({"conflict": "Resource is being used by running jobs", + "active_jobs": active_jobs}) return True class InventorySourceAccess(BaseAccess): @@ -765,10 +777,13 @@ class ProjectAccess(BaseAccess): if not is_change_allowed: return False active_jobs = [] - active_jobs.extend(Job.objects.filter(project=obj, status__in=ACTIVE_STATES)) - active_jobs.extend(ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES)) + active_jobs.extend([dict(type="job", id=o.id) + for o in Job.objects.filter(project=obj, status__in=ACTIVE_STATES)]) + active_jobs.extend([dict(type="project_update", id=o.id) + for o in ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs))) + raise StateConflict({"conflict": "Resource is being used by running jobs", + "active_jobs": active_jobs}) return True @check_superuser @@ -989,14 +1004,15 @@ class JobTemplateAccess(BaseAccess): return True - @check_superuser def can_delete(self, obj): is_delete_allowed = self.user in obj.admin_role if not is_delete_allowed: return False - active_jobs = obj.jobs.filter(status__in=ACTIVE_STATES) + active_jobs = [dict(type="job", id=o.id) + for o in obj.jobs.filter(status__in=ACTIVE_STATES)] if len(active_jobs) > 0: - raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs))) + raise StateConflict({"conflict": "Resource is being used by running jobs", + "active_jobs": active_jobs}) return True class JobAccess(BaseAccess): diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py index 14c92c4a54..e9aa4135c4 100644 --- a/awx/main/tests/functional/api/test_job_template.py +++ b/awx/main/tests/functional/api/test_job_template.py @@ -335,3 +335,15 @@ def test_jt_without_project(inventory): data["job_type"] = "scan" serializer = JobTemplateSerializer(data=data) assert serializer.is_valid() + +@pytest.mark.django_db +def test_disallow_template_delete_on_running_job(job_template_factory, delete, admin_user): + objects = job_template_factory('jt', + credential='c', + job_type="run", + project='p', + inventory='i', + organization='o') + j = objects.job_template.create_unified_job() + delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user) + assert delete_response.status_code == 409 |