Remove use of distutils

This has been removed in Python 3.12, which is preventing us using
ironicclient in those environments. Thankfully we only use it for
StrictVersion which we are using to parse microversions. This is
actually overkill here since we can always expect a version string like
'1.5', thus we do this parsing ourselves rather than drag in a new
library like 'microversion_parse'.

Change-Id: I7630ecfa2797c502868994302926bd2ac07d1ff7
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2024-07-25 13:09:21 +01:00
parent 85542f0caf
commit 54b878238c
2 changed files with 32 additions and 14 deletions

View File

@ -13,7 +13,6 @@
# 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 distutils.version import StrictVersion
import functools import functools
from http import client as http_client from http import client as http_client
import json import json
@ -48,17 +47,36 @@ _MAJOR_VERSION = 1
API_VERSION = '/v%d' % _MAJOR_VERSION API_VERSION = '/v%d' % _MAJOR_VERSION
API_VERSION_SELECTED_STATES = ('user', 'negotiated', 'cached', 'default') API_VERSION_SELECTED_STATES = ('user', 'negotiated', 'cached', 'default')
DEFAULT_MAX_RETRIES = 5 DEFAULT_MAX_RETRIES = 5
DEFAULT_RETRY_INTERVAL = 2 DEFAULT_RETRY_INTERVAL = 2
SENSITIVE_HEADERS = ('X-Auth-Token',) SENSITIVE_HEADERS = ('X-Auth-Token',)
SUPPORTED_ENDPOINT_SCHEME = ('http', 'https') SUPPORTED_ENDPOINT_SCHEME = ('http', 'https')
_API_VERSION_RE = re.compile(r'/+(v%d)?/*$' % _MAJOR_VERSION) _API_VERSION_RE = re.compile(r'/+(v%d)?/*$' % _MAJOR_VERSION)
@functools.total_ordering
class _Version:
_version_re = re.compile(r'^(\d) \. (\d+)$', re.VERBOSE | re.ASCII)
def __init__(self, version):
match = self._version_re.match(version)
if not match:
raise ValueError('invalid version number %s' % version)
major, minor = match.group(1, 2)
self.version = (int(major), int(minor))
def __str__(self):
return '.'.join(str(v) for v in self.version)
def __eq__(self, other):
return self.version == other.version
def __lt__(self, other):
return self.version < other.version
def _trim_endpoint_api_version(url): def _trim_endpoint_api_version(url):
"""Trim API version and trailing slash from endpoint.""" """Trim API version and trailing slash from endpoint."""
return re.sub(_API_VERSION_RE, '', url) return re.sub(_API_VERSION_RE, '', url)
@ -159,7 +177,8 @@ class VersionNegotiationMixin(object):
resp = _query_server(conn) resp = _query_server(conn)
min_ver, max_ver = self._parse_version_headers(resp) min_ver, max_ver = self._parse_version_headers(resp)
# Reset the maximum version that we permit # Reset the maximum version that we permit
if StrictVersion(max_ver) > StrictVersion(LATEST_VERSION):
if _Version(max_ver) > _Version(LATEST_VERSION):
LOG.debug("Remote API version %(max_ver)s is greater than the " LOG.debug("Remote API version %(max_ver)s is greater than the "
"version supported by ironicclient. Maximum available " "version supported by ironicclient. Maximum available "
"version is %(client_ver)s", "version is %(client_ver)s",
@ -198,8 +217,8 @@ class VersionNegotiationMixin(object):
negotiated_ver = max_ver negotiated_ver = max_ver
else: else:
negotiated_ver = str( negotiated_ver = str(
min(StrictVersion(requested_version), min(_Version(requested_version), _Version(max_ver))
StrictVersion(max_ver))) )
elif isinstance(requested_version, list): elif isinstance(requested_version, list):
if 'latest' in requested_version: if 'latest' in requested_version:
@ -212,8 +231,8 @@ class VersionNegotiationMixin(object):
versions = [] versions = []
for version in requested_version: for version in requested_version:
if min_ver <= StrictVersion(version) <= max_ver: if _Version(min_ver) <= _Version(version) <= _Version(max_ver):
versions.append(StrictVersion(version)) versions.append(_Version(version))
if versions: if versions:
negotiated_ver = str(max(versions)) negotiated_ver = str(max(versions))
else: else:
@ -232,8 +251,9 @@ class VersionNegotiationMixin(object):
"or a list of string values representing API versions.") "or a list of string values representing API versions.")
% {'req': requested_version})) % {'req': requested_version}))
if StrictVersion(negotiated_ver) < StrictVersion(min_ver): if _Version(negotiated_ver) < _Version(min_ver):
negotiated_ver = min_ver negotiated_ver = min_ver
# server handles microversions, but doesn't support # server handles microversions, but doesn't support
# the requested version, so try a negotiated version # the requested version, so try a negotiated version
self.api_version_select_state = 'negotiated' self.api_version_select_state = 'negotiated'
@ -248,10 +268,8 @@ class VersionNegotiationMixin(object):
return negotiated_ver return negotiated_ver
def _generic_parse_version_headers(self, accessor_func): def _generic_parse_version_headers(self, accessor_func):
min_ver = accessor_func('X-OpenStack-Ironic-API-Minimum-Version', min_ver = accessor_func('X-OpenStack-Ironic-API-Minimum-Version', None)
None) max_ver = accessor_func('X-OpenStack-Ironic-API-Maximum-Version', None)
max_ver = accessor_func('X-OpenStack-Ironic-API-Maximum-Version',
None)
return min_ver, max_ver return min_ver, max_ver
def _parse_version_headers(self, accessor_func): def _parse_version_headers(self, accessor_func):

View File

@ -232,7 +232,7 @@ class VersionNegotiationMixinTest(utils.BaseTestCase):
mock_conn = mock.MagicMock() mock_conn = mock.MagicMock()
self.test_object.api_version_select_state = 'user' self.test_object.api_version_select_state = 'user'
self.test_object.os_ironic_api_version = ['1.1', '1.6', '1.25', self.test_object.os_ironic_api_version = ['1.1', '1.6', '1.25',
'1.26', '1.26.1', '1.27', '1.26', '1.27', '1.28',
'1.30'] '1.30']
result = self.test_object.negotiate_version(mock_conn, self.response) result = self.test_object.negotiate_version(mock_conn, self.response)
self.assertEqual('1.26', result) self.assertEqual('1.26', result)