Просмотр исходного кода

Allow filtering results by status

We're adding a "status" search option that can be specified when
listing the following resources:

* deployments
    * status -> last_execution_status db field
* transfers
* transfer executions

The filtering will be performed on the db side.
Lucian Petrut 3 недель назад
Родитель
Сommit
eba28b57c1

+ 15 - 0
coriolis/api/v1/deployments.py

@@ -8,6 +8,7 @@ from coriolis.api import common
 from coriolis.api.v1 import utils as api_utils
 from coriolis.api.v1.views import deployment_view
 from coriolis.api import wsgi as api_wsgi
+from coriolis import constants
 from coriolis.deployments import api
 from coriolis.endpoints import api as endpoints_api
 from coriolis import exception
@@ -36,6 +37,18 @@ class DeploymentsController(api_wsgi.Controller):
 
         return deployment_view.single(deployment)
 
+    def _get_filters(self, req) -> dict:
+        filters = {}
+        # For simplicity and consistency, we'll use "status" to search for a
+        # given "last_execution_status".
+        status = req.GET.get("status")
+        if status is not None:
+            if status not in constants.ALL_EXECUTION_STATUSES:
+                raise exc.HTTPBadRequest(
+                    explanation=f"Unknown deployment status: {status}")
+            filters["status"] = status
+        return filters
+
     def _list(self, req):
         show_deleted = api_utils.get_bool_url_arg(
             req, "show_deleted", default=False)
@@ -47,6 +60,7 @@ class DeploymentsController(api_wsgi.Controller):
 
         marker, limit = common.get_paging_params(req)
         sort_keys, sort_dirs = common.get_sort_params(req)
+        filters = self._get_filters(req)
 
         return deployment_view.collection(
             self._deployment_api.get_deployments(
@@ -55,6 +69,7 @@ class DeploymentsController(api_wsgi.Controller):
                 include_task_info=include_task_info,
                 marker=marker, limit=limit,
                 sort_keys=sort_keys, sort_dirs=sort_dirs,
+                filters=filters,
             ))
 
     def index(self, req):

+ 14 - 1
coriolis/api/v1/transfer_tasks_executions.py

@@ -4,6 +4,7 @@
 from coriolis.api import common
 from coriolis.api.v1.views import transfer_tasks_execution_view
 from coriolis.api import wsgi as api_wsgi
+from coriolis import constants
 from coriolis import exception
 from coriolis.policies import transfer_tasks_executions as executions_policies
 from coriolis.transfer_tasks_executions import api
@@ -27,6 +28,16 @@ class TransferTasksExecutionController(api_wsgi.Controller):
 
         return transfer_tasks_execution_view.single(execution)
 
+    def _get_filters(self, req) -> dict:
+        filters = {}
+        status = req.GET.get("status")
+        if status is not None:
+            if status not in constants.ALL_EXECUTION_STATUSES:
+                raise exc.HTTPBadRequest(
+                    explanation=f"Unknown task execution status: {status}")
+            filters["status"] = status
+        return filters
+
     def index(self, req, transfer_id):
         context = req.environ["coriolis.context"]
         context.can(
@@ -34,12 +45,14 @@ class TransferTasksExecutionController(api_wsgi.Controller):
 
         marker, limit = common.get_paging_params(req)
         sort_keys, sort_dirs = common.get_sort_params(req)
+        filters = self._get_filters(req)
 
         return transfer_tasks_execution_view.collection(
             self._transfer_tasks_execution_api.get_executions(
                 context, transfer_id, include_tasks=False,
                 marker=marker, limit=limit,
-                sort_keys=sort_keys, sort_dirs=sort_dirs))
+                sort_keys=sort_keys, sort_dirs=sort_dirs,
+                filters=filters))
 
     def detail(self, req, transfer_id):
         context = req.environ["coriolis.context"]

+ 12 - 0
coriolis/api/v1/transfers.py

@@ -42,6 +42,16 @@ class TransferController(api_wsgi.Controller):
 
         return transfer_view.single(transfer)
 
+    def _get_filters(self, req) -> dict:
+        filters = {}
+        status = req.GET.get("status")
+        if status is not None:
+            if status not in constants.ALL_TASK_STATUSES:
+                raise exc.HTTPBadRequest(
+                    explanation=f"Unknown task status: {status}")
+            filters["status"] = status
+        return filters
+
     def _list(self, req):
         show_deleted = api_utils.get_bool_url_arg(
             req, "show_deleted", default=False)
