Tobiko adapted to python3.12

Updated requirements and pre-commit-config files to support python3.12.

Tests from tobiko/tests/functional/run have been removed to avoid
failures on tobiko-infrared-centos-9 with multiprocessing.pool module:
https://github.com/dask/dask/issues/5806
These tests were going to be removed in any case at:
https://review.opendev.org/c/x/tobiko/+/936633

TODO: after updating ansible-lint version used to 6.21.1, due to the
big amount of failures, ansible-lint is mostly skipped (see
.ansible-lint file) - this will be fixed in a follow up patch.

Change-Id: I2768cd8d0c2b5f015f1beb0e42dae67dd24f97dd
This commit is contained in:
Eduardo Olivares 2024-12-02 11:50:23 +01:00
parent 7bfd11555a
commit 926fc6326f
21 changed files with 53 additions and 198 deletions

View File

@ -1,7 +1,16 @@
--- ---
exclude_paths: exclude_paths:
- ./roles/infrared/ - roles/infrared/
- zuul.d/
- .*.yaml
- tobiko/
# TODO(eolivare) remove the following excluded_paths after having fixed
# ansible-lint errors
- infrared_plugin/
- roles/
- playbooks/
skip_list: skip_list:
- '206' - '206'

View File

@ -27,7 +27,7 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/pycqa/flake8.git - repo: https://github.com/pycqa/flake8.git
rev: '3.8.4' # pick a git hash / tag to point to rev: '6.1.0' # pick a git hash / tag to point to
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [flake8-import-order] additional_dependencies: [flake8-import-order]
@ -43,7 +43,7 @@ repos:
# args: [--ignore-missing-imports] # args: [--ignore-missing-imports]
- repo: https://github.com/ansible/ansible-lint.git - repo: https://github.com/ansible/ansible-lint.git
rev: v4.2.0 rev: v6.21.1
hooks: hooks:
- id: ansible-lint - id: ansible-lint
files: \.(yaml|yml)$ files: \.(yaml|yml)$

View File

@ -4,6 +4,7 @@ ansi2html # LGPLv3+
dpkt # BSD dpkt # BSD
openshift-client # Apache-2.0 openshift-client # Apache-2.0
pandas # BSD pandas # BSD
pandas==2.1.1;python_version>='3.9' # BSD
podman==4.7.0 # Apache-2.0 podman==4.7.0 # Apache-2.0
pytest-cov # MIT pytest-cov # MIT
pytest-reportportal # Apache-2.0 pytest-reportportal # Apache-2.0

View File

@ -31,6 +31,7 @@ python-novaclient==17.2.1
python-octaviaclient==2.2.0 python-octaviaclient==2.2.0
python-openstackclient==5.4.0 python-openstackclient==5.4.0
PyYAML==5.4.1 PyYAML==5.4.1
setuptools==21.0.0;python_version>='3.12'
sshtunnel==0.3.1 sshtunnel==0.3.1
testtools==2.5.0 testtools==2.5.0
validations-libs==1.1.0 validations-libs==1.1.0

View File

@ -28,5 +28,6 @@ python-novaclient>=17.2.1 # Apache-2.0
python-octaviaclient>=2.2.0 # Apache-2.0 python-octaviaclient>=2.2.0 # Apache-2.0
python-openstackclient>=5.4.0 # Apache-2.0 python-openstackclient>=5.4.0 # Apache-2.0
PyYAML>=5.4.1 # MIT PyYAML>=5.4.1 # MIT
setuptools!=24.0.0,!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2,!=36.2.0,>=21.0.0;python_version>='3.12' # PSF/ZPL
sshtunnel>=0.3.1 # MIT sshtunnel>=0.3.1 # MIT
testtools>=2.5.0 # MIT testtools>=2.5.0 # MIT

View File

