From 0b84e5be0d065ed439590d1c0eb7f7ba7a4d125e Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 25 Sep 2024 00:52:33 +0900 Subject: [PATCH] cinder: Add mechanism to compare api microversion Introduce the capability to detect and compare available api microversion to implement features which require specific microversion. Story: 2011229 Task: 51085 Change-Id: Ic9fa21015dd8a49414b78cbe13a6047f1c80a21f --- heat/common/config.py | 6 ++++ heat/engine/clients/os/cinder.py | 33 ++++++++++++++----- heat/tests/aws/test_volume.py | 7 ++-- heat/tests/clients/test_cinder_client.py | 3 ++ .../openstack/cinder/test_volume_utils.py | 3 ++ ...der_api_microversion-bfdf420bf214f7aa.yaml | 6 ++++ 6 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/max_cinder_api_microversion-bfdf420bf214f7aa.yaml diff --git a/heat/common/config.py b/heat/common/config.py index c070cabcf7..39791b4fff 100644 --- a/heat/common/config.py +++ b/heat/common/config.py @@ -197,6 +197,12 @@ engine_opts = [ 'this limitation, any nova feature supported with ' 'microversion number above max_nova_api_microversion ' 'will not be available.')), + cfg.StrOpt('max_cinder_api_microversion', + regex=r'^3\.\d+$', + help=_('Maximum cinder API version for client plugin. With ' + 'this limitation, any cinder feature supported with ' + 'microversion number above max_cinder_api_microversion ' + 'will not be available.')), cfg.StrOpt('max_ironic_api_microversion', regex=r'^\d+\.\d+$', help=_('Maximum ironic API version for client plugin. With ' diff --git a/heat/engine/clients/os/cinder.py b/heat/engine/clients/os/cinder.py index 55b7755424..8e5cd612aa 100644 --- a/heat/engine/clients/os/cinder.py +++ b/heat/engine/clients/os/cinder.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient import client as cc from cinderclient import exceptions from keystoneauth1 import exceptions as ks_exceptions @@ -20,6 +21,7 @@ from oslo_log import log as logging from heat.common import exception from heat.common.i18n import _ from heat.engine.clients import client_plugin +from heat.engine.clients import microversion_mixin from heat.engine.clients import os as os_client from heat.engine import constraints @@ -29,13 +31,18 @@ LOG = logging.getLogger(__name__) CLIENT_NAME = 'cinder' -class CinderClientPlugin(os_client.ExtensionMixin, +class CinderClientPlugin(microversion_mixin.MicroversionMixin, + os_client.ExtensionMixin, client_plugin.ClientPlugin): exceptions_module = exceptions service_types = [VOLUME_V3] = ['volumev3'] + CINDER_API_VERSION = '3' + + max_microversion = cfg.CONF.max_cinder_api_microversion + def get_volume_api_version(self): '''Returns the most recent API version.''' self.interface = self._get_client_option(CLIENT_NAME, 'endpoint_type') @@ -43,28 +50,38 @@ class CinderClientPlugin(os_client.ExtensionMixin, self.context.keystone_session.get_endpoint( service_type=self.VOLUME_V3, interface=self.interface) - self.service_type = self.VOLUME_V3 - self.client_version = '3' except ks_exceptions.EndpointNotFound: raise exception.Error(_('No volume service available.')) - def _create(self): + def _create(self, version=None): + if version is None: + version = self.CINDER_API_VERSION self.get_volume_api_version() - extensions = cc.discover_extensions(self.client_version) + extensions = cc.discover_extensions(self.CINDER_API_VERSION) args = { 'session': self.context.keystone_session, 'extensions': extensions, 'interface': self.interface, - 'service_type': self.service_type, + 'service_type': self.VOLUME_V3, 'region_name': self._get_region_name(), 'connect_retries': cfg.CONF.client_retry_limit, 'http_log_debug': self._get_client_option(CLIENT_NAME, 'http_log_debug') } - - client = cc.Client(self.client_version, **args) + client = cc.Client(version, **args) return client + def get_max_microversion(self): + if not self.max_microversion: + self.max_microversion = api_versions.get_highest_version( + self._create()).get_string() + return self.max_microversion + + def is_version_supported(self, version): + api_ver = api_versions.APIVersion(version) + max_api_ver = api_versions.APIVersion(self.get_max_microversion()) + return max_api_ver >= api_ver + @os_client.MEMOIZE_EXTENSIONS def _list_extensions(self): extensions = self.client().list_extensions.show_all() diff --git a/heat/tests/aws/test_volume.py b/heat/tests/aws/test_volume.py index a2dfbf91ca..fe2cae7e1e 100644 --- a/heat/tests/aws/test_volume.py +++ b/heat/tests/aws/test_volume.py @@ -14,6 +14,7 @@ import copy from unittest import mock +from cinderclient import api_versions from cinderclient import exceptions as cinder_exp from oslo_config import cfg @@ -623,7 +624,8 @@ class VolumeTest(vt_base.VolumeTestCase): self.create_volume(self.t, stack, 'DataVolume', no_create=True) - cinder.CinderClientPlugin._create.assert_called_once_with() + cinder.CinderClientPlugin._create.assert_called_once_with( + version=api_versions.MAX_VERSION) self.m_restore.assert_called_once_with('backup-123') self.cinder_fc.volumes.get.assert_called_with('vol-123') self.cinder_fc.volumes.update.assert_called_once_with( @@ -651,7 +653,8 @@ class VolumeTest(vt_base.VolumeTestCase): self.assertIn('Went to status error due to "Unknown"', str(ex)) - cinder.CinderClientPlugin._create.assert_called_once_with() + cinder.CinderClientPlugin._create.assert_called_once_with( + version=api_versions.MAX_VERSION) self.m_restore.assert_called_once_with('backup-123') self.cinder_fc.volumes.update.assert_called_once_with( fv.id, description=vol_name, name=vol_name) diff --git a/heat/tests/clients/test_cinder_client.py b/heat/tests/clients/test_cinder_client.py index f1810e899f..4cc121f3fd 100644 --- a/heat/tests/clients/test_cinder_client.py +++ b/heat/tests/clients/test_cinder_client.py @@ -15,6 +15,7 @@ from unittest import mock import uuid +from cinderclient import api_versions as cinder_api_versions from cinderclient import exceptions as cinder_exc from keystoneauth1 import exceptions as ks_exceptions @@ -160,6 +161,8 @@ class CinderClientAPIVersionTest(common.HeatTestCase): def test_cinder_api_v3(self): ctx = utils.dummy_context() self.patchobject(ctx.keystone_session, 'get_endpoint') + ctx.clients.client_plugin( + 'cinder').max_microversion = cinder_api_versions.MAX_VERSION client = ctx.clients.client('cinder') self.assertEqual('3.0', client.version) diff --git a/heat/tests/openstack/cinder/test_volume_utils.py b/heat/tests/openstack/cinder/test_volume_utils.py index 516d312077..51e2966b24 100644 --- a/heat/tests/openstack/cinder/test_volume_utils.py +++ b/heat/tests/openstack/cinder/test_volume_utils.py @@ -13,6 +13,7 @@ from unittest import mock +from cinderclient import api_versions from cinderclient.v3 import client as cinderclient from heat.engine.clients.os import cinder @@ -32,6 +33,8 @@ class VolumeTestCase(common.HeatTestCase): self.fc = fakes_nova.FakeClient() self.cinder_fc = cinderclient.Client('username', 'password') self.cinder_fc.volume_api_version = 3 + self.patchobject(cinder.CinderClientPlugin, 'get_max_microversion', + return_value=api_versions.MAX_VERSION) self.patchobject(cinder.CinderClientPlugin, '_create', return_value=self.cinder_fc) self.patchobject(nova.NovaClientPlugin, 'client', diff --git a/releasenotes/notes/max_cinder_api_microversion-bfdf420bf214f7aa.yaml b/releasenotes/notes/max_cinder_api_microversion-bfdf420bf214f7aa.yaml new file mode 100644 index 0000000000..4cbe02e0f3 --- /dev/null +++ b/releasenotes/notes/max_cinder_api_microversion-bfdf420bf214f7aa.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The new ``[DEFAULT] max_cinder_api_microversion`` option has been added. + This option overrides the maximum API microversion supported by Cinder, + which is detected automatically by default.