From 74274ec8bccc47c80f7302e3ce14e430826fe1ac Mon Sep 17 00:00:00 2001 From: Alistair Coles Date: Thu, 14 Aug 2025 11:14:41 +0100 Subject: [PATCH] checksum.py: fail gracefully if pyeclib is broken If pyeclib dist is missing files then the isal loading would blow up with "TypeError: 'NoneType' object is not iterable". This patch changes that to a RuntimeError with a more useful message. Change-Id: I64a85eb739fb72efe41f1ee829e463167246b793 Closes-Bug: #2120591 Signed-off-by: Alistair Coles --- swift/common/utils/checksum.py | 39 +++++++++++++++---------- test/unit/common/utils/test_checksum.py | 14 +++++++++ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/swift/common/utils/checksum.py b/swift/common/utils/checksum.py index d62b284a57..9f9a273ebc 100644 --- a/swift/common/utils/checksum.py +++ b/swift/common/utils/checksum.py @@ -25,12 +25,6 @@ import socket import struct import zlib -try: - import pyeclib # noqa - from importlib.metadata import files as pkg_files # py38+ -except ImportError: - pkg_files = None - # See if anycrc is available... if anycrc: @@ -41,17 +35,30 @@ else: crc64nvme_anycrc = None -# If isal is available system-wide, great! -isal_lib = ctypes.util.find_library('isal') -if isal_lib is None and pkg_files is not None: - # py38+: Hopefully pyeclib was installed from a manylinux wheel - # with isal baked in? - isal_libs = [f for f in pkg_files('pyeclib') - if f.name.startswith("libisal")] - if len(isal_libs) == 1: - isal_lib = isal_libs[0].locate() +def find_isal(): + # If isal is available system-wide, great! + isal_lib = ctypes.util.find_library('isal') + if isal_lib is None: + # py38+: Hopefully pyeclib was installed from a manylinux wheel + # with isal baked in? + try: + import pyeclib # noqa + from importlib.metadata import files as pkg_files # py38+ + except ImportError: + pass + else: + pyeclib_files = pkg_files('pyeclib') + if not pyeclib_files: + # see https://docs.python.org/3/library/importlib.metadata.html + raise RuntimeError('pyeclib installed but missing files') + isal_libs = [f for f in pyeclib_files + if f.name.startswith("libisal")] + if len(isal_libs) == 1: + isal_lib = isal_libs[0].locate() + return ctypes.CDLL(isal_lib) if isal_lib else None -isal = ctypes.CDLL(isal_lib) if isal_lib else None + +isal = find_isal() if hasattr(isal, 'crc32_iscsi'): # isa-l >= 2.16 isal.crc32_iscsi.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_uint] diff --git a/test/unit/common/utils/test_checksum.py b/test/unit/common/utils/test_checksum.py index 144f90bba5..87ffe29bac 100644 --- a/test/unit/common/utils/test_checksum.py +++ b/test/unit/common/utils/test_checksum.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys import unittest from unittest import mock @@ -23,6 +24,19 @@ from test.debug_logger import debug_logger from test.unit import requires_crc32c, requires_crc64nvme +class TestModuleFunctions(unittest.TestCase): + @unittest.skipIf( + sys.version_info.major == 3 and sys.version_info.minor < 8, + "importlib.metadata not available until py3.8") + def test_find_isal_pyeclib_dist_missing_files(self): + with mock.patch('ctypes.util.find_library', return_value=None): + with mock.patch('importlib.metadata.files', return_value=None): + with self.assertRaises(RuntimeError) as cm: + checksum.find_isal() + self.assertEqual('pyeclib installed but missing files', + str(cm.exception)) + + # If you're curious about the 0xe3069283, see "check" at # https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat.crc-32-iscsi class TestCRC32C(unittest.TestCase):