Commit Graph
140 Commits
Author SHA1 Message Date
YueGuobin 110e041e56 docker: cap GNS3_STOP_TIMEOUT at 210 s (controller stop budget)
The 600 s clamp was unreachable in practice: the controller's stop
request times out at 240 s (controller/node.py) and the Docker stop
query gets the value +30 s as its HTTP timeout, so anything above 210
would abort upstream first and surface an error while the stop keeps
running server-side. Cap at the derived ceiling and document the chain
in the clamp and the docstring.
2026-08-15 01:25:50 +08:00
YueGuobin 9604c85fda docker: harden the shm/devices/extra_configs/masking work (code review)
Nine fixes from a review of the docker-shm-devices diff:

* GNS3_STOP_TIMEOUT >300 s aborted at the manager's default HTTP timeout
  before Docker finished the stop — the stop query now gets a timeout
  with a margin over the grace period.
* Overlapping bind targets (GNS3_MASK_UDEV + GNS3_MASK_SYSTEMD on the
  same unit, a unit named twice, an extra_configs target equal to a
  masked unit) made Docker reject the create with 'Duplicate mount
  point' — Mounts are deduplicated by target.
* ExtraConfig.target now carries a pydantic validator (absolute file
  path, no '..'), so bad targets 422 at template-save time instead of
  failing at node-create time after a multi-GB image pull; directory
  forms ('/', '/etc/') are also rejected by the runtime guard instead
  of raising IsADirectoryError (raw 500).
* _check_host_readiness skipped every remaining check when one
  /proc/sys key was unreadable (mid-loop return) — now continues.
* The base-class GNS3_* env parser strips trailing commas like the
  vendor parser, so 'GNS3_MASK_UDEV=1,' composed from a list still
  activates.
* Vendor env knobs are re-parsed on every create(), so a PUT to the
  node's environment takes effect on the next (re)create.
* The graceful SIGTERM stop is now limited to the explicit user stop
  route; delete/update/close/crash-cleanup keep the immediate kill
  (those paths force-delete or recreate the container right after).
* An extra_configs target beneath a persisted volume is shadowed by the
  volume bind — warn at create time.
2026-08-15 00:52:55 +08:00
YueGuobin 62076c1727 docker: make the vendor graceful-stop grace period configurable (GNS3_STOP_TIMEOUT)
The 60 s SIGTERM grace was hardcoded, unlike every other vendor knob
(GNS3_SHM_SIZE, GNS3_DEVICES, GNS3_MASK_UDEV, ...) which rides the
environment line. Parse GNS3_STOP_TIMEOUT=<seconds> (default 60,
clamped to 1-600, invalid values keep the default) and use it in
VendorDockerVM._terminate_container().
2026-08-14 22:11:34 +08:00
YueGuobin e9339faaa7 docker: graceful stop for vendor NOS containers (SIGTERM + 60s grace)
DockerVM.stop() terminated containers with an immediate SIGKILL — fine
for init.sh-based containers whose state is persisted beforehand, but a
systemd NOS (Cisco XRd, SR Linux) needs a graceful shutdown and treats
the abrupt kill as an unclean shutdown (exit 137 on every stop).

Extract the final termination into _terminate_container() and override
it in VendorDockerVM: POST /containers/{id}/stop?t=60 sends SIGTERM and
waits for systemd to stop services; Docker itself SIGKILLs the
container once the grace period expires, so no fallback is needed.
Docker's 304 (already stopped) is swallowed.
2026-08-14 22:02:58 +08:00
YueGuobin e8b51aa12c docker: also null-bind udevadm in GNS3_MASK_UDEV
Masking the udev systemd units stopped the daemon's coldplug (host audio
resets), but host USB devices still reconnected on every XRd start. A/B
testing with plain `docker run` isolated the trigger: XRd's own
xr_startup.sh calls udevadm directly (USB license-dongle probing, e.g.
`udevadm trigger --action=add --parent-match=<usb device>`), which
synthesizes uevents into the host kernel from the privileged container --
no udevd required.

