feat: allocate IOL Docker application IDs from a pool disjoint from IOU

IOL interface MACs derive from the application ID (aabb.cc{app}{iface}),
so ids must be unique across opened projects sharing computes — the same
reason IOU has its allocator. IOL Docker nodes draw from the upper half
(512-1022, netiomux's fixed peer is 1023) so the two node types can
neither collide nor starve each other; IOU behavior is unchanged.

The controller sniffs the same GNS3_IOL_RUNNER environment marker the
compute uses to select IOLDockerVM (both the nested-properties and
template/top-level-kwarg shapes), stores the id in node properties like
IOU does, and passes it through the Docker create payload. Without an
allocation the compute falls back to a stable node-derived id in the
same upper range.
This commit is contained in:
YueGuobin committed 2026-09-05 21:54:52 +08:00
1 parent b17d021bf6
commit 0119373690
8 files changed
+177 -24

No files matched your search

+2 -1
View File
@@ -30,6 +30,7 @@ from .controller_error import (
from .node_types import BUILTIN_NODE_TYPES
from .ports.port_factory import PortFactory, StandardPortFactory, DynamipsPortFactory
from ..utils.images import images_directories
from ..utils.application_id import is_iol_runner_environment
from ..utils import macaddress_to_int, int_to_macaddress
from ..config import Config
@@ -804,7 +805,7 @@ class Node:
self._ports = DynamipsPortFactory(self._properties)
return
elif self._node_type == "docker":
if "GNS3_IOL_RUNNER=" in (self._properties.get("environment") or ""):
if is_iol_runner_environment(self._properties.get("environment")):
# IOL adapters are 4-port units (the IOU model): ports are
# Ethernet0/0-3, Ethernet1/0-3, … addressed as
# (adapter_number, port_number 0-3).
+35 -3
View File
@@ -40,7 +40,7 @@ from .udp_link import UDPLink
from .link import _UNSET
from ..config import Config
from ..utils.path import check_path_allowed, get_default_project_directory
from ..utils.application_id import get_next_application_id
from ..utils.application_id import get_next_application_id, is_iol_runner_environment
from ..utils.asyncio.pool import Pool
from ..utils.packet_filter_validation import validate_bpf_syntax
from ..utils.asyncio import locking
@@ -69,6 +69,21 @@ def open_required(func):
return wrapper
def _is_iol_docker_kwargs(kwargs) -> bool:
"""
Whether add_node() kwargs describe an iol-runner Docker node. The
environment can arrive nested in a ``properties`` dict or as a top-level
kwarg (the template path spreads it), matching the two shapes IOU
application-id injection handles.
"""
if "properties" in kwargs.keys():
environment = (kwargs.get("properties") or {}).get("environment")
else:
environment = kwargs.get("environment")
return is_iol_runner_environment(environment)
class Project:
"""
A project inside a controller
@@ -159,7 +174,7 @@ class Project:
assert self._status != "closed"
self.dump()
self._iou_id_lock = asyncio.Lock()
self._application_id_lock = asyncio.Lock()
# Serialise the "ensure project exists on this compute" check in
# _create_node: without it, concurrent node creations all pass the
# `compute not in _project_created_on_compute` check before any has
@@ -646,7 +661,7 @@ class Project:
self._computes.append(compute.id)
if node_type == "iou":
async with self._iou_id_lock:
async with self._application_id_lock:
# IOU application IDs must be allocated serially to avoid duplicates.
# The lock must also cover _create_node() because get_next_application_id()
# checks in-memory nodes (self._nodes), which are only registered
@@ -658,6 +673,23 @@ class Project:
elif "application_id" not in kwargs.keys() and not kwargs.get("properties"):
kwargs["application_id"] = get_next_application_id(self._controller.projects, self._computes)
node = await self._create_node(compute, name, node_id, node_type, **kwargs)
elif node_type == "docker" and _is_iol_docker_kwargs(kwargs):
# IOL Docker nodes derive interface MACs from the application ID
# exactly like IOU; they draw from the disjoint upper half of the
# id space so the two node types can neither collide nor starve
# each other.
async with self._application_id_lock:
if "properties" in kwargs.keys():
properties = kwargs.get("properties") or {}
if "application_id" not in properties:
properties["application_id"] = get_next_application_id(
self._controller.projects, self._computes, iol_docker=True
)
elif "application_id" not in kwargs.keys():
kwargs["application_id"] = get_next_application_id(
self._controller.projects, self._computes, iol_docker=True
)
node = await self._create_node(compute, name, node_id, node_type, **kwargs)
else:
node = await self._create_node(compute, name, node_id, node_type, **kwargs)
self.emit_notification("node.created", node.asdict())