feat: IOU-style startup-config for IOL Docker nodes

Templates reference a config file with the GNS3_IOL_STARTUP_CONFIG
environment knob; the controller materializes the file content into
startup_config_content on node creation (sent once, knob consumed —
the same pattern as the IOU startup_config mapping). The compute builds
the content into the node's nvram_<app id> at the next start using the
IOU nvram_import utility (IOL and IOU share the nvram container format,
verified against iol-xe 17.18.02): valid config at boot, no setup
dialog, %h hostname substitution, hostname rewrite on rename.

Semantics verified against the runner: IOL boots from NVRAM whenever it
holds a config, so a plain stop/start never re-applies the startup
config and 'write memory' survives restarts; an explicit content edit
(PUT) is re-applied on the next start and wins over the saved config,
like IOU.
This commit is contained in:
YueGuobin committed 2026-09-05 21:54:52 +08:00
1 parent 9147deccaa
commit 8b3aafbbdc
8 files changed
+440 -9

No files matched your search

+43
View File
@@ -40,6 +40,30 @@ import logging
log = logging.getLogger(__name__)
def _extract_iol_startup_config_knob(environment):
"""
Split the GNS3_IOL_STARTUP_CONFIG knob out of a Docker environment
(iol-runner images): the value is a config file name, resolved by the
controller in its configs directory.
:returns: (filename, environment without the knob line); filename is None
when the knob is absent or empty.
"""
if not environment or "GNS3_IOL_STARTUP_CONFIG=" not in environment:
return None, environment
filename = None
kept = []
for line in environment.splitlines():
stripped = line.strip().rstrip(",")
if stripped.startswith("GNS3_IOL_STARTUP_CONFIG="):
if filename is None:
filename = stripped.split("=", 1)[1].strip()
else:
kept.append(line)
return filename or None, "\n".join(kept)
class Node:
# These properties are used only on controller and are not forwarded to the compute
CONTROLLER_ONLY_PROPERTIES = [
@@ -573,6 +597,25 @@ class Node:
data[v] = self._base_config_file_content(self._properties[k])
del data[k]
del self._properties[k] # We send the file only one time
# IOL runner (Docker) startup-config: translate the config file
# referenced by the GNS3_IOL_STARTUP_CONFIG environment knob into
# content, like the mappings above. Sent only once — afterwards
# the node's configuration lives in its NVRAM on the compute (a
# reloaded node is recreated without the knob and boots from the
# persistent NVRAM, preserving `write memory`).
if self._node_type == "docker":
filename, environment = _extract_iol_startup_config_knob(self._properties.get("environment"))
if filename:
content = self._base_config_file_content(filename)
if content:
data["startup_config_content"] = content
data["environment"] = environment
self._properties["environment"] = environment
else:
log.warning(
f"Cannot load IOL startup-config file '{filename}' for node '{self._name}': file not found"
)
data["name"] = self._name
# For remote computes, convert absolute image paths to relative paths