Repository: pulp/pulpcore

Issue:
task filter reserved_resources_record does not work as expected
The task filter named `reserved_resources_record` specifies to be of type `array` in the openapi spec. But it only filters by the last entry in the list. Instead it should filter tasks that lock on all filtered resources.

Additionally, the filter is not aware of the `shared:` prefix.


Discussion:

Resolving patch:
diff --git a/pulpcore/app/viewsets/custom_filters.py b/pulpcore/app/viewsets/custom_filters.py
--- a/pulpcore/app/viewsets/custom_filters.py
+++ b/pulpcore/app/viewsets/custom_filters.py
@@ -22,7 +22,58 @@
 
 class ReservedResourcesFilter(Filter):
     """
-    Enables a user to filter tasks by a reserved resource href
+    Enables a user to filter tasks by a reserved resource href.
+    """
+
+    def __init__(self, *args, exclusive=True, shared=True, **kwargs):
+        self.exclusive = exclusive
+        self.shared = shared
+        assert (
+            exclusive or shared
+        ), "ReservedResourceFilter must have either exclusive or shared set."
+        super().__init__(*args, **kwargs)
+
+    def filter(self, qs, value):
+        """
+        Callback to filter the query set based on the provided filter value.
+
+        Args:
+            qs (django.db.models.query.QuerySet): The Queryset to filter
+            value (string|List[str]): href to a reference to a reserved resource or a list thereof
+
+        Returns:
+            django.db.models.query.QuerySet: Queryset filtered by the reserved resource
+        """
+
+        if value is not None:
+            if isinstance(value, str):
+                value = [value]
+            if self.exclusive:
+                if self.shared:
+                    for item in value:
+                        qs = qs.filter(reserved_resources_record__overlap=[item, "shared:" + item])
+                else:
+                    qs = qs.filter(reserved_resources_record__contains=value)
+            else:  # self.shared
+                qs = qs.filter(
+                    reserved_resources_record__contains=["shared:" + item for item in value]
+                )
+
+        return qs
+
+
+class ReservedResourcesInFilter(BaseInFilter, ReservedResourcesFilter):
+    """
+    Enables a user to filter tasks by a list of reserved resource hrefs.
+    """
+
+
+class ReservedResourcesRecordFilter(Filter):
+    """
+    Enables a user to filter tasks by a reserved resource href.
+
+    Warning: This filter is badly documented and not fully functional, but we need to keep it for
+    compatibility reasons. Use ``ReservedResourcesFilter`` instead.
     """
 
     def filter(self, qs, value):
diff --git a/pulpcore/app/viewsets/task.py b/pulpcore/app/viewsets/task.py
--- a/pulpcore/app/viewsets/task.py
+++ b/pulpcore/app/viewsets/task.py
@@ -26,6 +26,8 @@
     HyperlinkRelatedFilter,
     IsoDateTimeFilter,
     ReservedResourcesFilter,
+    ReservedResourcesInFilter,
+    ReservedResourcesRecordFilter,
     CreatedResourcesFilter,
 )
 from pulpcore.constants import TASK_INCOMPLETE_STATES, TASK_STATES, TASK_CHOICES
@@ -43,8 +45,17 @@ class TaskFilter(BaseFilterSet):
     parent_task = HyperlinkRelatedFilter()
     child_tasks = HyperlinkRelatedFilter()
     task_group = HyperlinkRelatedFilter()
-    reserved_resources_record = ReservedResourcesFilter()
+    # This filter is deprecated and badly documented, but we need to keep it for compatibility
+    # reasons
+    reserved_resources_record = ReservedResourcesRecordFilter()
     created_resources = CreatedResourcesFilter()
+    # Non model field filters
+    reserved_resources = ReservedResourcesFilter(exclusive=True, shared=True)
+    reserved_resources__in = ReservedResourcesInFilter(exclusive=True, shared=True)
+    exclusive_resources = ReservedResourcesFilter(exclusive=True, shared=False)
+    exclusive_resources__in = ReservedResourcesInFilter(exclusive=True, shared=False)
+    shared_resources = ReservedResourcesFilter(exclusive=False, shared=True)
+    shared_resources__in = ReservedResourcesInFilter(exclusive=False, shared=True)
 
     class Meta:
         model = Task


Test patch: