Allow passing explicit microversions to Resource methods
Sometimes users may want a specific behavior of a certain microversion rather than just the most recent supported one. For example, Ironic only supports creating nodes directly in the "available" state before 1.11. Change-Id: I2458650a9ce30440b5e29b940eaf1df60239ff32
This commit is contained in:
parent
6bc56b0eb6
commit
915da1e576
@ -317,13 +317,14 @@ class Object(_base.BaseResource):
|
||||
self._translate_response(response, has_body=False)
|
||||
return self
|
||||
|
||||
def _raw_delete(self, session):
|
||||
def _raw_delete(self, session, microversion=None):
|
||||
if not self.allow_delete:
|
||||
raise exceptions.MethodNotSupported(self, "delete")
|
||||
|
||||
request = self._prepare_request()
|
||||
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:
|
||||
# Fetch metadata to determine SLO flag
|
||||
|
@ -1384,7 +1384,15 @@ class Resource(dict):
|
||||
|
||||
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.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
@ -1394,6 +1402,7 @@ class Resource(dict):
|
||||
True.
|
||||
:param str base_path: Base part of the URI for creating resources, if
|
||||
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.
|
||||
:return: This :class:`Resource` instance.
|
||||
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
|
||||
@ -1403,7 +1412,8 @@ class Resource(dict):
|
||||
raise exceptions.MethodNotSupported(self, 'create')
|
||||
|
||||
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 = (
|
||||
self.create_requires_id
|
||||
if self.create_requires_id is not None
|
||||
@ -1464,6 +1474,8 @@ class Resource(dict):
|
||||
data,
|
||||
prepend_key=True,
|
||||
base_path=None,
|
||||
*,
|
||||
microversion=None,
|
||||
**params,
|
||||
):
|
||||
"""Create multiple remote resources based on this class and data.
|
||||
@ -1476,6 +1488,7 @@ class Resource(dict):
|
||||
True.
|
||||
:param str base_path: Base part of the URI for creating resources, if
|
||||
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.
|
||||
|
||||
:return: A generator of :class:`Resource` objects.
|
||||
@ -1493,7 +1506,8 @@ class Resource(dict):
|
||||
raise ValueError('Invalid data passed: %s' % data)
|
||||
|
||||
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 = (
|
||||
cls.create_requires_id
|
||||
if cls.create_requires_id is not None
|
||||
@ -1566,6 +1580,8 @@ class Resource(dict):
|
||||
base_path=None,
|
||||
error_message=None,
|
||||
skip_cache=False,
|
||||
*,
|
||||
microversion=None,
|
||||
**params,
|
||||
):
|
||||
"""Get a remote resource based on this instance.
|
||||
@ -1580,6 +1596,7 @@ class Resource(dict):
|
||||
requested object does not exist.
|
||||
:param bool skip_cache: A boolean indicating whether optional API
|
||||
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.
|
||||
:return: This :class:`Resource` instance.
|
||||
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
|
||||
@ -1594,7 +1611,8 @@ class Resource(dict):
|
||||
requires_id=requires_id, base_path=base_path
|
||||
)
|
||||
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(
|
||||
request.url,
|
||||
microversion=microversion,
|
||||
@ -1609,13 +1627,14 @@ class Resource(dict):
|
||||
self._translate_response(response, **kwargs)
|
||||
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.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||
:param str base_path: Base part of the URI for fetching resources, if
|
||||
different from :data:`~openstack.resource.Resource.base_path`.
|
||||
:param str microversion: API version to override the negotiated one.
|
||||
|
||||
:return: This :class:`Resource` instance.
|
||||
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
|
||||
@ -1627,7 +1646,8 @@ class Resource(dict):
|
||||
raise exceptions.MethodNotSupported(self, 'head')
|
||||
|
||||
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)
|
||||
response = session.head(request.url, microversion=microversion)
|
||||
@ -1650,6 +1670,8 @@ class Resource(dict):
|
||||
has_body=True,
|
||||
retry_on_conflict=None,
|
||||
base_path=None,
|
||||
*,
|
||||
microversion=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Commit the state of the instance to the remote resource.
|
||||
@ -1663,6 +1685,7 @@ class Resource(dict):
|
||||
CONFLICT (409). Value of ``None`` leaves the `Adapter` defaults.
|
||||
:param str base_path: Base part of the URI for modifying resources, if
|
||||
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
|
||||
_prepare_request()
|
||||
|
||||
@ -1688,7 +1711,8 @@ class Resource(dict):
|
||||
request = self._prepare_request(
|
||||
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(
|
||||
session,
|
||||
@ -1774,6 +1798,8 @@ class Resource(dict):
|
||||
has_body=True,
|
||||
retry_on_conflict=None,
|
||||
base_path=None,
|
||||
*,
|
||||
microversion=None,
|
||||
):
|
||||
"""Patch the remote resource.
|
||||
|
||||
@ -1792,6 +1818,7 @@ class Resource(dict):
|
||||
CONFLICT (409). Value of ``None`` leaves the `Adapter` defaults.
|
||||
:param str base_path: Base part of the URI for modifying resources, if
|
||||
different from :data:`~openstack.resource.Resource.base_path`.
|
||||
:param str microversion: API version to override the negotiated one.
|
||||
|
||||
:return: This :class:`Resource` instance.
|
||||
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
|
||||
@ -1810,7 +1837,8 @@ class Resource(dict):
|
||||
request = self._prepare_request(
|
||||
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:
|
||||
request.body += self._convert_patch(patch)
|
||||
|
||||
@ -1823,11 +1851,13 @@ class Resource(dict):
|
||||
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.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
: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
|
||||
_prepare_request()
|
||||
|
||||
@ -1838,7 +1868,8 @@ class Resource(dict):
|
||||
the resource was not found.
|
||||
"""
|
||||
|
||||
response = self._raw_delete(session, **kwargs)
|
||||
response = self._raw_delete(session, microversion=microversion,
|
||||
**kwargs)
|
||||
kwargs = {}
|
||||
if error_message:
|
||||
kwargs['error_message'] = error_message
|
||||
@ -1846,13 +1877,14 @@ class Resource(dict):
|
||||
self._translate_response(response, has_body=False, **kwargs)
|
||||
return self
|
||||
|
||||
def _raw_delete(self, session, **kwargs):
|
||||
def _raw_delete(self, session, microversion=None, **kwargs):
|
||||
if not self.allow_delete:
|
||||
raise exceptions.MethodNotSupported(self, 'delete')
|
||||
|
||||
request = self._prepare_request(**kwargs)
|
||||
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(
|
||||
request.url, headers=request.headers, microversion=microversion
|
||||
@ -1865,6 +1897,8 @@ class Resource(dict):
|
||||
paginated=True,
|
||||
base_path=None,
|
||||
allow_unknown_params=False,
|
||||
*,
|
||||
microversion=None,
|
||||
**params,
|
||||
):
|
||||
"""This method is a generator which yields resource objects.
|
||||
@ -1884,6 +1918,7 @@ class Resource(dict):
|
||||
unknown query parameters. This allows getting list of 'filters' and
|
||||
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 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
|
||||
@ -1903,7 +1938,8 @@ class Resource(dict):
|
||||
raise exceptions.MethodNotSupported(cls, 'list')
|
||||
|
||||
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:
|
||||
base_path = cls.base_path
|
||||
@ -2076,6 +2112,8 @@ class Resource(dict):
|
||||
name_or_id,
|
||||
ignore_missing=True,
|
||||
list_base_path=None,
|
||||
*,
|
||||
microversion=None,
|
||||
**params,
|
||||
):
|
||||
"""Find a resource by its name or id.
|
||||
@ -2090,6 +2128,7 @@ class Resource(dict):
|
||||
returned when attempting to find a nonexistent resource.
|
||||
:param str list_base_path: base_path to be used when need listing
|
||||
resources.
|
||||
:param str microversion: API version to override the negotiated one.
|
||||
:param dict params: Any additional parameters to be passed into
|
||||
underlying methods, such as to
|
||||
:meth:`~openstack.resource.Resource.existing` in order to pass on
|
||||
@ -2108,7 +2147,7 @@ class Resource(dict):
|
||||
match = cls.existing(
|
||||
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):
|
||||
# NOTE(gtema): There are few places around openstack that return
|
||||
# 400 if we try to GET resource and it doesn't exist.
|
||||
|
@ -1512,15 +1512,19 @@ class TestResourceActions(base.TestCase):
|
||||
|
||||
def _test_create(self, cls, requires_id=False, prepend_key=False,
|
||||
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
|
||||
sot = cls(id=id)
|
||||
sot._prepare_request = mock.Mock(return_value=self.request)
|
||||
sot._translate_response = mock.Mock()
|
||||
|
||||
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,
|
||||
base_path=base_path, **params)
|
||||
base_path=base_path, **kwargs)
|
||||
|
||||
id_is_dirty = ('id' in sot._body._dirty)
|
||||
self.assertEqual(id_marked_dirty, id_is_dirty)
|
||||
@ -1575,6 +1579,17 @@ class TestResourceActions(base.TestCase):
|
||||
self._test_create(Test, requires_id=True, prepend_key=True,
|
||||
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):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
@ -1663,6 +1678,29 @@ class TestResourceActions(base.TestCase):
|
||||
sot._translate_response.assert_called_once_with(self.response)
|
||||
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):
|
||||
result = self.sot.fetch(self.session, False)
|
||||
|
||||
@ -1739,16 +1777,21 @@ class TestResourceActions(base.TestCase):
|
||||
|
||||
def _test_commit(self, commit_method='PUT', prepend_key=True,
|
||||
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
|
||||
|
||||
# Need to make sot look dirty so we can attempt an update
|
||||
self.sot._body = mock.Mock()
|
||||
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,
|
||||
has_body=has_body, base_path=base_path,
|
||||
**(commit_args or {}))
|
||||
**commit_args)
|
||||
|
||||
self.sot._prepare_request.assert_called_once_with(
|
||||
prepend_key=prepend_key, base_path=base_path)
|
||||
@ -1810,6 +1853,10 @@ class TestResourceActions(base.TestCase):
|
||||
commit_args={'retry_on_conflict': False},
|
||||
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):
|
||||
self.sot._body = mock.Mock()
|
||||
self.sot._body.dirty = dict()
|
||||
@ -1910,6 +1957,29 @@ class TestResourceActions(base.TestCase):
|
||||
self.response, has_body=False)
|
||||
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
|
||||
# the generator. Wrap calls to self.sot.list in a `list`
|
||||
# and then test the results as a list of responses.
|
||||
|
Loading…
x
Reference in New Issue
Block a user