@@ -52,6 +62,7 @@ class TransferController(api_wsgi.Controller):
             req, "include_task_info", default=False)
         marker, limit = common.get_paging_params(req)
         sort_keys, sort_dirs = common.get_sort_params(req)
+        filters = self._get_filters(req)
         return transfer_view.collection(
             self._transfer_api.get_transfers(
                 context,
@@ -59,6 +70,7 @@ class TransferController(api_wsgi.Controller):
                 include_task_info=include_task_info,
                 marker=marker, limit=limit,
                 sort_keys=sort_keys, sort_dirs=sort_dirs,
+                filters=filters,
             ))
 
     def index(self, req):

+ 9 - 3
coriolis/conductor/rpc/client.py

@@ -147,7 +147,8 @@ class ConductorClient(rpc.BaseRPCClient):
                                       marker=None,
                                       limit=None,
                                       sort_keys=None,
-                                      sort_dirs=None):
+                                      sort_dirs=None,
+                                      filters=None):
         return self._call(
             ctxt, 'get_transfer_tasks_executions',
             transfer_id=transfer_id,
@@ -156,6 +157,7 @@ class ConductorClient(rpc.BaseRPCClient):
             limit=limit,
             sort_keys=sort_keys,
             sort_dirs=sort_dirs,
+            filters=filters,
         )
 
     def get_transfer_tasks_execution(self, ctxt, transfer_id, execution_id,
@@ -208,7 +210,8 @@ class ConductorClient(rpc.BaseRPCClient):
     def get_transfers(self, ctxt, include_tasks_executions=False,
                       include_task_info=False,
                       marker=None, limit=None,
-                      sort_keys=None, sort_dirs=None):
+                      sort_keys=None, sort_dirs=None,
+                      filters=None):
         return self._call(
             ctxt, 'get_transfers',
             include_tasks_executions=include_tasks_executions,
@@ -217,6 +220,7 @@ class ConductorClient(rpc.BaseRPCClient):
             limit=limit,
             sort_keys=sort_keys,
             sort_dirs=sort_dirs,
+            filters=filters,
         )
 
     def get_transfer(self, ctxt, transfer_id, include_task_info=False):
@@ -235,7 +239,8 @@ class ConductorClient(rpc.BaseRPCClient):
     def get_deployments(self, ctxt, include_tasks=False,
                         include_task_info=False,
                         marker=None, limit=None,
-                        sort_keys=None, sort_dirs=None):
+                        sort_keys=None, sort_dirs=None,
+                        filters=None):
         return self._call(
             ctxt, 'get_deployments', include_tasks=include_tasks,
             include_task_info=include_task_info,
@@ -243,6 +248,7 @@ class ConductorClient(rpc.BaseRPCClient):
             limit=limit,
             sort_keys=sort_keys,
             sort_dirs=sort_dirs,
+            filters=filters,
         )
 
     def get_deployment(self, ctxt, deployment_id, include_task_info=False):

+ 9 - 3
coriolis/conductor/rpc/server.py

@@ -1156,7 +1156,8 @@ class ConductorServerEndpoint(object):
                                       marker=None,
                                       limit=None,
                                       sort_keys=None,
-                                      sort_dirs=None):
+                                      sort_dirs=None,
+                                      filters=None):
         return db_api.get_transfer_tasks_executions(
             ctxt, transfer_id, include_tasks,
             include_task_info=include_task_info,
@@ -1164,6 +1165,7 @@ class ConductorServerEndpoint(object):
             limit=limit,
             sort_keys=sort_keys,
             sort_dirs=sort_dirs,
+            filters=filters,
             to_dict=True)
 
     @tasks_execution_synchronized
@@ -1217,7 +1219,8 @@ class ConductorServerEndpoint(object):
     def get_transfers(ctxt, include_tasks_executions=False,
                       include_task_info=False,
                       marker=None, limit=None,
-                      sort_keys=None, sort_dirs=None):
+                      sort_keys=None, sort_dirs=None,
+                      filters=None):
         return db_api.get_transfers(
             ctxt, include_tasks_executions=include_tasks_executions,
             include_task_info=include_task_info,
@@ -1225,6 +1228,7 @@ class ConductorServerEndpoint(object):
             limit=limit,
             sort_keys=sort_keys,
             sort_dirs=sort_dirs,
+            filters=filters,
             to_dict=True)
 
     @transfer_synchronized
