Add version foot protector
python-novaclient is frozen and won't accept support for new microversions. Attempting to use a microversion newer than what we support is likely to break things is unexpected ways. Save people's feet from this shotgun by introducing a new helper function, 'check_version', that we use to ensure we actually support the version in question. We rework the existing check_major_version to make it much faster since we only care about v2 and have done so for a very long time. Change-Id: I4d3fba6fcbf785ef3309b8f9eee45e31c7919777 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
@@ -14,7 +14,6 @@
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
import re
|
||||
import traceback
|
||||
import warnings
|
||||
@@ -195,35 +194,59 @@ class VersionedMethod(object):
|
||||
|
||||
|
||||
def get_available_major_versions():
|
||||
# NOTE(andreykurilin): available clients version should not be
|
||||
# hardcoded, so let's discover them.
|
||||
matcher = re.compile(r"v[0-9]*$")
|
||||
submodules = pkgutil.iter_modules([os.path.dirname(__file__)])
|
||||
available_versions = [name[1:] for loader, name, ispkg in submodules
|
||||
if matcher.search(name)]
|
||||
|
||||
return available_versions
|
||||
return ['2']
|
||||
|
||||
|
||||
def check_major_version(api_version):
|
||||
"""Checks major part of ``APIVersion`` obj is supported.
|
||||
|
||||
:raises novaclient.exceptions.UnsupportedVersion: if major part is not
|
||||
supported
|
||||
supported
|
||||
"""
|
||||
available_versions = get_available_major_versions()
|
||||
if (not api_version.is_null() and
|
||||
str(api_version.ver_major) not in available_versions):
|
||||
if len(available_versions) == 1:
|
||||
msg = _("Invalid client version '%(version)s'. "
|
||||
"Major part should be '%(major)s'") % {
|
||||
"version": api_version.get_string(),
|
||||
"major": available_versions[0]}
|
||||
else:
|
||||
msg = _("Invalid client version '%(version)s'. "
|
||||
"Major part must be one of: '%(major)s'") % {
|
||||
"version": api_version.get_string(),
|
||||
"major": ", ".join(available_versions)}
|
||||
if api_version.is_null():
|
||||
return
|
||||
|
||||
if api_version.ver_major == 2:
|
||||
return
|
||||
|
||||
msg = _(
|
||||
"Invalid client version '%(version)s'. Major part should be '2'"
|
||||
) % {"version": api_version.get_string()}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
|
||||
def check_version(api_version):
|
||||
"""Checks if version of ``APIVersion`` is supported.
|
||||
|
||||
Provided as an alternative to :func:`check_major_version` to avoid changing
|
||||
the behavior of that function.
|
||||
|
||||
:raises novaclient.exceptions.UnsupportedVersion: if major part is not
|
||||
supported
|
||||
"""
|
||||
if api_version.is_null():
|
||||
return
|
||||
|
||||
# we can't use API_MIN_VERSION since we do support 2.0 (which is 2.1 but
|
||||
# less strict)
|
||||
if api_version < APIVersion('2.0'):
|
||||
msg = _(
|
||||
"Invalid client version '%(version)s'. "
|
||||
"Min version supported is '%(min_version)s'"
|
||||
) % {
|
||||
"version": api_version.get_string(),
|
||||
"min_version": novaclient.API_MIN_VERSION,
|
||||
}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
if api_version > novaclient.API_MAX_VERSION:
|
||||
msg = _(
|
||||
"Invalid client version '%(version)s'. "
|
||||
"Max version supported is '%(max_version)s'"
|
||||
) % {
|
||||
"version": api_version.get_string(),
|
||||
"max_version": novaclient.API_MAX_VERSION,
|
||||
}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
|
||||
|
||||
@@ -51,6 +51,12 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
||||
self.timings = kwargs.pop('timings', False)
|
||||
self.api_version = kwargs.pop('api_version', None)
|
||||
self.api_version = self.api_version or api_versions.APIVersion()
|
||||
|
||||
if isinstance(self.api_version, str):
|
||||
self.api_version = api_versions.APIVersion(self.api_version)
|
||||
|
||||
api_versions.check_version(self.api_version)
|
||||
|
||||
super(SessionClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
|
||||
@@ -343,6 +343,27 @@ class WrapsTestCase(utils.TestCase):
|
||||
self.assertEqual(expected_name, fake_func.__id__)
|
||||
|
||||
|
||||
class CheckVersionTestCase(utils.TestCase):
|
||||
def test_version_unsupported(self):
|
||||
for version in ('1.0', '1.5', '1.100'):
|
||||
with self.subTest('version too old', version=version):
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion,
|
||||
api_versions.check_version,
|
||||
api_versions.APIVersion(version))
|
||||
|
||||
for version in ('2.97', '2.101', '3.0'):
|
||||
with self.subTest('version too new', version=version):
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion,
|
||||
api_versions.check_version,
|
||||
api_versions.APIVersion(version))
|
||||
|
||||
for version in ('2.1', '2.57', '2.96'):
|
||||
with self.subTest('version just right', version=version):
|
||||
api_versions.check_version(api_versions.APIVersion(version))
|
||||
|
||||
|
||||
class DiscoverVersionTestCase(utils.TestCase):
|
||||
def setUp(self):
|
||||
super(DiscoverVersionTestCase, self).setUp()
|
||||
|
||||
Reference in New Issue
Block a user