@ -25,12 +25,6 @@ import decorator
P = typing.TypeVar('P') P = typing.TypeVar('P')
GenericMetaBase = abc.ABCMeta GenericMetaBase = abc.ABCMeta
if hasattr(typing, 'GenericMeta'):
class GenericMetaBase( # type: ignore[no-redef]
typing.GenericMeta, # type: ignore[name-defined]
abc.ABCMeta):
# pylint: disable=function-redefined,no-member
pass
class GenericMeta(GenericMetaBase): class GenericMeta(GenericMetaBase):
@ -47,7 +41,10 @@ class GenericMeta(GenericMetaBase):
if inspect.ismethod(class_getitem): if inspect.ismethod(class_getitem):
cls = class_getitem(item) cls = class_getitem(item)
else: else:
cls = class_getitem(cls, item) try:
cls = class_getitem(cls, item)
except TypeError:
cls = class_getitem(item)
return cls return cls

View File

@ -75,7 +75,7 @@ class TobikoException(Exception):
message=self.message) message=self.message)
def __eq__(self, other): def __eq__(self, other):
return type(self) == type(other) and str(self) == str(other) return type(self) is type(other) and str(self) == str(other)
def __hash__(self): def __hash__(self):
return hash(type(self)) + hash(str(self)) return hash(type(self)) + hash(str(self))

View File

@ -123,8 +123,9 @@ class UbuntuImageFixture(UbuntuMinimalImageFixture,
@staticmethod @staticmethod
def _get_ethernet_device() -> str: def _get_ethernet_device() -> str:
"""From OSP17 and above, Ubuntu stack should use a different interface name. """From OSP17 and above, Ubuntu stack should use a different interface
This method returns the interface name, depending on the OSP version. name. This method returns the interface name, depending on the OSP
version.
""" """
if_name = CONF.tobiko.ubuntu.interface_name if_name = CONF.tobiko.ubuntu.interface_name
if if_name is not None: if if_name is not None:

View File

@ -34,10 +34,8 @@ class Mixin:
# This is the UDS where all the IO goes # This is the UDS where all the IO goes
io_socket = attach['sockets']['io_socket'] io_socket = attach['sockets']['io_socket']
assert len(io_socket) <= 107,\ assert len(io_socket) <= 107, \
'Path length for sockets too long. {} > 107'.format( 'Path length for sockets too long. {} > 107'.format(len(io_socket))
len(io_socket)
)
# This is the control socket where resizing events are sent to conmon # This is the control socket where resizing events are sent to conmon
# attach['sockets']['control_socket'] # attach['sockets']['control_socket']

View File

@ -33,7 +33,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
setattr(self, 'running', self.data['containerrunning']) setattr(self, 'running', self.data['containerrunning'])
self.data['running'] = self.data['containerrunning'] self.data['running'] = self.data['containerrunning']
assert self._id == data['id'],\ assert self._id == data['id'], \
'Requested container id({}) does not match store id({})'.format( 'Requested container id({}) does not match store id({})'.format(
self._id, data['id'] self._id, data['id']
) )

View File

@ -25,10 +25,9 @@ class Image(collections.UserDict):
self._id = id self._id = id
self._client = client self._client = client
assert self._id == data['id'],\ assert self._id == data['id'], \
'Requested image id({}) does not match store id({})'.format( 'Requested image id({}) does not match store id({})'.format(
self._id, data['id'] self._id, data['id'])
)
@staticmethod @staticmethod
def _split_token(values=None, sep='='): def _split_token(values=None, sep='='):

View File

@ -65,7 +65,7 @@ def get_nodes_for_groups(groups):
for group in node.groups: for group in node.groups:
if group in groups: if group in groups:
nodes.append(node) nodes.append(node)
return(nodes) return nodes
def get_config_files(node, kolla_jsons, conf_ignorelist, scripts_to_check): def get_config_files(node, kolla_jsons, conf_ignorelist, scripts_to_check):

View File

@ -607,8 +607,8 @@ def kill_rabbitmq_service():
node.name)) node.name))
retry = tobiko.retry(timeout=30, interval=5) retry = tobiko.retry(timeout=30, interval=5)
for _ in retry: for _ in retry:
if not(pacemaker.PacemakerResourcesStatus(). if not (pacemaker.PacemakerResourcesStatus().
rabbitmq_resource_healthy()): rabbitmq_resource_healthy()):
return return
@ -630,8 +630,8 @@ def kill_all_galera_services():
node.name)) node.name))
retry = tobiko.retry(timeout=30, interval=5) retry = tobiko.retry(timeout=30, interval=5)
for _ in retry: for _ in retry:
if not(pacemaker.PacemakerResourcesStatus(). if not (pacemaker.PacemakerResourcesStatus().
galera_resource_healthy()): galera_resource_healthy()):
return return

