Merge "Allow passing explicit microversions to Resource methods"

This commit is contained in:
Zuul 2022-08-27 08:31:45 +00:00 committed by Gerrit Code Review
commit a163c05c92
3 changed files with 130 additions and 20 deletions

View File

@ -317,13 +317,14 @@ class Object(_base.BaseResource):
self._translate_response(response, has_body=False) self._translate_response(response, has_body=False)
return self return self
def _raw_delete(self, session): def _raw_delete(self, session, microversion=None):
if not self.allow_delete: if not self.allow_delete:
raise exceptions.MethodNotSupported(self, "delete") raise exceptions.MethodNotSupported(self, "delete")
request = self._prepare_request() request = self._prepare_request()
session = self._get_session(session) session = self._get_session(session)
microversion = self._get_microversion(session, action='delete') if microversion is None:
microversion = self._get_microversion(session, action='delete')
if self.is_static_large_object is None: if self.is_static_large_object is None:
# Fetch metadata to determine SLO flag # Fetch metadata to determine SLO flag

View File

@ -1410,7 +1410,15 @@ class Resource(dict):
return actual return actual
def create(self, session, prepend_key=True, base_path=None, **params): def create(
self,
session,
prepend_key=True,
base_path=None,
*,
microversion=None,
**params
):
"""Create a remote resource based on this instance. """Create a remote resource based on this instance.
:param session: The session to use for making this request. :param session: The session to use for making this request.
@ -1420,6 +1428,7 @@ class Resource(dict):
True. True.
:param str base_path: Base part of the URI for creating resources, if :param str base_path: Base part of the URI for creating resources, if
different from :data:`~openstack.resource.Resource.base_path`. different from :data:`~openstack.resource.Resource.base_path`.
:param str microversion: API version to override the negotiated one.
:param dict params: Additional params to pass. :param dict params: Additional params to pass.
:return: This :class:`Resource` instance. :return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if :raises: :exc:`~openstack.exceptions.MethodNotSupported` if
@ -1429,7 +1438,8 @@ class Resource(dict):
raise exceptions.MethodNotSupported(self, 'create') raise exceptions.MethodNotSupported(self, 'create')
session = self._get_session(session) session = self._get_session(session)
microversion = self._get_microversion(session, action='create') if microversion is None:
microversion = self._get_microversion(session, action='create')
requires_id = ( requires_id = (
self.create_requires_id self.create_requires_id
if self.create_requires_id is not None if self.create_requires_id is not None
@ -1490,6 +1500,8 @@ class Resource(dict):
data, data,
prepend_key=True, prepend_key=True,
base_path=None, base_path=None,
*,
microversion=None,
**params, **params,
): ):
"""Create multiple remote resources based on this class and data. """Create multiple remote resources based on this class and data.
@ -1502,6 +1514,7 @@ class Resource(dict):
True. True.
:param str base_path: Base part of the URI for creating resources, if :param str base_path: Base part of the URI for creating resources, if
different from :data:`~openstack.resource.Resource.base_path`. different from :data:`~openstack.resource.Resource.base_path`.
:param str microversion: API version to override the negotiated one.
:param dict params: Additional params to pass. :param dict params: Additional params to pass.
:return: A generator of :class:`Resource` objects. :return: A generator of :class:`Resource` objects.
@ -1519,7 +1532,8 @@ class Resource(dict):
raise ValueError('Invalid data passed: %s' % data) raise ValueError('Invalid data passed: %s' % data)
session = cls._get_session(session) session = cls._get_session(session)
microversion = cls._get_microversion(session, action='create') if microversion is None:
microversion = cls._get_microversion(session, action='create')
requires_id = ( requires_id = (
cls.create_requires_id cls.create_requires_id
if cls.create_requires_id is not None if cls.create_requires_id is not None
@ -1592,6 +1606,8 @@ class Resource(dict):
base_path=None, base_path=None,
error_message=None, error_message=None,
skip_cache=False, skip_cache=False,
*,
microversion=None,
**params, **params,
): ):
"""Get a remote resource based on this instance. """Get a remote resource based on this instance.
@ -1606,6 +1622,7 @@ class Resource(dict):
requested object does not exist. requested object does not exist.
:param bool skip_cache: A boolean indicating whether optional API :param bool skip_cache: A boolean indicating whether optional API
cache should be skipped for this invocation. cache should be skipped for this invocation.
:param str microversion: API version to override the negotiated one.
:param dict params: Additional parameters that can be consumed. :param dict params: Additional parameters that can be consumed.
:return: This :class:`Resource` instance. :return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if :raises: :exc:`~openstack.exceptions.MethodNotSupported` if
@ -1620,7 +1637,8 @@ class Resource(dict):
requires_id=requires_id, base_path=base_path requires_id=requires_id, base_path=base_path
) )
session = self._get_session(session) session = self._get_session(session)
microversion = self._get_microversion(session, action='fetch') if microversion is None:
microversion = self._get_microversion(session, action='fetch')
response = session.get( response = session.get(
request.url, request.url,
microversion=microversion, microversion=microversion,
@ -1635,13 +1653,14 @@ class Resource(dict):
self._translate_response(response, **kwargs) self._translate_response(response, **kwargs)
return self return self
def head(self, session, base_path=None): def head(self, session, base_path=None, *, microversion=None):
"""Get headers from a remote resource based on this instance. """Get headers from a remote resource based on this instance.
:param session: The session to use for making this request. :param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter` :type session: :class:`~keystoneauth1.adapter.Adapter`
:param str base_path: Base part of the URI for fetching resources, if :param str base_path: Base part of the URI for fetching resources, if
different from :data:`~openstack.resource.Resource.base_path`. different from :data:`~openstack.resource.Resource.base_path`.
:param str microversion: API version to override the negotiated one.
:return: This :class:`Resource` instance. :return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if :raises: :exc:`~openstack.exceptions.MethodNotSupported` if
@ -1653,7 +1672,8 @@ class Resource(dict):
raise exceptions.MethodNotSupported(self, 'head') raise exceptions.MethodNotSupported(self, 'head')
session = self._get_session(session) session = self._get_session(session)
microversion = self._get_microversion(session, action='fetch') if microversion is None:
microversion = self._get_microversion(session, action='fetch')
request = self._prepare_request(base_path=base_path) request = self._prepare_request(base_path=base_path)
response = session.head(request.url, microversion=microversion) response = session.head(request.url, microversion=microversion)
@ -1676,6 +1696,8 @@ class Resource(dict):
has_body=True, has_body=True,
retry_on_conflict=None, retry_on_conflict=None,
base_path=None, base_path=None,
*,
microversion=None,
**kwargs, **kwargs,
): ):
"""Commit the state of the instance to the remote resource. """Commit the state of the instance to the remote resource.
@ -1689,6 +1711,7 @@ class Resource(dict):
CONFLICT (409). Value of ``None`` leaves the `Adapter` defaults. CONFLICT (409). Value of ``None`` leaves the `Adapter` defaults.
:param str base_path: Base part of the URI for modifying resources, if :param str base_path: Base part of the URI for modifying resources, if
different from :data:`~openstack.resource.Resource.base_path`. different from :data:`~openstack.resource.Resource.base_path`.
:param str microversion: API version to override the negotiated one.
:param dict kwargs: Parameters that will be passed to :param dict kwargs: Parameters that will be passed to
_prepare_request() _prepare_request()
@ -1714,7 +1737,8 @@ class Resource(dict):
request = self._prepare_request( request = self._prepare_request(
prepend_key=prepend_key, base_path=base_path, **kwargs prepend_key=prepend_key, base_path=base_path, **kwargs
) )
microversion = self._get_microversion(session, action='commit') if microversion is None:
microversion = self._get_microversion(session, action='commit')
return self._commit( return self._commit(
session, session,
@ -1800,6 +1824,8 @@ class Resource(dict):
has_body=True, has_body=True,
retry_on_conflict=None, retry_on_conflict=None,
base_path=None, base_path=None,
*,
microversion=None,
): ):
"""Patch the remote resource. """Patch the remote resource.
@ -1818,6 +1844,7 @@ class Resource(dict):
CONFLICT (409). Value of ``None`` leaves the `Adapter` defaults. CONFLICT (409). Value of ``None`` leaves the `Adapter` defaults.
:param str base_path: Base part of the URI for modifying resources, if :param str base_path: Base part of the URI for modifying resources, if
different from :data:`~openstack.resource.Resource.base_path`. different from :data:`~openstack.resource.Resource.base_path`.
:param str microversion: API version to override the negotiated one.
:return: This :class:`Resource` instance. :return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if :raises: :exc:`~openstack.exceptions.MethodNotSupported` if
@ -1836,7 +1863,8 @@ class Resource(dict):
request = self._prepare_request( request = self._prepare_request(
prepend_key=prepend_key, base_path=base_path, patch=True prepend_key=prepend_key, base_path=base_path, patch=True
) )
microversion = self._get_microversion(session, action='patch') if microversion is None:
microversion = self._get_microversion(session, action='patch')
if patch: if patch:
request.body += self._convert_patch(patch) request.body += self._convert_patch(patch)
@ -1849,11 +1877,13 @@ class Resource(dict):
retry_on_conflict=retry_on_conflict, retry_on_conflict=retry_on_conflict,
) )
def delete(self, session, error_message=None, **kwargs): def delete(self, session, error_message=None, *, microversion=None,
**kwargs):
"""Delete the remote resource based on this instance. """Delete the remote resource based on this instance.
:param session: The session to use for making this request. :param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter` :type session: :class:`~keystoneauth1.adapter.Adapter`
:param str microversion: API version to override the negotiated one.
:param dict kwargs: Parameters that will be passed to :param dict kwargs: Parameters that will be passed to
_prepare_request() _prepare_request()
@ -1864,7 +1894,8 @@ class Resource(dict):
the resource was not found. the resource was not found.
""" """
response = self._raw_delete(session, **kwargs) response = self._raw_delete(session, microversion=microversion,
**kwargs)
kwargs = {} kwargs = {}
if error_message: if error_message:
kwargs['error_message'] = error_message kwargs['error_message'] = error_message
@ -1872,13 +1903,14 @@ class Resource(dict):
self._translate_response(response, has_body=False, **kwargs) self._translate_response(response, has_body=False, **kwargs)
return self return self
def _raw_delete(self, session, **kwargs): def _raw_delete(self, session, microversion=None, **kwargs):
if not self.allow_delete: if not self.allow_delete:
raise exceptions.MethodNotSupported(self, 'delete') raise exceptions.MethodNotSupported(self, 'delete')
request = self._prepare_request(**kwargs) request = self._prepare_request(**kwargs)
session = self._get_session(session) session = self._get_session(session)
microversion = self._get_microversion(session, action='delete') if microversion is None:
microversion = self._get_microversion(session, action='delete')
return session.delete( return session.delete(
request.url, headers=request.headers, microversion=microversion request.url, headers=request.headers, microversion=microversion
@ -1891,6 +1923,8 @@ class Resource(dict):
paginated=True, paginated=True,
base_path=None, base_path=None,
allow_unknown_params=False, allow_unknown_params=False,
*,
microversion=None,
**params, **params,
): ):
"""This method is a generator which yields resource objects. """This method is a generator which yields resource objects.
@ -1910,6 +1944,7 @@ class Resource(dict):
unknown query parameters. This allows getting list of 'filters' and unknown query parameters. This allows getting list of 'filters' and
passing everything known to the server. ``False`` will result in passing everything known to the server. ``False`` will result in
validation exception when unknown query parameters are passed. validation exception when unknown query parameters are passed.
:param str microversion: API version to override the negotiated one.
:param dict params: These keyword arguments are passed through the :param dict params: These keyword arguments are passed through the
:meth:`~openstack.resource.QueryParamter._transpose` method :meth:`~openstack.resource.QueryParamter._transpose` method
to find if any of them match expected query parameters to be sent to find if any of them match expected query parameters to be sent
@ -1929,7 +1964,8 @@ class Resource(dict):
raise exceptions.MethodNotSupported(cls, 'list') raise exceptions.MethodNotSupported(cls, 'list')
session = cls._get_session(session) session = cls._get_session(session)
microversion = cls._get_microversion(session, action='list') if microversion is None:
microversion = cls._get_microversion(session, action='list')
if base_path is None: if base_path is None:
base_path = cls.base_path base_path = cls.base_path
@ -2102,6 +2138,8 @@ class Resource(dict):
name_or_id, name_or_id,
ignore_missing=True, ignore_missing=True,
list_base_path=None, list_base_path=None,
*,
microversion=None,
**params, **params,
): ):
"""Find a resource by its name or id. """Find a resource by its name or id.
@ -2116,6 +2154,7 @@ class Resource(dict):
returned when attempting to find a nonexistent resource. returned when attempting to find a nonexistent resource.
:param str list_base_path: base_path to be used when need listing :param str list_base_path: base_path to be used when need listing
resources. resources.
:param str microversion: API version to override the negotiated one.
:param dict params: Any additional parameters to be passed into :param dict params: Any additional parameters to be passed into
underlying methods, such as to underlying methods, such as to
:meth:`~openstack.resource.Resource.existing` in order to pass on :meth:`~openstack.resource.Resource.existing` in order to pass on
@ -2134,7 +2173,7 @@ class Resource(dict):
match = cls.existing( match = cls.existing(
id=name_or_id, connection=session._get_connection(), **params id=name_or_id, connection=session._get_connection(), **params
) )
return match.fetch(session, **params) return match.fetch(session, microversion=microversion, **params)
except (exceptions.NotFoundException, exceptions.BadRequestException): except (exceptions.NotFoundException, exceptions.BadRequestException):
# NOTE(gtema): There are few places around openstack that return # NOTE(gtema): There are few places around openstack that return
# 400 if we try to GET resource and it doesn't exist. # 400 if we try to GET resource and it doesn't exist.

View File

@ -1528,15 +1528,19 @@ class TestResourceActions(base.TestCase):
def _test_create(self, cls, requires_id=False, prepend_key=False, def _test_create(self, cls, requires_id=False, prepend_key=False,
microversion=None, base_path=None, params=None, microversion=None, base_path=None, params=None,
id_marked_dirty=True): id_marked_dirty=True, explicit_microversion=None):
id = "id" if requires_id else None id = "id" if requires_id else None
sot = cls(id=id) sot = cls(id=id)
sot._prepare_request = mock.Mock(return_value=self.request) sot._prepare_request = mock.Mock(return_value=self.request)
sot._translate_response = mock.Mock() sot._translate_response = mock.Mock()
params = params or {} params = params or {}
kwargs = params.copy()
if explicit_microversion is not None:
kwargs['microversion'] = explicit_microversion
microversion = explicit_microversion
result = sot.create(self.session, prepend_key=prepend_key, result = sot.create(self.session, prepend_key=prepend_key,
base_path=base_path, **params) base_path=base_path, **kwargs)
id_is_dirty = ('id' in sot._body._dirty) id_is_dirty = ('id' in sot._body._dirty)
self.assertEqual(id_marked_dirty, id_is_dirty) self.assertEqual(id_marked_dirty, id_is_dirty)
@ -1591,6 +1595,17 @@ class TestResourceActions(base.TestCase):
self._test_create(Test, requires_id=True, prepend_key=True, self._test_create(Test, requires_id=True, prepend_key=True,
microversion='1.42') microversion='1.42')
def test_put_create_with_explicit_microversion(self):
class Test(resource.Resource):
service = self.service_name
base_path = self.base_path
allow_create = True
create_method = 'PUT'
_max_microversion = '1.99'
self._test_create(Test, requires_id=True, prepend_key=True,
explicit_microversion='1.42')
def test_put_create_with_params(self): def test_put_create_with_params(self):
class Test(resource.Resource): class Test(resource.Resource):
service = self.service_name service = self.service_name
@ -1679,6 +1694,29 @@ class TestResourceActions(base.TestCase):
sot._translate_response.assert_called_once_with(self.response) sot._translate_response.assert_called_once_with(self.response)
self.assertEqual(result, sot) self.assertEqual(result, sot)
def test_fetch_with_explicit_microversion(self):
class Test(resource.Resource):
service = self.service_name
base_path = self.base_path
allow_fetch = True
_max_microversion = '1.99'
sot = Test(id='id')
sot._prepare_request = mock.Mock(return_value=self.request)
sot._translate_response = mock.Mock()
result = sot.fetch(self.session, microversion='1.42')
sot._prepare_request.assert_called_once_with(
requires_id=True, base_path=None)
self.session.get.assert_called_once_with(
self.request.url, microversion='1.42', params={},
skip_cache=False)
self.assertEqual(sot.microversion, '1.42')
sot._translate_response.assert_called_once_with(self.response)
self.assertEqual(result, sot)
def test_fetch_not_requires_id(self): def test_fetch_not_requires_id(self):
result = self.sot.fetch(self.session, False) result = self.sot.fetch(self.session, False)
@ -1755,16 +1793,21 @@ class TestResourceActions(base.TestCase):
def _test_commit(self, commit_method='PUT', prepend_key=True, def _test_commit(self, commit_method='PUT', prepend_key=True,
has_body=True, microversion=None, has_body=True, microversion=None,
commit_args=None, expected_args=None, base_path=None): commit_args=None, expected_args=None, base_path=None,
explicit_microversion=None):
self.sot.commit_method = commit_method self.sot.commit_method = commit_method
# Need to make sot look dirty so we can attempt an update # Need to make sot look dirty so we can attempt an update
self.sot._body = mock.Mock() self.sot._body = mock.Mock()
self.sot._body.dirty = mock.Mock(return_value={"x": "y"}) self.sot._body.dirty = mock.Mock(return_value={"x": "y"})
commit_args = commit_args or {}
if explicit_microversion is not None:
commit_args['microversion'] = explicit_microversion
microversion = explicit_microversion
self.sot.commit(self.session, prepend_key=prepend_key, self.sot.commit(self.session, prepend_key=prepend_key,
has_body=has_body, base_path=base_path, has_body=has_body, base_path=base_path,
**(commit_args or {})) **commit_args)
self.sot._prepare_request.assert_called_once_with( self.sot._prepare_request.assert_called_once_with(
prepend_key=prepend_key, base_path=base_path) prepend_key=prepend_key, base_path=base_path)
@ -1826,6 +1869,10 @@ class TestResourceActions(base.TestCase):
commit_args={'retry_on_conflict': False}, commit_args={'retry_on_conflict': False},
expected_args={'retriable_status_codes': {503}}) expected_args={'retriable_status_codes': {503}})
def test_commit_put_explicit_microversion(self):
self._test_commit(commit_method='PUT', prepend_key=True, has_body=True,
explicit_microversion='1.42')
def test_commit_not_dirty(self): def test_commit_not_dirty(self):
self.sot._body = mock.Mock() self.sot._body = mock.Mock()
self.sot._body.dirty = dict() self.sot._body.dirty = dict()
@ -1926,6 +1973,29 @@ class TestResourceActions(base.TestCase):
self.response, has_body=False) self.response, has_body=False)
self.assertEqual(result, sot) self.assertEqual(result, sot)
def test_delete_with_explicit_microversion(self):
class Test(resource.Resource):
service = self.service_name
base_path = self.base_path
allow_delete = True
_max_microversion = '1.99'
sot = Test(id='id')
sot._prepare_request = mock.Mock(return_value=self.request)
sot._translate_response = mock.Mock()
result = sot.delete(self.session, microversion='1.42')
sot._prepare_request.assert_called_once_with()
self.session.delete.assert_called_once_with(
self.request.url,
headers='headers',
microversion='1.42')
sot._translate_response.assert_called_once_with(
self.response, has_body=False)
self.assertEqual(result, sot)
# NOTE: As list returns a generator, testing it requires consuming # NOTE: As list returns a generator, testing it requires consuming
# the generator. Wrap calls to self.sot.list in a `list` # the generator. Wrap calls to self.sot.list in a `list`
# and then test the results as a list of responses. # and then test the results as a list of responses.