@@ -1383,7 +1387,8 @@ class ConductorServerEndpoint(object):
     @staticmethod
     def get_deployments(ctxt, include_tasks, include_task_info=False,
                         marker=None, limit=None,
-                        sort_keys=None, sort_dirs=None):
+                        sort_keys=None, sort_dirs=None,
+                        filters=None):
         return db_api.get_deployments(
             ctxt, include_tasks,
             include_task_info=include_task_info,
@@ -1391,6 +1396,7 @@ class ConductorServerEndpoint(object):
             limit=limit,
             sort_keys=sort_keys,
             sort_dirs=sort_dirs,
+            filters=filters,
             to_dict=True)
 
     @deployment_synchronized

+ 11 - 0
coriolis/constants.py

@@ -33,6 +33,11 @@ FINALIZED_EXECUTION_STATUSES = [
     EXECUTION_STATUS_ERROR_ALLOCATING_MINIONS
 ]
 
+ALL_EXECUTION_STATUSES = (
+    ACTIVE_EXECUTION_STATUSES +
+    FINALIZED_EXECUTION_STATUSES
+)
+
 TASK_STATUS_SCHEDULED = "SCHEDULED"
 TASK_STATUS_PENDING = "PENDING"
 TASK_STATUS_STARTING = "STARTING"
@@ -83,6 +88,12 @@ FINALIZED_TASK_STATUSES = [
     TASK_STATUS_FAILED_TO_CANCEL
 ]
 
+ALL_TASK_STATUSES = (
+    ACTIVE_TASK_STATUSES +
+    CANCELED_TASK_STATUSES +
+    FINALIZED_TASK_STATUSES
+)
+
 TASK_TYPE_FINALIZE_INSTANCE_DEPLOYMENT = "FINALIZE_INSTANCE_DEPLOYMENT"
 TASK_TYPE_CLEANUP_FAILED_INSTANCE_DEPLOYMENT = (
     "CLEANUP_FAILED_INSTANCE_DEPLOYMENT")

+ 25 - 0
coriolis/db/api.py

@@ -1,6 +1,7 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
+import copy
 import uuid
 
 from oslo_config import cfg
@@ -281,6 +282,7 @@ def get_transfer_tasks_executions(context, transfer_id, include_tasks=False,
                                   limit=None,
                                   sort_keys: list[str] | None = None,
                                   sort_dirs: list[str] | None = None,
+                                  filters: dict | None = None,
                                   to_dict=False):
     q = _soft_delete_aware_query(context, models.TasksExecution)
     q = q.join(models.Transfer)