View File

@ -1,54 +0,0 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import os
import typing
import testtools
from tobiko import run
class DiscoverTestIdsTest(testtools.TestCase):
def test_discover_test_ids(self,
test_files: typing.List[str] = None):
if test_files is None:
test_files = [__file__]
test_ids = run.discover_test_ids(test_files=test_files)
self.assertIn(self.id(), test_ids)
def test_forked_discover_test_ids(self,
test_files: typing.List[str] = None):
if test_files is None:
test_files = [__file__]
test_ids = run.forked_discover_test_ids(test_files=test_files)
self.assertIn(self.id(), test_ids)
def test_find_test_ids(self,
test_path: typing.List[str] = None,
forked=False):
if test_path is None:
test_path = [__file__]
test_ids = run.find_test_ids(test_path=test_path, forked=forked)
self.assertIn(self.id(), test_ids)
def test_find_test_ids_with_test_dir(self):
self.test_find_test_ids(test_path=[os.path.dirname(__file__)])
def test_find_test_ids_with_forked(self):
self.test_find_test_ids(forked=True)

View File

@ -1,45 +0,0 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import os
import typing
import testtools
from tobiko import run
class FindTestFilesTest(testtools.TestCase):
def test_find_test_files(self,
test_path: typing.List[str] = None):
if test_path is None:
test_path = [__file__]
test_files = run.find_test_files(test_path=test_path)
self.assertIn(__file__, test_files)
test_path = [os.path.realpath(path)
for path in test_path]
for test_file in test_files:
for path in test_path:
if test_file.startswith(path):
break
else:
self.fail(f"File '{test_file}' not in any path ({test_path})")
def test_find_test_files_with_test_dir(self):
return self.test_find_test_files(
test_path=[os.path.dirname(__file__)])

View File

@ -1,56 +0,0 @@
# Copyright (c) 2022 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import functools
import os
import typing
import unittest
import testtools
from tobiko import run
def nested_test_case(test_method: typing.Callable[[testtools.TestCase], None]):
@functools.wraps(test_method)
def wrapper(self: unittest.TestCase):
nested_counter = int(os.environ.get('NESTED_TEST_CASE', 0))
if not nested_counter:
os.environ['NESTED_TEST_CASE'] = str(nested_counter + 1)
try:
test_method(self)
finally:
if nested_counter:
os.environ['NESTED_TEST_CASE'] = str(nested_counter)
else:
os.environ.pop('NESTED_TEST_CASE')
return wrapper
class RunTestsTest(unittest.TestCase):
@nested_test_case
def test_run_tests(self):
result = run.run_tests(__file__)
self.assertGreater(result.testsRun, 0)
@nested_test_case
def test_run_tests_with_dir(self):
test_dir = os.path.dirname(__file__)
result = run.run_tests(test_path=test_dir)
self.assertGreater(result.testsRun, 0)

View File

