From e7bdb02f90de6d7839fadd2dff8e6019a5d8b74e Mon Sep 17 00:00:00 2001 From: Jan Hartkopf Date: Wed, 29 Nov 2023 17:36:46 +0100 Subject: [PATCH] Allow project switching for Designate API Contrary to other OpenStack APIs, the Designate v2 API implements project switching based on HTTP headers, especially x-auth-sudo-project-id and x-auth-all-projects. This commit adds feature parity with other OpenStack APIs, i. e. direct usage of parameters "project_id" and "all_projects" for DNS resource list operations. Story: 2010909 Task: 48748 Change-Id: I1cb1efbf1f243cca0c5bb6e1058d25b2ad863355 Signed-off-by: Jan Hartkopf --- openstack/dns/v2/_base.py | 20 +++++++++++ openstack/dns/v2/_proxy.py | 2 +- openstack/proxy.py | 2 +- openstack/resource.py | 9 ++++- openstack/tests/unit/test_resource.py | 33 +++++++++++++++++++ ...urce-list-by-project-8b5479a045ef7373.yaml | 6 ++++ 6 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-dns-resource-list-by-project-8b5479a045ef7373.yaml diff --git a/openstack/dns/v2/_base.py b/openstack/dns/v2/_base.py index 0cac1eac5..a72513a7a 100644 --- a/openstack/dns/v2/_base.py +++ b/openstack/dns/v2/_base.py @@ -72,6 +72,26 @@ class Resource(resource.Resource): "No %s found for %s" % (cls.__name__, name_or_id) ) + @classmethod + def list( + cls, + session, + project_id=None, + all_projects=None, + **params, + ): + headers: ty.Union[ty.Dict[str, str] | None] = ( + {} if project_id or all_projects else None + ) + + if headers is not None: + if project_id: + headers["x-auth-sudo-project-id"] = str(project_id) + if all_projects: + headers["x-auth-all-projects"] = str(all_projects) + + return super().list(session=session, headers=headers, **params) + @classmethod def _get_next_link(cls, uri, response, data, marker, limit, total_yielded): next_link = None diff --git a/openstack/dns/v2/_proxy.py b/openstack/dns/v2/_proxy.py index de2ee5742..9c71b2f5f 100644 --- a/openstack/dns/v2/_proxy.py +++ b/openstack/dns/v2/_proxy.py @@ -561,7 +561,7 @@ class Proxy(proxy.Proxy): # ======== Zone Shares ======== def zone_shares(self, zone, **query): - """Retrieve a generator of zone sharess + """Retrieve a generator of zone shares :param zone: The zone ID or a :class:`~openstack.dns.v2.zone.Zone` instance diff --git a/openstack/proxy.py b/openstack/proxy.py index c058964a9..41144159b 100644 --- a/openstack/proxy.py +++ b/openstack/proxy.py @@ -880,7 +880,7 @@ class Proxy(adapter.Adapter): if resource_name in skip_resources: self.log.debug( - f"Skipping resource {resource_name} " "in project cleanup" + f"Skipping resource {resource_name} in project cleanup" ) return True diff --git a/openstack/resource.py b/openstack/resource.py index 53b786176..ca4196c09 100644 --- a/openstack/resource.py +++ b/openstack/resource.py @@ -1990,6 +1990,7 @@ class Resource(dict): allow_unknown_params=False, *, microversion=None, + headers=None, **params, ): """This method is a generator which yields resource objects. @@ -2010,6 +2011,8 @@ class Resource(dict): passing everything known to the server. ``False`` will result in validation exception when unknown query parameters are passed. :param str microversion: API version to override the negotiated one. + :param dict headers: Additional headers to inject into the HTTP + request. :param dict params: These keyword arguments are passed through the :meth:`~openstack.resource.QueryParamter._transpose` method to find if any of them match expected query parameters to be sent @@ -2079,6 +2082,10 @@ class Resource(dict): return False return True + headers_final = {"Accept": "application/json"} + if headers: + headers_final = {**headers_final, **headers} + # Track the total number of resources yielded so we can paginate # swift objects total_yielded = 0 @@ -2086,7 +2093,7 @@ class Resource(dict): # Copy query_params due to weird mock unittest interactions response = session.get( uri, - headers={"Accept": "application/json"}, + headers=headers_final, params=query_params.copy(), microversion=microversion, ) diff --git a/openstack/tests/unit/test_resource.py b/openstack/tests/unit/test_resource.py index 7d09eda4b..253db463f 100644 --- a/openstack/tests/unit/test_resource.py +++ b/openstack/tests/unit/test_resource.py @@ -18,6 +18,7 @@ from unittest import mock from keystoneauth1 import adapter import requests +from openstack import dns from openstack import exceptions from openstack import format from openstack import resource @@ -2651,6 +2652,38 @@ class TestResourceActions(base.TestCase): Test.base_path % {"something": uri_param}, ) + def test_list_with_injected_headers(self): + mock_empty = mock.Mock() + mock_empty.status_code = 200 + mock_empty.json.return_value = {"resources": []} + + self.session.get.side_effect = [mock_empty] + + _ = list( + self.test_class.list(self.session, headers={'X-Test': 'value'}) + ) + + expected = {'Accept': 'application/json', 'X-Test': 'value'} + self.assertEqual( + expected, self.session.get.call_args.kwargs['headers'] + ) + + @mock.patch.object(resource.Resource, 'list') + def test_list_dns_with_headers(self, mock_resource_list): + dns.v2._base.Resource.list( + self.session, + project_id='1234', + all_projects=True, + ) + + expected = { + 'x-auth-sudo-project-id': '1234', + 'x-auth-all-projects': 'True', + } + self.assertEqual( + expected, mock_resource_list.call_args.kwargs['headers'] + ) + def test_allow_invalid_list_params(self): qp = "query param!" qp_name = "query-param" diff --git a/releasenotes/notes/add-dns-resource-list-by-project-8b5479a045ef7373.yaml b/releasenotes/notes/add-dns-resource-list-by-project-8b5479a045ef7373.yaml new file mode 100644 index 000000000..f79ed22c0 --- /dev/null +++ b/releasenotes/notes/add-dns-resource-list-by-project-8b5479a045ef7373.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add functionality to list DNS resources for a certain project only, + or for all projects, using the new `project_id` and `all_projects` + parameters.