@@ -293,6 +295,13 @@ def get_transfer_tasks_executions(context, transfer_id, include_tasks=False,
 
     q = q.filter(models.Transfer.id == transfer_id)
 
+    filters = copy.deepcopy(filters or {})
+    if "status" in filters:
+        status = filters.pop("status")
+        q = q.filter(models.TasksExecution.status == status)
+    if filters:
+        raise ValueError("Unsupported filters: %s" % filters)
+
     sort_keys, sort_dirs = process_sort_params(
         sort_keys,
         sort_dirs,
@@ -458,6 +467,7 @@ def get_transfers(context,
                   limit=None,
                   sort_keys: list[str] | None = None,
                   sort_dirs: list[str] | None = None,
+                  filters: dict | None = None,
                   to_dict=False):
     q = _soft_delete_aware_query(context, models.Transfer)
     if include_tasks_executions:
@@ -471,6 +481,13 @@ def get_transfers(context,
         q = q.filter(
             models.Transfer.project_id == context.project_id)
 
+    filters = copy.deepcopy(filters or {})
+    if "status" in filters:
+        status = filters.pop("status")
+        q = q.filter(models.Transfer.last_execution_status == status)
+    if filters:
+        raise ValueError("Unsupported filters: %s" % filters)
+
     sort_keys, sort_dirs = process_sort_params(
         sort_keys,
         sort_dirs,
@@ -588,6 +605,7 @@ def get_deployments(context,
                     limit=None,
                     sort_keys: list[str] | None = None,
                     sort_dirs: list[str] | None = None,
+                    filters: dict | None = None,
                     to_dict=False):
     q = _soft_delete_aware_query(context, models.Deployment)
     if include_tasks:
@@ -600,6 +618,13 @@ def get_deployments(context,
     if is_user_context(context):
         q = q.filter_by(project_id=context.project_id)
 
+    filters = copy.deepcopy(filters or {})
+    if "status" in filters:
+        status = filters.pop("status")
+        q = q.filter(models.Deployment.last_execution_status == status)
+    if filters:
+        raise ValueError("Unsupported filters: %s" % filters)
+
     sort_keys, sort_dirs = process_sort_params(
         sort_keys,
         sort_dirs,

+ 4 - 4
coriolis/deployer_manager/rpc/server.py

@@ -123,10 +123,10 @@ class DeployerManagerServerEndpoint:
             try:
                 deployments = self._rpc_conductor_client.get_deployments(
                     self._admin_ctx, include_tasks=False,
-                    include_task_info=False)
-                for d in deployments:
-                    if d['last_execution_status'] == PENDING_STATUS:
-                        self._check_deployer_status(d['id'])
+                    include_task_info=False,
+                    filters={'status': PENDING_STATUS})
+                for pending_deployment in deployments:
+                    self._check_deployer_status(pending_deployment['id'])
             except Exception:
                 LOG.warning(
                     f"Deployer manager failed to list pending deployments. "

+ 5 - 2
coriolis/deployments/api.py

@@ -28,11 +28,14 @@ class API(object):
     def get_deployments(self, ctxt, include_tasks=False,
                         include_task_info=False,
                         marker=None, limit=None,
-                        sort_keys=None, sort_dirs=None):
+                        sort_keys=None, sort_dirs=None,
+                        filters=None):
         return self._rpc_client.get_deployments(
             ctxt, include_tasks, include_task_info=include_task_info,
             marker=marker, limit=limit,
-            sort_keys=sort_keys, sort_dirs=sort_dirs)
+            sort_keys=sort_keys, sort_dirs=sort_dirs,
+            filters=filters,
+        )
 
     def get_deployment(self, ctxt, deployment_id, include_task_info=False):
         return self._rpc_client.get_deployment(

+ 4 - 0
coriolis/tests/api/v1/test_transfer_tasks_executions.py

@@ -95,6 +95,9 @@ class TransferTasksExecutionControllerTestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.marker,
             mock.sentinel.limit,
         )
+        mock_req.GET = {
+            "status": "RUNNING",
+        }
 
         result = self.transfer_api.index(mock_req, transfer_id)
 
@@ -111,6 +114,7 @@ class TransferTasksExecutionControllerTestCase(test_base.CoriolisBaseTestCase):
             limit=mock.sentinel.limit,
             sort_keys=mock.sentinel.sort_keys,
             sort_dirs=mock.sentinel.sort_dirs,
+            filters={"status": "RUNNING"},
         )
         mock_collection.assert_called_once_with(
             mock_get_executions.return_value)

+ 4 - 0
coriolis/tests/api/v1/test_transfers.py

@@ -107,6 +107,9 @@ class TransferControllerTestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.marker,
             mock.sentinel.limit,
         )
+        mock_req.GET = {
+            "status": "RUNNING",
+        }
 
         mock_get_bool_url_arg.side_effect = [False, False]
 
@@ -132,6 +135,7 @@ class TransferControllerTestCase(test_base.CoriolisBaseTestCase):
             limit=mock.sentinel.limit,
             sort_keys=mock.sentinel.sort_keys,
             sort_dirs=mock.sentinel.sort_dirs,
+            filters={"status": "RUNNING"},
         )
         mock_collection.assert_called_once_with(
             mock_get_transfers.return_value)

+ 2 - 0
coriolis/tests/conductor/rpc/test_client.py

@@ -166,6 +166,7 @@ class ConductorClientTestCase(test_base.CoriolisRPCClientTestCase):
         args = {
             "transfer_id": "mock_transfer_id",
             "include_tasks": False,
+            "filters": {"status": "RUNNING"},
             **self._mock_pagination_args,
         }
         self._test(self.client.get_transfer_tasks_executions, args)
@@ -210,6 +211,7 @@ class ConductorClientTestCase(test_base.CoriolisRPCClientTestCase):
         args = {
             "include_tasks_executions": False,
             "include_task_info": False,
+            "filters": {"status": "RUNNING"},
             **self._mock_pagination_args,
         }
         self._test(self.client.get_transfers, args)

+ 4 - 0
coriolis/tests/conductor/rpc/test_server.py

@@ -1451,6 +1451,7 @@ class ConductorServerEndpointTestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.transfer_id,
             mock.sentinel.execution_id,
             include_task_info=False,
+            filters={"status": "RUNNING"},
             **self._mock_pagination_args,
         )
 
