Implement unified search_resources method

We have a lot of search_XXX calls in the cloud layer, but not for everything.
They are also doing nearly same to what it is possible to do with plain proxy 
methods and therfore we want to simplify and unify this so that Ansible modules
can easily rely on a single function in different modules instead or needing a 
dedicated search method for every resource doing the same.

New method accepts resource_type, resource identifier (which may be
empty), filters and also possibility to pass additional args into the
_get and _list calls (for unpredicted special cases). With this all
search_XXX functions can be finally deprecated.

Change-Id: I375c2b625698c4920211eb6e089a1b820755be84
This commit is contained in:
Artem Goncharov 2022-11-04 17:23:00 +01:00
parent 4421b87508
commit 6b62d28151
3 changed files with 213 additions and 0 deletions

View File

@ -753,6 +753,77 @@ class _OpenStackCloudMixin:
else:
return False
def search_resources(
self,
resource_type,
name_or_id,
get_args=None,
get_kwargs=None,
list_args=None,
list_kwargs=None,
**filters
):
"""Search resources
Search resources matching certain conditions
:param str resource_type: String representation of the expected
resource as `service.resource` (i.e. "network.security_group").
:param str name_or_id: Name or ID of the resource
:param list get_args: Optional args to be passed to the _get call.
:param dict get_kwargs: Optional kwargs to be passed to the _get call.
:param list list_args: Optional args to be passed to the _list call.
:param dict list_kwargs: Optional kwargs to be passed to the _list call
:param dict filters: Additional filters to be used for querying
resources.
"""
get_args = get_args or ()
get_kwargs = get_kwargs or {}
list_args = list_args or ()
list_kwargs = list_kwargs or {}
# User used string notation. Try to find proper
# resource
(service_name, resource_name) = resource_type.split('.')
if not hasattr(self, service_name):
raise exceptions.SDKException(
"service %s is not existing/enabled" %
service_name
)
service_proxy = getattr(self, service_name)
try:
resource_type = service_proxy._resource_registry[resource_name]
except KeyError:
raise exceptions.SDKException(
"Resource %s is not known in service %s" %
(resource_name, service_name)
)
if name_or_id:
# name_or_id is definitely not None
try:
resource_by_id = service_proxy._get(
resource_type,
name_or_id,
*get_args,
**get_kwargs)
return [resource_by_id]
except exceptions.ResourceNotFound:
pass
if not filters:
filters = {}
if name_or_id:
filters["name"] = name_or_id
list_kwargs.update(filters)
return list(service_proxy._list(
resource_type,
*list_args,
**list_kwargs
))
def project_cleanup(
self,
dry_run=True,

View File

@ -0,0 +1,135 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
from openstack import exceptions
from openstack import proxy
from openstack import resource
from openstack.tests.unit import base
class TestSearch(base.TestCase):
class FakeResource(resource.Resource):
allow_fetch = True
allow_list = True
foo = resource.Body("foo")
def setUp(self):
super(TestSearch, self).setUp()
self.session = proxy.Proxy(self.cloud)
self.session._sdk_connection = self.cloud
self.session._get = mock.Mock()
self.session._list = mock.Mock()
self.session._resource_registry = dict(
fake=self.FakeResource
)
# Set the mock into the cloud connection
setattr(self.cloud, "mock_session", self.session)
def test_raises_unknown_service(self):
self.assertRaises(
exceptions.SDKException,
self.cloud.search_resources,
"wrong_service.wrong_resource",
"name"
)
def test_raises_unknown_resource(self):
self.assertRaises(
exceptions.SDKException,
self.cloud.search_resources,
"mock_session.wrong_resource",
"name"
)
def test_search_resources_get_finds(self):
self.session._get.return_value = self.FakeResource(foo="bar")
ret = self.cloud.search_resources(
"mock_session.fake",
"fake_name"
)
self.session._get.assert_called_with(
self.FakeResource, "fake_name")
self.assertEqual(1, len(ret))
self.assertEqual(
self.FakeResource(foo="bar").to_dict(),
ret[0].to_dict()
)
def test_search_resources_list(self):
self.session._get.side_effect = exceptions.ResourceNotFound
self.session._list.return_value = [
self.FakeResource(foo="bar")
]
ret = self.cloud.search_resources(
"mock_session.fake",
"fake_name"
)
self.session._get.assert_called_with(
self.FakeResource, "fake_name")
self.session._list.assert_called_with(
self.FakeResource, name="fake_name")
self.assertEqual(1, len(ret))
self.assertEqual(
self.FakeResource(foo="bar").to_dict(),
ret[0].to_dict()
)
def test_search_resources_args(self):
self.session._get.side_effect = exceptions.ResourceNotFound
self.session._list.return_value = []
self.cloud.search_resources(
"mock_session.fake",
"fake_name",
get_args=["getarg1"],
get_kwargs={"getkwarg1": "1"},
list_args=["listarg1"],
list_kwargs={"listkwarg1": "1"},
filter1="foo"
)
self.session._get.assert_called_with(
self.FakeResource, "fake_name",
"getarg1", getkwarg1="1")
self.session._list.assert_called_with(
self.FakeResource,
"listarg1", listkwarg1="1",
name="fake_name", filter1="foo"
)
def test_search_resources_name_empty(self):
self.session._list.return_value = [
self.FakeResource(foo="bar")
]
ret = self.cloud.search_resources(
"mock_session.fake",
None,
foo="bar"
)
self.session._get.assert_not_called()
self.session._list.assert_called_with(
self.FakeResource, foo="bar")
self.assertEqual(1, len(ret))
self.assertEqual(
self.FakeResource(foo="bar").to_dict(),
ret[0].to_dict()
)

View File

@ -0,0 +1,7 @@
---
features:
- |
Add search_resources method implementing generic search interface accepting
resource name (as "service.resource"), name_or_id and list of additional
filters and returning 0 or many resources matching those. This interface is
primarily designed to be used by Ansible modules.