fix(topology): cache IPAllocator host set; type repo params as BaseRepository

_host_set is computed once in __init__ — reserve() and is_free() were rebuilding
the full host frozenset on every call. BaseRepository already existed; the Any
annotations were just never updated.
This commit is contained in:
2026-04-30 21:52:29 -04:00
parent 257857338c
commit 84e0ac4a43
2 changed files with 10 additions and 7 deletions

View File

@@ -12,9 +12,10 @@ open one.
from __future__ import annotations
from ipaddress import IPv4Network
from typing import Any, Iterable
from typing import Iterable
from decnet.topology.status import TopologyStatus
from decnet.web.db.repository import BaseRepository
class AllocatorExhausted(RuntimeError):
@@ -34,6 +35,7 @@ class IPAllocator:
self._pool: list[str] = [
str(ip) for ip in self._net.hosts() if str(ip) != self._gateway
]
self._host_set: frozenset[str] = frozenset(str(h) for h in self._net.hosts())
self._taken: set[str] = set()
self._cursor = 0
@@ -57,7 +59,7 @@ class IPAllocator:
def reserve(self, ip: str) -> None:
if ip == self._gateway:
raise ValueError(f"{ip} is the gateway of {self._net.with_prefixlen}")
if ip not in {str(h) for h in self._net.hosts()}:
if ip not in self._host_set:
raise ValueError(f"{ip} not in {self._net.with_prefixlen}")
self._taken.add(ip)
@@ -65,7 +67,7 @@ class IPAllocator:
self._taken.discard(ip)
def is_free(self, ip: str) -> bool:
return ip not in self._taken and ip in {str(h) for h in self._net.hosts()} and ip != self._gateway
return ip not in self._taken and ip in self._host_set and ip != self._gateway
class SubnetAllocator:
@@ -148,7 +150,7 @@ _SUBNET_CLAIMING_STATES: frozenset[str] = frozenset(
)
async def reserved_subnets(repo: Any) -> set[str]:
async def reserved_subnets(repo: BaseRepository) -> set[str]:
"""All LAN subnets currently claimed by non-torn-down topologies."""
out: set[str] = set()
for status in _SUBNET_CLAIMING_STATES:

View File

@@ -5,12 +5,13 @@ from ipaddress import IPv4Address, IPv4Network
from typing import Any
from decnet.topology.allocator import IPAllocator
from decnet.web.db.repository import BaseRepository
from decnet.topology.config import GeneratedTopology
from decnet.topology.status import TopologyStatus, assert_transition
async def persist(
repo: Any,
repo: BaseRepository,
plan: GeneratedTopology,
*,
target_host_uuid: str | None = None,
@@ -90,7 +91,7 @@ async def persist(
async def transition_status(
repo: Any,
repo: BaseRepository,
topology_id: str,
new_status: str,
reason: str | None = None,
@@ -107,7 +108,7 @@ async def transition_status(
await repo.update_topology_status(topology_id, new_status, reason=reason)
async def hydrate(repo: Any, topology_id: str) -> dict[str, Any] | None:
async def hydrate(repo: BaseRepository, topology_id: str) -> dict[str, Any] | None:
"""Load a topology + children into a single dict for callers.
Shape::