@@ -1464,6 +1465,7 @@ class ConductorServerEndpointTestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.execution_id,
             include_task_info=False,
             **self._mock_pagination_args,
+            filters={"status": "RUNNING"},
             to_dict=True,
         )
 
@@ -1681,6 +1683,7 @@ class ConductorServerEndpointTestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.context,
             include_tasks_executions=False,
             include_task_info=False,
+            filters={"status": "RUNNING"},
             **self._mock_pagination_args,
         )
 
@@ -1692,6 +1695,7 @@ class ConductorServerEndpointTestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.context,
             include_tasks_executions=False,
             include_task_info=False,
+            filters={"status": "RUNNING"},
             to_dict=True,
             **self._mock_pagination_args,
         )

+ 79 - 1
coriolis/tests/integration/test_pagination.py → coriolis/tests/integration/test_api_listing.py

@@ -7,6 +7,7 @@ import datetime
 import operator
 import uuid
 
+from keystoneauth1.exceptions import http as http_exc
 from oslo_utils import timeutils
 
 from coriolis import constants
@@ -17,7 +18,7 @@ from coriolis import exception
 from coriolis.tests.integration import base
 
 
-class PaginationTest(base.CoriolisIntegrationTestBase):
+class APIListingTestBase(base.CoriolisIntegrationTestBase):
     FAKE_USER_ID = "fake-user-id"
     FAKE_PROJECT_ID = "fake-project-id"
 
@@ -175,6 +176,8 @@ class PaginationTest(base.CoriolisIntegrationTestBase):
             "created_at": created_at,
         }
 
+
+class PaginationTest(APIListingTestBase):
     def test_transfer_execution_list(self):
         executions = self._client.transfer_executions.list(
             self._transfers[0].id)
@@ -295,3 +298,78 @@ class PaginationTest(base.CoriolisIntegrationTestBase):
         ret_transfer_summary = [self._get_record_summary(t) for t in transfers]
         self.assertEqual(
             exp_sorted_transfer_summary[2:4], ret_transfer_summary)
+
+
+class APIFilterTest(APIListingTestBase):
+    @classmethod
+    def _setup_mocks(cls):
+        super()._setup_mocks()
+
+        # We'll create a few more resources, some in error state, some in
+        # completed state.
+        cls._completed_transfer = cls._create_db_transfer(
+            origin_endpoint_id=cls._src_endpoint.id,
+            destination_endpoint_id=cls._dst_endpoint.id,
+            last_execution_status=constants.TASK_STATUS_COMPLETED,
+        )
+
+        cls._failed_execution = cls._create_db_execution(
+            transfer=cls._completed_transfer,
+            status=constants.EXECUTION_STATUS_ERROR,
+        )
+        cls._completed_execution = cls._create_db_execution(
+            transfer=cls._completed_transfer,
+            status=constants.EXECUTION_STATUS_COMPLETED,
+        )
+
+        cls._failed_deployment = cls._create_db_deployment(
+            transfer_id=cls._completed_transfer.id,
+            origin_endpoint_id=cls._src_endpoint.id,
+            destination_endpoint_id=cls._dst_endpoint.id,
+            last_execution_status=constants.EXECUTION_STATUS_ERROR,
+        )
+        cls._completed_deployment = cls._create_db_deployment(
+            transfer_id=cls._completed_transfer.id,
+            origin_endpoint_id=cls._src_endpoint.id,
+            destination_endpoint_id=cls._dst_endpoint.id,
+            last_execution_status=constants.EXECUTION_STATUS_COMPLETED,
+        )
+
+    def test_transfer_execution_filter(self):
+        executions = self._client.transfer_executions.list(
+            self._completed_transfer.id,
+            filters={"status": constants.EXECUTION_STATUS_COMPLETED})
+        ret_summary = [self._get_record_summary(e) for e in executions]
+        exp_summary = [self._get_record_summary(self._completed_execution)]
+        self.assertEqual(exp_summary, ret_summary)
+
+    def test_transfer_filter(self):
+        executions = self._client.transfers.list(
+            filters={"status": constants.TASK_STATUS_COMPLETED})
+        ret_summary = [self._get_record_summary(e) for e in executions]
+        exp_summary = [self._get_record_summary(self._completed_transfer)]
+        self.assertEqual(exp_summary, ret_summary)
+
+    def test_deployment_filter(self):
+        executions = self._client.deployments.list(
+            filters={"status": constants.EXECUTION_STATUS_COMPLETED})
+        ret_summary = [self._get_record_summary(e) for e in executions]
+        exp_summary = [self._get_record_summary(self._completed_deployment)]
+        self.assertEqual(exp_summary, ret_summary)
+
+    def test_invalid_filter(self):
+        self.assertRaises(
+            http_exc.BadRequest,
+            self._client.deployments.list,
+            filters={"status": "fake-status"})
+
+        self.assertRaises(
+            http_exc.BadRequest,
+            self._client.transfers.list,
+            filters={"status": "fake-status"})
+
+        self.assertRaises(
+            http_exc.BadRequest,
+            self._client.transfer_executions.list,
+            self._completed_transfer.id,
+            filters={"status": "fake-status"})