@ -179,9 +179,9 @@ class OvercloudVersionTest(unittest.TestCase):
if v.micro > 0: if v.micro > 0:
lower_v = f"{v.major}.{v.minor}.{v.micro - 1}" lower_v = f"{v.major}.{v.minor}.{v.micro - 1}"
elif v.minor > 0: elif v.minor > 0:
lower_v = f"{v.major}.{v.minor -1}.{v.micro}" lower_v = f"{v.major}.{v.minor - 1}.{v.micro}"
elif v.major > 0: elif v.major > 0:
lower_v = f"{v.major -1}.{v.minor}.{v.micro}" lower_v = f"{v.major - 1}.{v.minor}.{v.micro}"
else: else:
raise ValueError(f"wrong version: {v}") raise ValueError(f"wrong version: {v}")
return lower_v return lower_v

View File

@ -87,9 +87,9 @@ class UndercloudVersionTest(unittest.TestCase):
if v.micro > 0: if v.micro > 0:
lower_v = f"{v.major}.{v.minor}.{v.micro - 1}" lower_v = f"{v.major}.{v.minor}.{v.micro - 1}"
elif v.minor > 0: elif v.minor > 0:
lower_v = f"{v.major}.{v.minor -1}.{v.micro}" lower_v = f"{v.major}.{v.minor - 1}.{v.micro}"
elif v.major > 0: elif v.major > 0:
lower_v = f"{v.major -1}.{v.minor}.{v.micro}" lower_v = f"{v.major - 1}.{v.minor}.{v.micro}"
else: else:
raise ValueError(f"wrong version: {v}") raise ValueError(f"wrong version: {v}")
return lower_v return lower_v

View File

@ -210,7 +210,7 @@ class OvercloudProcessesStatus(object):
ovn_proc_filtered_df[node_filter] ovn_proc_filtered_df[node_filter]
total_num_processes = len(ovn_proc_filtered_per_node_df) total_num_processes = len(ovn_proc_filtered_per_node_df)
if type(process_dict['number']) == int: if isinstance(process_dict['number'], int):
expected_num_processes = process_dict['number'] expected_num_processes = process_dict['number']
elif process_dict['number'] == 'all': elif process_dict['number'] == 'all':
expected_num_processes = len(node_list) expected_num_processes = len(node_list)

View File

