Repository: pulp/pulpcore

Issue:
Replace MissingResource exception with a subclass of rest_framework.exceptions.APIException
Author: @dkliban (dkliban@redhat.com)


Redmine Issue: 2382, https://pulp.plan.io/issues/2382

---

DRF provides an APIException\[0\] base class that should be used to create custom exceptions for Pulp's API. The new MissingResource exception should take two arguments: resource_type and resource_id.

\[0\] http://www.django-rest-framework.org/api-guide/exceptions/#apiexception






Discussion:
I think we should still do this and do it in 3.20.
We should not port this, but instead delete it. It's used in zero places. Here's my audit:

https://github.com/pulp/pulpcore/search?q=MissingResource   <--- lives here, and used in exactly one place
https://github.com/pulp/pulp_ansible/search?q=MissingResource
https://github.com/pulp/pulp-certguard/search?q=MissingResource
https://github.com/pulp/pulp_cookbook/search?q=MissingResource
https://github.com/pulp/pulp_container/search?q=MissingResource
https://github.com/pulp/pulp_deb/search?q=MissingResource
https://github.com/pulp/pulp_file/search?q=MissingResource
https://github.com/ansible/galaxy_ng/search?q=MissingResource
https://github.com/pulp/pulp_maven/search?q=MissingResource
https://github.com/pulp/pulp_npm/search?q=MissingResource
https://github.com/pulp/pulp_ostree/search?q=MissingResource
https://github.com/pulp/pulp_python/search?q=MissingResource
https://github.com/pulp/pulp_rpm/search?q=MissingResource


The one place is [here](https://github.com/pulp/pulpcore/blob/fcb355c1f8fec6780524c010cf5bba7e1aa1d51b/pulpcore/tasking/util.py#L32). I think we should just switch that single line to ApiException and not have Pulp's plugin API offer a custom exception. For something to be so broadly offered and unused tells me, at least right now, we don't need this.
Don't even catch the DoesNotExist exception in that place. It should do the proper 404 on it's own.

Resolving patch:
diff --git a/pulpcore/exceptions/__init__.py b/pulpcore/exceptions/__init__.py
--- a/pulpcore/exceptions/__init__.py
+++ b/pulpcore/exceptions/__init__.py
@@ -5,7 +5,6 @@
     TimeoutException,
     exception_to_dict,
 )
-from .http import MissingResource  # noqa
 from .validation import (  # noqa
     DigestValidationError,
     SizeValidationError,
diff --git a/pulpcore/exceptions/http.py b/pulpcore/exceptions/http.py
deleted file mode 100644
--- a/pulpcore/exceptions/http.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import http.client
-from gettext import gettext as _
-
-from .base import PulpException
-
-
-class MissingResource(PulpException):
-    """
-    Base class for missing resource exceptions.
-
-    Exceptions that are raised due to requests for resources that do not exist should inherit
-    from this base class.
-    """
-
-    http_status_code = http.client.NOT_FOUND
-
-    def __init__(self, **resources):
-        """
-        :param resources: keyword arguments of resource_type=resource_id
-        :type resources: dict
-        """
-        super().__init__("PLP0001")
-        self.resources = resources
-
-    def __str__(self):
-        resources_str = ", ".join("%s=%s" % (k, v) for k, v in self.resources.items())
-        msg = _("The following resources are missing: %s") % resources_str
-        return msg.encode("utf-8")
diff --git a/pulpcore/tasking/util.py b/pulpcore/tasking/util.py
--- a/pulpcore/tasking/util.py
+++ b/pulpcore/tasking/util.py
@@ -8,7 +8,6 @@
 from pulpcore.app.models import Task
 from pulpcore.app.util import get_view_name_for_model
 from pulpcore.constants import TASK_FINAL_STATES, TASK_INCOMPLETE_STATES, TASK_STATES
-from pulpcore.exceptions import MissingResource
 
 _logger = logging.getLogger(__name__)
 
@@ -24,12 +23,9 @@ def cancel(task_id):
         task_id (str): The ID of the task you wish to cancel
 
     Raises:
-        MissingResource: if a task with given task_id does not exist
+        rest_framework.exceptions.NotFound: If a task with given task_id does not exist
     """
-    try:
-        task_status = Task.objects.get(pk=task_id)
-    except Task.DoesNotExist:
-        raise MissingResource(task=task_id)
+    task_status = Task.objects.get(pk=task_id)
 
     if task_status.state in TASK_FINAL_STATES:
         # If the task is already done, just stop


Test patch:
diff --git a/pulpcore/tests/functional/__init__.py b/pulpcore/tests/functional/__init__.py
--- a/pulpcore/tests/functional/__init__.py
+++ b/pulpcore/tests/functional/__init__.py
@@ -4,6 +4,8 @@
 
 from pulp_smash.config import get_config
 from pulp_smash.pulp3.bindings import delete_orphans
+from pulp_smash.utils import get_pulp_setting
+
 
 from pulpcore.client.pulpcore import (
     ApiClient,
@@ -141,3 +143,13 @@ def delete_orphans_pre(request):
         raise pytest.UsageError("This test is not suitable to be marked parallel.")
     delete_orphans()
     yield
+
+
+@pytest.fixture(scope="session")
+def pulp_api_v3_path(cli_client):
+    v3_api_root = get_pulp_setting(cli_client, "V3_API_ROOT")
+    if v3_api_root is None:
+        raise RuntimeError(
+            "This fixture requires the server to have the `V3_API_ROOT` setting set."
+        )
+    return v3_api_root
diff --git a/pulpcore/tests/functional/api/test_tasks.py b/pulpcore/tests/functional/api/test_tasks.py
new file mode 100644
--- /dev/null
+++ b/pulpcore/tests/functional/api/test_tasks.py
@@ -0,0 +1,17 @@
+from uuid import uuid4
+
+import pytest
+
+from pulpcore.client.pulpcore.exceptions import ApiException
+
+
+def test_cancel_invalid_task_raises_404(pulp_api_v3_path, tasks_api_client):
+    patched_task_cancel = {"state": "canceled"}
+
+    missing_task_url = f"{pulp_api_v3_path}tasks/{uuid4()}/"
+
+    with pytest.raises(ApiException) as e_info:
+        tasks_api_client.tasks_cancel(
+            task_href=missing_task_url, patched_task_cancel=patched_task_cancel
+        )
+    assert e_info.value.status == 404