Allow resource get to carry query string

This patch augments the resource and proxy class to do resource get with
optional queries, i.e. an 'args' parameter. We need this for some
parameterized resource retrieval. For example:

  GET <endpoint>/<resource_id>?with_details=True

Change-Id: I2b89c1b16b31db92845640bfc22f485b4f3ca00c
This commit is contained in:
tengqm
2015-12-29 22:39:17 -05:00
parent 28bd54238a
commit 04236214ac
9 changed files with 48 additions and 18 deletions

View File

@@ -436,17 +436,19 @@ class Proxy(proxy.BaseProxy):
return self._find(_node.Node, name_or_id, return self._find(_node.Node, name_or_id,
ignore_missing=ignore_missing) ignore_missing=ignore_missing)
def get_node(self, node): def get_node(self, node, args=None):
"""Get a single node. """Get a single node.
:param value: The value can be the name or ID of a node or a :param node: The value can be the name or ID of a node or a
:class:`~openstack.cluster.v1.node.Node` instance. :class:`~openstack.cluster.v1.node.Node` instance.
:param args: An optional argument that will be translated into query
strings when retrieving the node.
:returns: One :class:`~openstack.cluster.v1.node.Node` :returns: One :class:`~openstack.cluster.v1.node.Node`
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no :raises: :class:`~openstack.exceptions.ResourceNotFound` when no
node matching the name or ID could be found. node matching the name or ID could be found.
""" """
return self._get(_node.Node, node) return self._get(_node.Node, node, args=args)
def nodes(self, **query): def nodes(self, **query):
"""Retrieve a generator of nodes. """Retrieve a generator of nodes.

View File

@@ -76,11 +76,13 @@ class Limits(resource.Resource):
absolute = resource.prop("absolute", type=AbsoluteLimits) absolute = resource.prop("absolute", type=AbsoluteLimits)
rate = resource.prop("rate", type=list) rate = resource.prop("rate", type=list)
def get(self, session, include_headers=False): def get(self, session, args=None, include_headers=False):
"""Get the Limits resource. """Get the Limits resource.
:param session: The session to use for making this request. :param session: The session to use for making this request.
:type session: :class:`~openstack.session.Session` :type session: :class:`~openstack.session.Session`
:param dict args: An optional dict that will be translated into query
strings for retrieving the object when specified.
:returns: A Limits instance :returns: A Limits instance
:rtype: :class:`~openstack.compute.v2.limits.Limits` :rtype: :class:`~openstack.compute.v2.limits.Limits`

View File

@@ -42,7 +42,7 @@ class ServerMetadata(resource.Resource):
return attrs return attrs
@classmethod @classmethod
def get_data_by_id(cls, session, resource_id, path_args=None, def get_data_by_id(cls, session, resource_id, path_args=None, args=None,
include_headers=False): include_headers=False):
url = cls._get_url(path_args) url = cls._get_url(path_args)
resp = session.get(url, endpoint_filter=cls.service) resp = session.get(url, endpoint_filter=cls.service)

View File

@@ -146,7 +146,7 @@ class Object(resource.Resource):
#: value in the X-Delete-At metadata item. #: value in the X-Delete-At metadata item.
delete_after = resource.header("x-delete-after", type=int) delete_after = resource.header("x-delete-after", type=int)
def get(self, session): def get(self, session, args=None):
url = self._get_url(self, self.id) url = self._get_url(self, self.id)
# TODO(thowe): Add filter header support bug #1488269 # TODO(thowe): Add filter header support bug #1488269
headers = {'Accept': 'bytes'} headers = {'Accept': 'bytes'}

View File

