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

+20 -5
View File
@@ -261,7 +261,7 @@ async def test_start_writes_iol_config(compute_project, manager):
assert config["binary"] == "/binary.iol"
assert config["num-eth"] == 16 # 4 adapters, each a 4-port unit
assert config["num-serial"] == 0
assert config["local-app"] == int(vm.id.replace("-", ""), 16) % 1022 + 1
assert config["local-app"] == 512 + int(vm.id.replace("-", ""), 16) % 511
assert config["remote-app"] == 1023
assert config["memory"] == 2048
assert config["user-id"] == os.getuid()
@@ -274,10 +274,25 @@ def test_local_app_is_distinct_per_node(compute_project, manager):
# drop each other's frames as MAC loops, so IDs must differ per node.
vm1 = _make_vm(compute_project, manager)
vm2 = _make_vm(compute_project, manager)
id1 = int(vm1.id.replace("-", ""), 16) % 1022 + 1
id2 = int(vm2.id.replace("-", ""), 16) % 1022 + 1
assert id1 != id2
assert 1 <= id1 <= 1022 and 1 <= id2 <= 1022
assert vm1.application_id != vm2.application_id
for vm in (vm1, vm2):
assert 512 <= vm.application_id <= 1022
@pytest.mark.asyncio
async def test_allocated_application_id_overrides_fallback(compute_project, manager):
# The controller allocates the application ID (upper half of the id
# space); the node-derived hash is only a fallback without one.
vm = _make_vm(compute_project, manager)
vm.application_id = 700
assert vm.application_id == 700
_mock_start(vm)
with patch("gns3server.compute.docker.Docker.install_busybox"):
with asyncio_patch("gns3server.compute.docker.Docker.query"):
await vm.start()
with open(os.path.join(vm.working_dir, "config", "iol-config.json")) as f:
config = json.load(f)
assert config["local-app"] == 700
@pytest.mark.asyncio
+41
View File
@@ -296,6 +296,47 @@ async def test_add_node_iou(controller):
assert node3.properties["application_id"] == 3
@pytest.mark.asyncio
async def test_add_node_iol_docker(controller):
"""
IOL Docker nodes (GNS3_IOL_RUNNER marker) get an application ID from the
upper half of the id space, disjoint from IOU's lower half
"""
compute = MagicMock()
compute.id = "local"
project = await controller.add_project(project_id=str(uuid.uuid4()), name="test1")
project.emit_notification = MagicMock()
response = MagicMock()
compute.post = AsyncioMagicMock(return_value=response)
# template shape: environment as a top-level kwarg
node1 = await project.add_node(
compute, "iol1", None, node_type="docker", image="iol-xe/iol-xe:17-18-02", environment="GNS3_IOL_RUNNER=1"
)
# raw API shape: environment nested in properties
node2 = await project.add_node(
compute,
"iol2",
None,
node_type="docker",
properties={"image": "iol-xe/iol-xe:17-18-02", "environment": "GNS3_IOL_RUNNER=1"},
)
# plain docker nodes are left alone
node3 = await project.add_node(
compute, "web", None, node_type="docker", image="nginx", environment="FOO=1", adapters=1
)
assert node1.properties["application_id"] == 512
assert node2.properties["application_id"] == 513
assert "application_id" not in node3.properties
# IOU keeps its own pool: a subsequent IOU node still gets the lower half
node4 = await project.add_node(compute, "iou1", None, node_type="iou")
assert node4.properties["application_id"] == 1
@pytest.mark.asyncio
async def test_add_node_iou_with_multiple_projects(controller):
"""