+ 2 - 0
coriolis/tests/transfer_tasks_executions/test_api.py

@@ -55,6 +55,7 @@ class APITestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.limit,
             mock.sentinel.sort_keys,
             mock.sentinel.sort_dirs,
+            mock.sentinel.filters,
         )
 
         self.rpc_client.get_transfer_tasks_executions.assert_called_once_with(
@@ -64,6 +65,7 @@ class APITestCase(test_base.CoriolisBaseTestCase):
             mock.sentinel.limit,
             mock.sentinel.sort_keys,
             mock.sentinel.sort_dirs,
+            mock.sentinel.filters,
         )
         self.assertEqual(
             result, self.rpc_client.get_transfer_tasks_executions.return_value)

+ 2 - 0
coriolis/tests/transfers/test_api.py

@@ -70,11 +70,13 @@ class APITestCase(test_base.CoriolisBaseTestCase):
     def test_get_transfers(self):
         result = self.api.get_transfers(
             self.ctxt, include_tasks_executions=False, include_task_info=False,
+            filters={"status": "RUNNING"},
             **self._mock_pagination_args,
         )
 
         self.rpc_client.get_transfers.assert_called_once_with(
             self.ctxt, False, include_task_info=False,
+            filters={"status": "RUNNING"},
             **self._mock_pagination_args,
         )
         self.assertEqual(result, self.rpc_client.get_transfers.return_value)

+ 3 - 2
coriolis/transfer_tasks_executions/api.py

@@ -22,10 +22,11 @@ class API(object):
 
     def get_executions(self, ctxt, transfer_id, include_tasks=False,
                        marker=None, limit=None,
-                       sort_keys=None, sort_dirs=None):
+                       sort_keys=None, sort_dirs=None,
+                       filters=None):
         return self._rpc_client.get_transfer_tasks_executions(
             ctxt, transfer_id, include_tasks, marker, limit,
-            sort_keys, sort_dirs)
+            sort_keys, sort_dirs, filters)
 
     def get_execution(self, ctxt, transfer_id, execution_id):
         return self._rpc_client.get_transfer_tasks_execution(

+ 4 - 2
coriolis/transfers/api.py

@@ -34,12 +34,14 @@ class API(object):
     def get_transfers(self, ctxt, include_tasks_executions=False,
                       include_task_info=False,
                       marker=None, limit=None,
-                      sort_keys=None, sort_dirs=None):
+                      sort_keys=None, sort_dirs=None,
+                      filters=None):
         return self._rpc_client.get_transfers(
             ctxt, include_tasks_executions,
             include_task_info=include_task_info,
             marker=marker, limit=limit,
-            sort_keys=sort_keys, sort_dirs=sort_dirs)
+            sort_keys=sort_keys, sort_dirs=sort_dirs,
+            filters=filters)
 
     def get_transfer(self, ctxt, transfer_id, include_task_info=False):
         return self._rpc_client.get_transfer(