GNS3_MASK_UDEV=1 now also binds /dev/null over the udevadm binary
(/bin, /sbin, /usr/bin). Verified with a plain-run experiment: with the
bind, host udev monitor shows zero usb/input/hid/sound events during XRd
boot (only normal docker veth traffic), and XRd itself boots to running
state -- it does not need udevadm under GNS3 (interfaces are pre-created
veths).
2026-08-14 19:09:48 +08:00
YueGuobin 347537f1b3 docker: mask systemd-udevd in privileged containers (GNS3_MASK_UDEV)
A privileged systemd-based NOS container (Cisco XRd boots /usr/sbin/init)
runs systemd-udevd, which on startup coldplugs every device it can reach.
In privileged mode that includes the HOST's USB/input/audio/disk devices,
so every XRd start reconnects USB, mutes audio, and disrupts the host
journal -- highly disruptive on Linux desktops (caught in the act: the
container's udevd was even rescanning the host BTRFS root device).

XRd doesn't need udev (its interfaces are pre-created by GNS3 veth and
mapped via XR_INTERFACES). Add two opt-in env vars, consumed host-side at
container create time in the inherited DockerVM.create (so VendorDockerVM
nodes get it too):

  GNS3_MASK_UDEV=1        -> bind /dev/null over the udevd unit, its two
                             activation sockets, and the coldplug/settle
                             trigger services
  GNS3_MASK_SYSTEMD=u1,u2 -> bind /dev/null over arbitrary units in
                             /etc/systemd/system/ (comma/semicolon list)

Only injected when set, so ordinary nodes are unaffected.
2026-08-14 01:12:21 +08:00
YueGuobin 08e37a4509 docker: inject config files into containers via extra_configs
Add an `extra_configs` field (list of {target, content}) to the docker
node/template/appliance schemas. For each entry GNS3 writes `content` to a
file in the node working directory and bind-mounts it read-only at `target`
inside the container.

This lets a NOS appliance seed its startup config without rebuilding the
image: XRd points XR_FIRST_BOOT_CONFIG at an injected /firstboot.cfg, FRR at
/etc/frr/frr.conf, etc. The bind is a single-file mount applied at create
time, so it works for both the generic init.sh path and vendor nodes that
skip init.sh (console_type=docker_exec). Entries are only injected when
present, so ordinary nodes are unaffected.

The content can't go through `environment` (it is line-delimited, one var per
line), hence a dedicated field -- the same plumbing shape as extra_volumes.
2026-08-13 23:33:34 +08:00
YueGuobin 2bee34031c docker: warn about low inotify/file-max and missing FUSE at connect
Add a read-only _check_host_readiness() that runs once after the Docker
daemon connection is established. It reads /proc/sys inotify/file-max
limits and /proc/filesystems (for FUSE), and logs a warning with the exact
commands to fix when they are too low for heavy containers -- XRd wants
~4000 inotify instances per node against a stock default of 128.

The server runs unprivileged (only the setuid ubridge helper has root), so
it can only check, not set; the warning tells the admin exactly what to
raise once. Stays silent when the limits are already sufficient.
2026-08-13 22:39:48 +08:00
YueGuobin 86d30f34b4 docker: inject /dev/shm size and host devices via HostConfig from env
Heavy NOS containers (e.g. Cisco XRd) need /dev/shm larger than Docker's
64 MB default and host device nodes such as /dev/fuse. Add two opt-in
environment variables, consumed host-side and applied as native Docker
HostConfig keys at create time:

  GNS3_SHM_SIZE (MB)  -> HostConfig.ShmSize (bytes)
  GNS3_DEVICES        -> HostConfig.Devices in `docker run --device` syntax
                         (host[:container[:perm]]; Docker resolves major/minor
                         from the host node itself)

Native HostConfig (rather than remount/mknod inside init.sh) is used so this
works for vendor NOS nodes that skip init.sh (console_type=docker_exec) --
the path XRd must take, since GNS3's init.sh wrapper crashes XRd's glibc
loader. It applies whether or not init.sh runs, needs no schema/API/UI
change (reuses the `environment` field), and only takes effect when the vars
are set, so ordinary nodes keep default Docker behaviour.

