fix: Handle duplicate resource exception and update urllib3 53/74053/4 v0.37.19
authorAnil Belur <abelur@linuxfoundation.org>
Mon, 12 Jan 2026 14:28:24 +0000 (00:28 +1000)
committerAnil Belur <abelur@linuxfoundation.org>
Mon, 12 Jan 2026 14:29:14 +0000 (00:29 +1000)
1. Fix duplicate resource exception handling across OpenStack modules:
   - OpenStack SDK changed exception message format
   - Old: 'Multiple matches found for...'
   - New: 'More than one {Resource} exists with the name...'
   - Update exception check to handle both message formats
   - Applied to: image.py, server.py, volume.py

2. Fix server.created_at attribute:
   - Use 'created_at' instead of 'created' for Server objects
   - Bug introduced during shade to openstacksdk migration (2023)
   - Applied to: server.py lines 27, 92

3. Update urllib3 requirement:
   - Change from 'urllib3<2.1.0' to 'urllib3>=1.26.15,<3.0.0'
   - Resolves dependency conflict warnings
   - Supports newer urllib3 2.x while maintaining compatibility

Fixes builder-openstack-cron job failures.

Issue: AttributeError: 'Server' object has no attribute 'created'
Issue: More than one Image/Server/Volume exists with the name
Issue: urllib3 version conflict

Change-Id: I4561bd1092c81c31e730fb21a2788966128049c1
Signed-off-by: Anil Belur <abelur@linuxfoundation.org>
lftools/openstack/image.py
lftools/openstack/server.py
lftools/openstack/volume.py
pyproject.toml

index 3bcb0f8..038309c 100644 (file)
@@ -118,11 +118,14 @@ def cleanup(os_cloud, days=0, hide_public=False, ci_managed=True, clouds=None):
             try:
                 result = cloud.delete_image(image.name)
             except OpenStackCloudException as e:
-                if str(e).startswith("Multiple matches found for"):
-                    log.warning("{}. Skipping image...".format(str(e)))
+                error_msg = str(e)
+                if error_msg.startswith("Multiple matches found for") or error_msg.startswith(
+                    "More than one Image exists with the name"
+                ):
+                    log.warning("{}. Skipping image...".format(error_msg))
                     continue
                 else:
-                    log.error("Unexpected exception: {}".format(str(e)))
+                    log.error("Unexpected exception: {}".format(error_msg))
                     raise
 
             if not result:
index 4f6922f..5f1608c 100644 (file)
@@ -24,7 +24,9 @@ def _filter_servers(servers, days=0):
     """Filter server data and return list."""
     filtered = []
     for server in servers:
-        if days and (datetime.strptime(server.created, "%Y-%m-%dT%H:%M:%SZ") >= datetime.now() - timedelta(days=days)):
+        if days and (
+            datetime.strptime(server.created_at, "%Y-%m-%dT%H:%M:%SZ") >= datetime.now() - timedelta(days=days)
+        ):
             continue
 
         filtered.append(server)
@@ -89,7 +91,7 @@ def remove(os_cloud, server_name, minutes=0):
         print("ERROR: Server not found.")
         sys.exit(1)
 
-    if datetime.strptime(server.created, "%Y-%m-%dT%H:%M:%SZ") >= datetime.utcnow() - timedelta(minutes=minutes):
+    if datetime.strptime(server.created_at, "%Y-%m-%dT%H:%M:%SZ") >= datetime.utcnow() - timedelta(minutes=minutes):
         print('WARN: Server "{}" is not older than {} minutes.'.format(server.name, minutes))
     else:
         cloud.delete_server(server.name)
index 7faa770..88e2eab 100644 (file)
@@ -56,11 +56,14 @@ def cleanup(os_cloud, days=0):
             try:
                 result = cloud.delete_volume(volume.name)
             except OpenStackCloudException as e:
-                if str(e).startswith("Multiple matches found for"):
-                    print("WARNING: {}. Skipping volume...".format(str(e)))
+                error_msg = str(e)
+                if error_msg.startswith("Multiple matches found for") or error_msg.startswith(
+                    "More than one Volume exists with the name"
+                ):
+                    print("WARNING: {}. Skipping volume...".format(error_msg))
                     continue
                 else:
-                    print("ERROR: Unexpected exception: {}".format(str(e)))
+                    print("ERROR: Unexpected exception: {}".format(error_msg))
                     raise
 
             if not result:
index 7afaf4b..490f5c9 100644 (file)
@@ -80,7 +80,7 @@ dependencies = [
     "tabulate",
     "toml",
     "tqdm",
-    "urllib3<2.1.0",
+    "urllib3>=1.26.15,<3.0.0",
     "websocket-client",
     "wrapt",
     "xdg"