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
This commit is contained in:
Takashi Kajinami 2024-09-25 00:52:33 +09:00
parent 0135d50659
commit 0b84e5be0d
6 changed files with 48 additions and 10 deletions

View File

@ -197,6 +197,12 @@ engine_opts = [
'this limitation, any nova feature supported with ' 'this limitation, any nova feature supported with '
'microversion number above max_nova_api_microversion ' 'microversion number above max_nova_api_microversion '
'will not be available.')), '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', cfg.StrOpt('max_ironic_api_microversion',
regex=r'^\d+\.\d+$', regex=r'^\d+\.\d+$',
help=_('Maximum ironic API version for client plugin. With ' help=_('Maximum ironic API version for client plugin. With '

View File

@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from cinderclient import api_versions
from cinderclient import client as cc from cinderclient import client as cc
from cinderclient import exceptions from cinderclient import exceptions
from keystoneauth1 import exceptions as ks_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 import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.engine.clients import client_plugin 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.clients import os as os_client
from heat.engine import constraints from heat.engine import constraints
@ -29,13 +31,18 @@ LOG = logging.getLogger(__name__)
CLIENT_NAME = 'cinder' CLIENT_NAME = 'cinder'
class CinderClientPlugin(os_client.ExtensionMixin, class CinderClientPlugin(microversion_mixin.MicroversionMixin,
os_client.ExtensionMixin,
client_plugin.ClientPlugin): client_plugin.ClientPlugin):
exceptions_module = exceptions exceptions_module = exceptions
service_types = [VOLUME_V3] = ['volumev3'] service_types = [VOLUME_V3] = ['volumev3']
CINDER_API_VERSION = '3'
max_microversion = cfg.CONF.max_cinder_api_microversion
def get_volume_api_version(self): def get_volume_api_version(self):
'''Returns the most recent API version.''' '''Returns the most recent API version.'''
self.interface = self._get_client_option(CLIENT_NAME, 'endpoint_type') 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( self.context.keystone_session.get_endpoint(
service_type=self.VOLUME_V3, service_type=self.VOLUME_V3,
interface=self.interface) interface=self.interface)
self.service_type = self.VOLUME_V3
self.client_version = '3'
except ks_exceptions.EndpointNotFound: except ks_exceptions.EndpointNotFound:
raise exception.Error(_('No volume service available.')) 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() self.get_volume_api_version()
extensions = cc.discover_extensions(self.client_version) extensions = cc.discover_extensions(self.CINDER_API_VERSION)
args = { args = {
'session': self.context.keystone_session, 'session': self.context.keystone_session,
'extensions': extensions, 'extensions': extensions,
'interface': self.interface, 'interface': self.interface,
'service_type': self.service_type, 'service_type': self.VOLUME_V3,
'region_name': self._get_region_name(), 'region_name': self._get_region_name(),
'connect_retries': cfg.CONF.client_retry_limit, 'connect_retries': cfg.CONF.client_retry_limit,
'http_log_debug': self._get_client_option(CLIENT_NAME, 'http_log_debug': self._get_client_option(CLIENT_NAME,
'http_log_debug') 'http_log_debug')
} }
client = cc.Client(version, **args)
client = cc.Client(self.client_version, **args)
return client 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 @os_client.MEMOIZE_EXTENSIONS
def _list_extensions(self): def _list_extensions(self):
extensions = self.client().list_extensions.show_all() extensions = self.client().list_extensions.show_all()

View File

@ -14,6 +14,7 @@
import copy import copy
from unittest import mock from unittest import mock
from cinderclient import api_versions
from cinderclient import exceptions as cinder_exp from cinderclient import exceptions as cinder_exp
from oslo_config import cfg from oslo_config import cfg
@ -623,7 +624,8 @@ class VolumeTest(vt_base.VolumeTestCase):
self.create_volume(self.t, stack, 'DataVolume', no_create=True) 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.m_restore.assert_called_once_with('backup-123')
self.cinder_fc.volumes.get.assert_called_with('vol-123') self.cinder_fc.volumes.get.assert_called_with('vol-123')
self.cinder_fc.volumes.update.assert_called_once_with( 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"', self.assertIn('Went to status error due to "Unknown"',
str(ex)) 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.m_restore.assert_called_once_with('backup-123')
self.cinder_fc.volumes.update.assert_called_once_with( self.cinder_fc.volumes.update.assert_called_once_with(
fv.id, description=vol_name, name=vol_name) fv.id, description=vol_name, name=vol_name)

View File

@ -15,6 +15,7 @@
from unittest import mock from unittest import mock
import uuid import uuid
from cinderclient import api_versions as cinder_api_versions
from cinderclient import exceptions as cinder_exc from cinderclient import exceptions as cinder_exc
from keystoneauth1 import exceptions as ks_exceptions from keystoneauth1 import exceptions as ks_exceptions
@ -160,6 +161,8 @@ class CinderClientAPIVersionTest(common.HeatTestCase):
def test_cinder_api_v3(self): def test_cinder_api_v3(self):
ctx = utils.dummy_context() ctx = utils.dummy_context()
self.patchobject(ctx.keystone_session, 'get_endpoint') self.patchobject(ctx.keystone_session, 'get_endpoint')
ctx.clients.client_plugin(
'cinder').max_microversion = cinder_api_versions.MAX_VERSION
client = ctx.clients.client('cinder') client = ctx.clients.client('cinder')
self.assertEqual('3.0', client.version) self.assertEqual('3.0', client.version)

View File

@ -13,6 +13,7 @@
from unittest import mock from unittest import mock
from cinderclient import api_versions
from cinderclient.v3 import client as cinderclient from cinderclient.v3 import client as cinderclient
from heat.engine.clients.os import cinder from heat.engine.clients.os import cinder
@ -32,6 +33,8 @@ class VolumeTestCase(common.HeatTestCase):
self.fc = fakes_nova.FakeClient() self.fc = fakes_nova.FakeClient()
self.cinder_fc = cinderclient.Client('username', 'password') self.cinder_fc = cinderclient.Client('username', 'password')
self.cinder_fc.volume_api_version = 3 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', self.patchobject(cinder.CinderClientPlugin, '_create',
return_value=self.cinder_fc) return_value=self.cinder_fc)
self.patchobject(nova.NovaClientPlugin, 'client', self.patchobject(nova.NovaClientPlugin, 'client',

View File

@ -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.