Skip to content

Commit 4cf8d2d

Browse files
author
Nils Bars
committed
Fix Docker API race conditions causing CI failures
- Handle None for network.attrs["Containers"] in get_connected_container() and get_container_ip() methods - Handle None for IPAM Config in _get_used_subnets() when Docker returns null instead of empty list - Add retry logic in create_network() for "Pool overlaps" errors that occur when multiple processes allocate the same subnet concurrently
1 parent e98531a commit 4cf8d2d

1 file changed

Lines changed: 48 additions & 17 deletions

File tree

webapp/ref/core/docker.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,10 @@ def get_connected_container(
213213
if not network:
214214
return []
215215

216-
return network.attrs["Containers"].keys()
216+
containers = network.attrs.get("Containers")
217+
if containers is None:
218+
return []
219+
return containers.keys()
217220

218221
def get_connected_networks(
219222
self, container: Union[str, docker.models.containers.Container]
@@ -305,7 +308,10 @@ def container_get_ip(
305308
network = self.network(network, raise_on_not_found=True)
306309

307310
network.reload()
308-
for k, v in network.attrs["Containers"].items():
311+
containers = network.attrs.get("Containers")
312+
if containers is None:
313+
return None
314+
for k, v in containers.items():
309315
if k == container.id:
310316
return v["IPv4Address"]
311317
return None
@@ -401,7 +407,7 @@ def _get_used_subnets(self) -> set[ipaddress.IPv4Network]:
401407
used = set()
402408
for network in self.client.networks.list():
403409
try:
404-
ipam_config = network.attrs.get("IPAM", {}).get("Config", [])
410+
ipam_config = network.attrs.get("IPAM", {}).get("Config") or []
405411
for config in ipam_config:
406412
subnet_str = config.get("Subnet")
407413
if subnet_str:
@@ -444,23 +450,48 @@ def create_network(self, name=None, driver="bridge", internal=False):
444450
random.choices(string.ascii_uppercase, k=10)
445451
)
446452

447-
# Allocate a /29 subnet from our pool
448-
subnet = self._allocate_subnet()
449-
if subnet is None:
450-
raise RuntimeError(
451-
"No available subnet in instance network pool. "
452-
"Consider cleaning up unused networks."
453-
)
453+
# Retry loop to handle race conditions when multiple processes
454+
# try to allocate the same subnet concurrently
455+
max_retries = 10
456+
last_error = None
457+
458+
for attempt in range(max_retries):
459+
# Allocate a /29 subnet from our pool
460+
subnet = self._allocate_subnet()
461+
if subnet is None:
462+
raise RuntimeError(
463+
"No available subnet in instance network pool. "
464+
"Consider cleaning up unused networks."
465+
)
466+
467+
# First usable host is the gateway
468+
gateway = str(list(subnet.hosts())[0])
454469

455-
# First usable host is the gateway
456-
gateway = str(list(subnet.hosts())[0])
470+
ipam_pool = IPAMPool(subnet=str(subnet), gateway=gateway)
471+
ipam_config = IPAMConfig(pool_configs=[ipam_pool])
457472

458-
ipam_pool = IPAMPool(subnet=str(subnet), gateway=gateway)
459-
ipam_config = IPAMConfig(pool_configs=[ipam_pool])
473+
log.debug(
474+
f"Creating network {name} with subnet {subnet} (attempt {attempt + 1})"
475+
)
476+
try:
477+
return self.client.networks.create(
478+
name, driver=driver, internal=internal, ipam=ipam_config
479+
)
480+
except errors.APIError as e:
481+
# Check if this is a subnet overlap error (race condition)
482+
if "Pool overlaps" in str(e):
483+
log.warning(
484+
f"Subnet {subnet} was allocated by another process, retrying..."
485+
)
486+
last_error = e
487+
continue
488+
# Re-raise other API errors
489+
raise
460490

461-
log.debug(f"Creating network {name} with subnet {subnet}")
462-
return self.client.networks.create(
463-
name, driver=driver, internal=internal, ipam=ipam_config
491+
# All retries exhausted
492+
raise RuntimeError(
493+
f"Failed to allocate subnet after {max_retries} attempts. "
494+
f"Last error: {last_error}"
464495
)
465496

466497
def network(self, network_id, raise_on_not_found=False):

0 commit comments

Comments
 (0)