@@ -177,7 +177,7 @@ class BaseProxy(object):
return res.create(self.session) return res.create(self.session)
@_check_resource(strict=False) @_check_resource(strict=False)
def _get(self, resource_type, value=None, path_args=None): def _get(self, resource_type, value=None, path_args=None, args=None):
"""Get a resource """Get a resource
:param resource_type: The type of resource to get. :param resource_type: The type of resource to get.
@@ -187,13 +187,16 @@ class BaseProxy(object):
subclass. subclass.
:param path_args: A dict containing arguments for forming the request :param path_args: A dict containing arguments for forming the request
URL, if needed. URL, if needed.
:param args: A optional dict containing arguments that will be
translated into query strings when forming the request URL.
:returns: The result of the ``get`` :returns: The result of the ``get``
:rtype: :class:`~openstack.resource.Resource` :rtype: :class:`~openstack.resource.Resource`
""" """
res = self._get_resource(resource_type, value, path_args) res = self._get_resource(resource_type, value, path_args)
try: try:
return res.get(self.session) return res.get(self.session, args=args)
except ksa_exc.NotFound as exc: except ksa_exc.NotFound as exc:
raise exceptions.ResourceNotFound( raise exceptions.ResourceNotFound(
"No %s found for %s" % (resource_type.__name__, value), "No %s found for %s" % (resource_type.__name__, value),

View File

@@ -37,6 +37,7 @@ import time
from keystoneauth1 import exceptions as ksa_exc from keystoneauth1 import exceptions as ksa_exc
import six import six
from six.moves.urllib import parse as url_parse
from openstack import exceptions from openstack import exceptions
from openstack import utils from openstack import utils
@@ -570,7 +571,7 @@ class Resource(collections.MutableMapping):
return self return self
@classmethod @classmethod
def get_data_by_id(cls, session, resource_id, path_args=None, def get_data_by_id(cls, session, resource_id, path_args=None, args=None,
include_headers=False): include_headers=False):
"""Get the attributes of a remote resource from an id. """Get the attributes of a remote resource from an id.
@@ -581,6 +582,8 @@ class Resource(collections.MutableMapping):
:param dict path_args: A dictionary of arguments to construct :param dict path_args: A dictionary of arguments to construct
a compound URL. a compound URL.
See `How path_args are used`_ for details. See `How path_args are used`_ for details.
:param dict args: A dictionary of query parameters to be appended to
the compound URL.
:param bool include_headers: ``True`` if header data should be :param bool include_headers: ``True`` if header data should be
included in the response body, included in the response body,
``False`` if not. ``False`` if not.
@@ -593,6 +596,8 @@ class Resource(collections.MutableMapping):
raise exceptions.MethodNotSupported(cls, 'retrieve') raise exceptions.MethodNotSupported(cls, 'retrieve')
url = cls._get_url(path_args, resource_id) url = cls._get_url(path_args, resource_id)
if args:
url = '?'.join([url, url_parse.urlencode(args)])
response = session.get(url, endpoint_filter=cls.service) response = session.get(url, endpoint_filter=cls.service)
body = response.json() body = response.json()
@@ -629,7 +634,7 @@ class Resource(collections.MutableMapping):
include_headers=include_headers) include_headers=include_headers)
return cls.existing(**body) return cls.existing(**body)
def get(self, session, include_headers=False): def get(self, session, include_headers=False, args=None):
"""Get the remote resource associated with this instance. """Get the remote resource associated with this instance.
:param session: The session to use for making this request. :param session: The session to use for making this request.
@@ -637,12 +642,13 @@ class Resource(collections.MutableMapping):
:param bool include_headers: ``True`` if header data should be :param bool include_headers: ``True`` if header data should be
included in the response body, included in the response body,
``False`` if not. ``False`` if not.
:param dict args: A dictionary of query parameters to be appended to
the compound URL.
:return: This :class:`Resource` instance. :return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if :raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_retrieve` is not set to ``True``. :data:`Resource.allow_retrieve` is not set to ``True``.
""" """
body = self.get_data_by_id(session, self.id, path_args=self, body = self.get_data_by_id(session, self.id, path_args=self, args=args,
include_headers=include_headers) include_headers=include_headers)
self._attrs.update(body) self._attrs.update(body)
self._loaded = True self._loaded = True

View File

@@ -304,7 +304,13 @@ class TestClusterProxy(test_proxy_base.TestProxyBase):
self.verify_find(self.proxy.find_node, node.Node) self.verify_find(self.proxy.find_node, node.Node)
def test_node_get(self): def test_node_get(self):
self.verify_get(self.proxy.get_node, node.Node) self.verify_get(self.proxy.get_node, node.Node, args=None,
expected_kwargs={'args': None})
def test_node_get_with_args(self):
self.verify_get(self.proxy.get_node, node.Node, args={'details': True},
method_kwargs={'args': {'details': True}},
expected_kwargs={'args': {'details': True}})
def test_nodes(self): def test_nodes(self):
self.verify_list(self.proxy.nodes, node.Node, self.verify_list(self.proxy.nodes, node.Node,

View File

@@ -221,14 +221,20 @@ class TestProxyGet(testtools.TestCase):
def test_get_resource(self): def test_get_resource(self):
rv = self.sot._get(RetrieveableResource, self.res) rv = self.sot._get(RetrieveableResource, self.res)
self.res.get.assert_called_with(self.session) self.res.get.assert_called_with(self.session, args=None)
self.assertEqual(rv, self.fake_result)
def test_get_resource_with_args(self):
rv = self.sot._get(RetrieveableResource, self.res, args={'K': 'V'})
self.res.get.assert_called_with(self.session, args={'K': 'V'})
self.assertEqual(rv, self.fake_result) self.assertEqual(rv, self.fake_result)
def test_get_id(self): def test_get_id(self):
rv = self.sot._get(RetrieveableResource, self.fake_id) rv = self.sot._get(RetrieveableResource, self.fake_id)
RetrieveableResource.existing.assert_called_with(id=self.fake_id) RetrieveableResource.existing.assert_called_with(id=self.fake_id)
self.res.get.assert_called_with(self.session) self.res.get.assert_called_with(self.session, args=None)
self.assertEqual(rv, self.fake_result) self.assertEqual(rv, self.fake_result)
def test_get_not_found(self): def test_get_not_found(self):

View File

@@ -100,16 +100,21 @@ class TestProxyBase(base.TestCase):
expected_args=[resource_type, "resource_or_id"], expected_args=[resource_type, "resource_or_id"],
expected_kwargs=expected_kwargs) expected_kwargs=expected_kwargs)
def verify_get(self, test_method, resource_type, value=None, def verify_get(self, test_method, resource_type, value=None, args=None,
mock_method="openstack.proxy.BaseProxy._get", mock_method="openstack.proxy.BaseProxy._get",
ignore_value=False, **kwargs): ignore_value=False, **kwargs):
the_value = value the_value = value
if value is None: if value is None:
the_value = [] if ignore_value else ["value"] the_value = [] if ignore_value else ["value"]
expected_kwargs = {"path_args": kwargs} if kwargs else {} expected_kwargs = kwargs.pop("expected_kwargs", {})
method_kwargs = kwargs.pop("method_kwargs", kwargs)
if args:
expected_kwargs["args"] = args
if kwargs:
expected_kwargs["path_args"] = kwargs
self._verify2(mock_method, test_method, self._verify2(mock_method, test_method,
method_args=the_value, method_args=the_value,
method_kwargs=kwargs, method_kwargs=method_kwargs or {},
expected_args=[resource_type] + the_value, expected_args=[resource_type] + the_value,
expected_kwargs=expected_kwargs) expected_kwargs=expected_kwargs)