GNS3_-prefixed user env vars stay dropped from the container environment
(only consumed here host-side), keeping GNS3-injected vars safe.
2026-08-13 22:39:48 +08:00
YueGuobin 8889cea38b fix: recreate docker_exec console on reconnect after CLI exit
The reconnect-blank-screen bug: when sr_cli exited (quit / idle timeout /
crash) the while-true wrapper restarted it mid-session with no client
attached, so its startup CPR probe (\e[6n) went unanswered and the TUI
degraded/blocked. On reconnect lazy_started=True skipped recreation, so the
client saw a blank screen.

Fix: drop the while-true wrapper. Now when the CLI exits, the exec pty
closes (EOF), the broadcast task ends, and the next client connection
detects the dead upstream via _upstream_alive() and recreates the exec —
with a terminal attached, so CPR is answered. A live exec is reused
(just a Ctrl-L redraw).

_LazyExecTelnetServer is extracted from a closure to module level so the
reconnect/recreate logic is unit-testable. Add 9 tests covering
_upstream_alive states and the recreate-on-death / reuse-if-live /
close-half-dead-writer / no-while-true behaviors.

Full Docker suite (120) passes.
2026-08-13 02:05:17 +08:00
YueGuobin 1e154ac85f test: add tests for VendorDockerVM and the docker_exec factory
25 tests covering:
- Docker.create_node factory: selects VendorDockerVM iff console_type ==
  docker_exec, DockerVM otherwise (including telnet/ssh/vnc/http/none/spice)
- GNS3_* env parsing: SKIP_INIT, INTERFACE_NAMES, CONSOLE_CMD (single and
  multiline), defaults
- create(): init.sh skipped under GNS3_SKIP_INIT, prepended otherwise;
  GNS3_MAX_ETHERNET follows the interface rename; /etc/network mount dropped
  under SKIP_INIT (and host skeleton dir removed) but kept without it
- _add_ubridge_connection: move_to_ns targets the renamed interface
  (mgmt0) or falls back to eth{N}
- start(): docker_exec console dispatch + SKIP_INIT volume bridge + permission
  fix; without SKIP_INIT the vendor passes are skipped
- _fix_permissions: skips dead/missing containers (no restart), targets
  /gns3volumes bind-mount paths
- _setup_skip_init_volumes: runs the docker exec bridge script
- _cleanup_console_resources: closes the exec pty writer

Full Docker suite (111) and compute suite (395) pass — the four hook
extractions in DockerVM introduce no regressions.
2026-08-13 02:05:17 +08:00
YueGuobin 2f70bd6daa revert: drop _connect_nio thread-pool executor, restore async _ubridge_send
The _connect_nio thread-pool optimisation (send_batch_sync) targeted
node-start performance, but start_all already runs at concurrency=3
(by design, to avoid overwhelming the host).  It also introduced a
Python 3.13 incompatibility (trsock.setblocking forbidden) that
prevented docker nodes from starting.  Since node-start is not the
target of this branch (project-open link creation is), revert to the
simple per-command async _ubridge_send.

The project-open batch NIO dispatch (create_batch_nios) is unaffected —
it never called _connect_nio (nodes aren't started during open).
2026-08-11 01:16:13 +08:00
YueGuobin 05934fa8e8 perf: offload per-NIO ubridge commands to thread-pool executor
Replace the 3-5 sequential await _ubridge_send calls in _connect_nio
with a single run_in_executor batch.  The batch holds the node-level
asyncio Lock to prevent interleaving with async sends, then uses the
hypervisor's new send_batch_sync method which does blocking socket
sendall/recv inside the thread pool.  Different nodes' batches now
run in true OS-thread parallelism rather than serialising through
the asyncio event loop between every command.

- ubridge_hypervisor.send_batch_sync: blocking batch send using
  the underlying socket from the asyncio transport, protected by
  threading.Lock.
- _connect_nio: builds command list (add_nio_udp, start_capture,
  bridge start, reset_packet_filters, add_packet_filter) and
  dispatches to the default executor.
2026-08-10 23:06:54 +08:00
YueGuobin e7dfe9c7c4 fix: SIGKILL docker container on stop instead of 5s grace period
Docker node stop took ~5s every time. The stop API grace period
(params t=5, unchanged since 2015) was always exhausted: the business
process (often an interactive shell) ignores SIGTERM, and GNS3 doesn't
depend on graceful shutdown — _fix_permissions and /gns3volumes already
persist container state before stop() is called.

Use POST /containers/{id}/kill (SIGKILL, zero delay) instead of stop.
The 409 (container already stopped) replaces the previous 304 handling
for the race where the container exits between the state check and the call.

t=5 traced to commit 33edbefa3 (2015-10-14) "Docker cleanup and
improvements" — introduced with no recorded rationale.
2026-08-10 21:48:26 +08:00
grossmj 544d15c87e fix: tests after merging 2026-08-08 16:50:19 +02:00
grossmj dff79f65d3 Merge branch '2.2' into 3.1
# Conflicts:
#	gns3server/compute/qemu/qemu_vm.py
#	gns3server/controller/import_project.py
#	gns3server/controller/project.py
#	gns3server/crash_report.py
#	gns3server/version.py
#	tests/compute/docker/test_docker_vm.py
#	tests/controller/test_import_project.py
#	tests/controller/test_project.py
2026-08-08 16:27:14 +02:00
Sanjay Santhanam 41e8777609 fix: correct always-true state check in DockerVM.stop()
The condition 'state != "stopped" or state != "exited"' is a tautology,
so the state check was a no-op and a stop request was sent even for a
container that had already exited.

_get_container_state() never returns "stopped" (only "running",
"paused" or "exited"), so the intended negation of the condition used in
_fix_permissions() requires 'and', not 'or' (De Morgan's law).

Added a regression test asserting no stop query is issued for an
already-exited container.
2026-07-25 10:30:16 -07:00
Cristi 2abc7e09a3 Adds controller and compute API support for explicitly pulling or updating Docker images. 2026-07-24 16:40:20 +03:00
YueGuobin ce95c89b64 Fix _fix_permissions test: set process.returncode=0 and update assertion
- Set returncode=0 on mock process to follow the success path
- Update assertion to include stderr=asyncio.subprocess.PIPE parameter
2026-06-10 00:44:12 +08:00
grossmj c5b9f379fe Merge remote-tracking branch 'origin/2.2' into 3.0
# Conflicts:
#	Dockerfile
#	dev-requirements.txt
#	gns3server/compute/qemu/qemu_vm.py
#	gns3server/version.py
#	requirements.txt
2026-02-20 16:28:53 +08:00
grossmj 715b7032bc Fix tests 2026-02-20 15:51:49 +08:00
grossmj 1d45df6613 Upgrade dependencies 2026-01-24 21:38:58 +08:00
grossmj 60c10e7ecc Merge remote-tracking branch 'origin/2.2' into 3.0
# Conflicts:
#	.github/workflows/testing.yml
#	CHANGELOG
#	README.md
#	gns3server/appliances/infix.gns3a
#	gns3server/compute/docker/__init__.py
#	gns3server/compute/docker/docker_vm.py
#	gns3server/crash_report.py
#	gns3server/version.py
#	gns3server/web/web_server.py
#	setup.py
#	tests/controller/test_project.py
2025-11-29 11:42:02 +10:00
grossmj f2aaf5ab38 Docker API version requirements and process to handle older daemons 2025-11-17 22:26:18 +10:00
grossmj bae6221991 Set name of container in Docker 2025-11-17 10:59:05 +10:00
grossmj 232b9c2806 Fix tests 2025-05-25 17:35:45 +02:00
grossmj 8c7c17b889 Upgrade dependencies and fix Pydantic warnings 2024-10-31 17:09:52 +10:00
grossmj 7e902726bf Fix issues after merge 2024-10-31 13:06:17 +10:00
grossmj c4fcb43e78 Merge branch '2.2' into 3.0
# Conflicts:
#	.github/workflows/testing.yml
#	CHANGELOG
#	README.md
#	dev-requirements.txt
#	gns3server/compute/docker/docker_vm.py
#	gns3server/controller/export_project.py
#	gns3server/controller/import_project.py
#	gns3server/controller/project.py
#	gns3server/crash_report.py
#	gns3server/static/web-ui/index.html
#	gns3server/static/web-ui/main.df8c319a3da6fb0e3629.js
#	gns3server/version.py
#	requirements.txt
#	setup.py
#	tests/compute/docker/test_docker_vm.py
#	tests/controller/test_import_project.py
2024-10-31 12:54:35 +10:00
grossmj 48b7e6ca50 Fix run Docker containers with user namespaces enabled. Fixes #2414 2024-10-26 19:03:05 +10:00
grossmj a7b56739a9 Fixes after merging 2024-09-23 14:08:24 +07:00
grossmj 8ce0cbf7f7 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3server/compute/base_node.py
#	gns3server/compute/docker/docker_vm.py
#	gns3server/compute/dynamips/nodes/c1700.py
#	gns3server/compute/dynamips/nodes/c2600.py
#	gns3server/compute/dynamips/nodes/c2691.py
#	gns3server/compute/dynamips/nodes/c3600.py
#	gns3server/compute/dynamips/nodes/c3725.py
#	gns3server/compute/dynamips/nodes/c3745.py
#	gns3server/compute/dynamips/nodes/c7200.py
#	gns3server/compute/dynamips/nodes/router.py
#	gns3server/compute/qemu/qemu_vm.py
#	gns3server/controller/node.py
#	gns3server/controller/topology.py
#	gns3server/handlers/api/compute/docker_handler.py
#	gns3server/handlers/api/compute/dynamips_vm_handler.py
#	gns3server/handlers/api/compute/qemu_handler.py
#	gns3server/schemas/docker.py
#	gns3server/schemas/docker_template.py
#	gns3server/schemas/dynamips_template.py
#	gns3server/schemas/dynamips_vm.py
#	gns3server/schemas/node.py
#	gns3server/schemas/qemu.py
#	gns3server/schemas/qemu_template.py
2024-09-23 13:28:56 +07:00
Jeremy Grossmann af6f34b2ca Revert "Backport auxiliary console support for Qemu, Docker and Dynamips nodes" 2024-09-23 13:10:58 +07:00
grossmj d3138daa23 Fix Docker tests 2024-09-22 22:12:40 +07:00
grossmj 7619080a50 Merge branch '2.2' into 3.0
# Conflicts:
#	gns3server/compute/base_node.py
#	gns3server/compute/docker/docker_vm.py
#	gns3server/compute/dynamips/nodes/c1700.py
#	gns3server/compute/dynamips/nodes/c2600.py
#	gns3server/compute/dynamips/nodes/c2691.py
#	gns3server/compute/dynamips/nodes/c3600.py
#	gns3server/compute/dynamips/nodes/c3725.py
#	gns3server/compute/dynamips/nodes/c3745.py
#	gns3server/compute/dynamips/nodes/c7200.py
#	gns3server/compute/dynamips/nodes/router.py
#	gns3server/compute/qemu/qemu_vm.py
#	gns3server/controller/node.py
#	gns3server/handlers/api/compute/docker_handler.py
#	gns3server/handlers/api/compute/dynamips_vm_handler.py
#	gns3server/handlers/api/compute/qemu_handler.py
#	gns3server/schemas/docker.py
#	gns3server/schemas/docker_template.py
#	gns3server/schemas/dynamips_template.py
#	gns3server/schemas/dynamips_vm.py
#	gns3server/schemas/node.py
#	gns3server/schemas/qemu.py
#	gns3server/schemas/qemu_template.py
2024-09-22 22:01:06 +07:00
grossmj c41c11eb34 Backport auxiliary console support for Qemu, Docker and Dynamips nodes 2024-09-22 18:29:04 +07:00
grossmj 22f022cc22 Fix for running Docker containers with user namespaces enabled 2024-09-19 10:40:22 +07:00
grossmj 8429494280 Test base MAC address for Docker VMs 2024-09-19 10:19:07 +07:00
grossmj 3214f52e9e Merge branch '2.2' into 3.0
# Conflicts:
#	gns3server/compute/docker/docker_vm.py
#	gns3server/handlers/api/compute/docker_handler.py
#	gns3server/schemas/docker.py
#	gns3server/schemas/docker_template.py
#	tests/compute/docker/test_docker_vm.py
2024-09-18 17:29:06 +07:00
grossmj 3792901dc7 Support for configuring MAC address in Docker containers 2024-09-18 16:30:22 +07:00
grossmj d3860ba84b Merge branch '2.2' into 3.0
# Conflicts:
#	CHANGELOG
#	dev-requirements.txt
#	gns3server/compute/qemu/__init__.py
#	gns3server/compute/vmware/__init__.py
#	gns3server/controller/snapshot.py
#	gns3server/crash_report.py
#	gns3server/handlers/api/compute/atm_switch_handler.py
#	gns3server/run.py
#	gns3server/static/web-ui/26.77d4bfd104f37c42e028.js
#	gns3server/static/web-ui/index.html
#	gns3server/static/web-ui/runtime.415291667f70565cd8ef.js
#	gns3server/utils/__init__.py
#	gns3server/utils/images.py
#	gns3server/utils/interfaces.py
#	gns3server/version.py
#	gns3server/web/web_server.py
#	pytest.ini
#	requirements.txt
#	scripts/update-bundled-web-ui.sh
#	setup.py
#	tests/api/routes/compute/test_dynamips_nodes.py
#	tests/compute/builtin/nodes/test_cloud.py
#	tests/compute/docker/test_docker.py
#	tests/compute/docker/test_docker_vm.py
#	tests/compute/dynamips/test_dynamips_manager.py
#	tests/compute/dynamips/test_dynamips_router.py
#	tests/compute/iou/test_iou_vm.py
#	tests/compute/qemu/test_qcow2.py
#	tests/compute/qemu/test_qemu_manager.py
#	tests/compute/qemu/test_qemu_vm.py
#	tests/compute/test_base_node.py
#	tests/compute/test_manager.py
#	tests/compute/test_project.py
#	tests/compute/traceng/test_traceng_vm.py
#	tests/compute/virtualbox/test_virtualbox_manager.py
#	tests/compute/virtualbox/test_virtualbox_vm.py
#	tests/compute/vmware/test_vmware_manager.py
#	tests/compute/vmware/test_vmware_vm.py
#	tests/compute/vpcs/test_vpcs_vm.py
#	tests/conftest.py
#	tests/controller/gns3vm/test_remote_gns3_vm.py
#	tests/controller/gns3vm/test_virtualbox_gns3_vm.py
#	tests/controller/gns3vm/test_vmware_gns3_vm.py
#	tests/controller/test_export_project.py
#	tests/controller/test_gns3vm.py
#	tests/controller/test_import_project.py
#	tests/test_config.py
#	tests/utils/test_asyncio.py
#	tests/utils/test_images.py
#	tests/web/test_response.py
2024-05-16 17:53:39 +07:00
grossmj 46a9fdb61d Remove event_loop 2024-05-09 17:53:02 +07:00
grossmj 9958b876cf Do not run Docker VM tests on Windows 2024-05-09 17:36:58 +07:00
grossmj 33499be425 Fix warnings in tests 2024-05-09 17:11:12 +07:00
grossmj 2609849507 Fix tests after updating error message when busybox is not installed. 2024-04-23 18:00:37 +07:00
grossmj e9827653ae Merge branch '2.2' into 3.0
# Conflicts:
#	gns3server/crash_report.py
#	gns3server/static/web-ui/index.html
#	gns3server/version.py
#	tests/compute/docker/test_docker_vm.py
2024-03-05 01:05:46 +08:00
grossmj 0137688ba7 Test busybox is installed 2024-02-22 13:05:48 +08:00
grossmj 84db3b9996 Fix not all Docker resources are copied to a writable location 2024-02-22 12:59:44 +08:00
grossmj 1a53c9aabf Backport from v3: install Docker resources in a writable location at runtime. 2024-02-14 16:13:45 +08:00
grossmj 09ff807055 Install Docker resources in writable location 2023-11-13 11:23:26 +10:00