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:
parent
4421b87508
commit
6b62d28151
@ -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,
|
||||
|
135
openstack/tests/unit/cloud/test_openstackcloud.py
Normal file
135
openstack/tests/unit/cloud/test_openstackcloud.py
Normal 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()
|
||||
)
|
7
releasenotes/notes/search_resource-b9c2f772e01d3b2c.yaml
Normal file
7
releasenotes/notes/search_resource-b9c2f772e01d3b2c.yaml
Normal 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.
|
Loading…
x
Reference in New Issue
Block a user