@ -65,7 +65,7 @@ dfs-sdk===1.2.27
dib-utils===0.0.11 dib-utils===0.0.11
directord===0.12.0 directord===0.12.0
diskimage-builder===3.20.3 diskimage-builder===3.20.3
distlib===0.3.4 distlib===0.3.9
distro===1.7.0 distro===1.7.0
Django===3.2.12 Django===3.2.12
django-appconf===1.0.5 django-appconf===1.0.5
@ -89,7 +89,7 @@ enmerkar===0.7.1
enum-compat===0.0.3 enum-compat===0.0.3
etcd3===0.12.0 etcd3===0.12.0
etcd3gw===1.0.1 etcd3gw===1.0.1
eventlet===0.33.0 eventlet===0.36.1
exabgp===4.2.17 exabgp===4.2.17
execnet===1.9.0 execnet===1.9.0
extras===1.0.0 extras===1.0.0
@ -118,7 +118,7 @@ google-auth-httplib2===0.1.0
googleapis-common-protos===1.55.0 googleapis-common-protos===1.55.0
gossip===2.4.0 gossip===2.4.0
graphviz===0.19.1 graphviz===0.19.1
greenlet===1.1.2 greenlet===3.1.1
grpcio===1.44.0 grpcio===1.44.0
gssapi===1.7.3 gssapi===1.7.3
gunicorn===20.1.0 gunicorn===20.1.0
@ -136,6 +136,8 @@ ifaddr===0.1.7
imagesize===1.3.0 imagesize===1.3.0
immutables===0.16 immutables===0.16
importlib-metadata===4.11.1;python_version=='3.8' importlib-metadata===4.11.1;python_version=='3.8'
importlib-metadata===6.2.1;python_version=='3.9'
importlib-metadata===8.5.0;python_version>='3.10'
importlib-resources===5.2.3 importlib-resources===5.2.3
infi.dtypes.iqn===0.4.0 infi.dtypes.iqn===0.4.0
infi.dtypes.wwn===0.1.1 infi.dtypes.wwn===0.1.1
@ -389,7 +391,7 @@ pytz-deprecation-shim===0.1.0.post0
pyudev===0.23.2 pyudev===0.23.2
pywbem===1.4.1 pywbem===1.4.1
pywinrm===0.4.2 pywinrm===0.4.2
PyYAML===6.0 PyYAML===6.0.1
pyzabbix===1.0.0 pyzabbix===1.0.0
pyzmq===21.0.2 pyzmq===21.0.2
rbd-iscsi-client===0.1.8 rbd-iscsi-client===0.1.8
@ -433,8 +435,7 @@ semantic-version===2.9.0
sentinels===1.0.0 sentinels===1.0.0
seqdiag===3.0.0;python_version=='3.8' seqdiag===3.0.0;python_version=='3.8'
setproctitle===1.2.2 setproctitle===1.2.2
setuptools===60.9.3;python_version=='3.8' setuptools===70.3.0;python_version=='3.9'
setuptools===70.3.0;python_version>='3.9'
simplegeneric===0.8.1 simplegeneric===0.8.1
simplejson===3.17.6 simplejson===3.17.6
six===1.16.0 six===1.16.0
@ -473,7 +474,8 @@ ssh-python===0.9.0
sshtunnel===0.4.0 sshtunnel===0.4.0
statsd===3.3.0 statsd===3.3.0
stestr===3.2.1 stestr===3.2.1
stevedore===3.5.0 stevedore===3.5.0;python_version<'3.12'
stevedore===5.4.0;python_version>='3.12'
storage-interfaces===1.0.4 storage-interfaces===1.0.4
storops===1.2.10 storops===1.2.10
storpool===7.0.0 storpool===7.0.0
@ -514,9 +516,10 @@ types-cryptography===3.3.15
types-enum34===1.1.8 types-enum34===1.1.8
types-ipaddress===1.0.8 types-ipaddress===1.0.8
types-paramiko===2.8.13 types-paramiko===2.8.13
types-setuptools===75.5.0.20241122
typing===3.7.4.3 typing===3.7.4.3
typing_extensions===4.1.1 typing_extensions===4.12.2
tzdata===2021.5 tzdata===2024.2
tzlocal===4.1 tzlocal===4.1
uhashring===2.1 uhashring===2.1
ujson===5.1.0;python_version=='3.8' ujson===5.1.0;python_version=='3.8'
@ -528,7 +531,7 @@ validations-libs===1.6.0
vine===5.0.0 vine===5.0.0
vintage===0.4.1 vintage===0.4.1
virtualbmc===2.2.1 virtualbmc===2.2.1
virtualenv===20.13.2 virtualenv===20.27.1
voluptuous===0.12.2 voluptuous===0.12.2
waiting===1.4.1 waiting===1.4.1
waitress===2.0.0 waitress===2.0.0
@ -541,7 +544,7 @@ websockify===0.10.0
WebTest===3.0.0 WebTest===3.0.0
Werkzeug===2.0.3 Werkzeug===2.0.3
whereto===0.4.0 whereto===0.4.0
wrapt===1.13.3 wrapt===1.14.0
ws4py===0.5.1 ws4py===0.5.1
wsgi-intercept===1.9.3 wsgi-intercept===1.9.3
WSME===0.11.0 WSME===0.11.0
@ -590,7 +593,7 @@ XStatic-term.js===0.0.7.0
XStatic-tv4===1.2.7.0 XStatic-tv4===1.2.7.0
xvfbwrapper===0.2.9 xvfbwrapper===0.2.9
yamlloader===1.1.0 yamlloader===1.1.0
yappi===1.3.3 yappi===1.6.10
yaql===2.0.0 yaql===2.0.0
zake===0.2.2 zake===0.2.2
zeroconf===0.38.3;python_version=='3.8' zeroconf===0.38.3;python_version=='3.8'