mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
fix: keep payload-delivered state across container (re)creation
create() re-runs _parse_vendor_environment on every (re)create — and a stop removes the container, so every start recreates it. Resetting the controller-allocated application id there silently flipped nodes to the hash fallback after their first stop/start (MAC change, plus collision risk with the allocation pool), and dropped any pending startup-config delivered by a PUT. The application id and startup-config state now default via class attributes instead of being re-initialized by the parser, and the hash fallback is removed outright: starting an IOL node without a coordinated allocation raises an actionable error — an uncoordinated id could collide with the pool and blackhole traffic as a MAC loop. Found in the E2E run: node booted app id 512, came back as 566 (hash fallback) after one stop/start; the PUT-ed config edit vanished the same way.
This commit is contained in:
3 files changed
+75
-38
No files matched your search
@@ -81,6 +81,8 @@ def _make_vm(compute_project, manager, environment="GNS3_IOL_RUNNER=1",
|
||||
extra_volumes=extra_volumes or [], adapters=adapters,
|
||||
)
|
||||
vm._cid = "e90e34656842"
|
||||
# mirrors the controller flow, which always delivers an allocated id
|
||||
vm.application_id = 700
|
||||
return vm
|
||||
|
||||
|
||||
@@ -263,7 +265,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"] == 512 + int(vm.id.replace("-", ""), 16) % 511
|
||||
assert config["local-app"] == 700
|
||||
assert config["remote-app"] == 1023
|
||||
assert config["memory"] == 2048
|
||||
assert config["user-id"] == os.getuid()
|
||||
@@ -271,30 +273,33 @@ async def test_start_writes_iol_config(compute_project, manager):
|
||||
assert vm.status == "started"
|
||||
|
||||
|
||||
def test_local_app_is_distinct_per_node(compute_project, manager):
|
||||
# IOL derives interface MACs from the app ID: two nodes sharing one would
|
||||
# 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)
|
||||
assert vm1.application_id != vm2.application_id
|
||||
for vm in (vm1, vm2):
|
||||
assert 512 <= vm.application_id <= 1022
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_application_id_is_an_actionable_error(compute_project, manager):
|
||||
# There is no fallback id on purpose: an uncoordinated one could collide
|
||||
# with a pool allocation and blackhole traffic as a MAC loop. Nodes come
|
||||
# through the controller, which always allocates.
|
||||
vm = _make_vm(compute_project, manager)
|
||||
vm._application_id = None
|
||||
vm._get_container_state = AsyncioMagicMock(return_value="stopped")
|
||||
|
||||
with pytest.raises(DockerError, match="no application ID"):
|
||||
await vm._prepare_iol_runtime()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_allocated_application_id_overrides_fallback(compute_project, manager):
|
||||
async def test_allocated_application_id_used(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.
|
||||
# space, disjoint from IOU's); the node uses it verbatim.
|
||||
vm = _make_vm(compute_project, manager)
|
||||
vm.application_id = 700
|
||||
assert vm.application_id == 700
|
||||
vm.application_id = 701
|
||||
assert vm.application_id == 701
|
||||
_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
|
||||
assert config["local-app"] == 701
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -734,3 +739,22 @@ def test_asdict_exposes_startup_config_content(compute_project, manager):
|
||||
assert vm.asdict()["startup_config_content"] is None
|
||||
vm.startup_config_content = "hostname RouterOne"
|
||||
assert vm.asdict()["startup_config_content"] == "hostname RouterOne"
|
||||
|
||||
|
||||
def test_reparse_on_recreate_keeps_payload_state(compute_project, manager):
|
||||
# create() re-runs _parse_vendor_environment on every (re)create (a stop
|
||||
# removes the container, so every start recreates it): payload-delivered
|
||||
# state must survive the re-parse. Losing the allocated application id
|
||||
# would flip MACs to the fallback hash (colliding with the allocation
|
||||
# pool); losing the startup-config would drop pending PUT edits.
|
||||
vm = _make_vm(compute_project, manager)
|
||||
vm.application_id = 700
|
||||
vm.startup_config_content = "hostname RouterOne"
|
||||
|
||||
vm._parse_vendor_environment()
|
||||
|
||||
assert vm.application_id == 700
|
||||
assert vm.startup_config_content == "hostname RouterOne"
|
||||
assert vm._startup_config_dirty is True
|
||||
# environment-derived state still re-derives
|
||||
assert vm._gns3_init is False and vm._unix_socket_nio is True
|
||||
Reference in new issue
Block a user