mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
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().
This commit is contained in:
parent
e9339faaa7
commit
62076c1727
@ -65,7 +65,7 @@ graph TB
|
||||
| config injection | `extra_configs: [{target, content}]` (template/node/appliance schema field) | content written under the node dir, bind-mounted **read-only** at `target` — seeds NOS startup configs without rebuilding the image | `docker_vm.py` `_mount_binds()`; persisted in `docker_templates.extra_configs` (Alembic migration) |
|
||||
| udev masking | `GNS3_MASK_UDEV=1` | `/dev/null` over the 5 udev systemd units **and** `/bin|/sbin|/usr/bin/udevadm` | `docker_vm.py` `create()` |
|
||||
| generic unit mask | `GNS3_MASK_SYSTEMD=u1,u2` | `/dev/null` over arbitrary `/etc/systemd/system/<unit>` | `docker_vm.py` `create()` |
|
||||
| graceful stop | automatic for vendor containers (`docker_exec`) | stop sends SIGTERM and waits up to 60 s (Docker SIGKILLs after the grace period) instead of the base class' immediate kill — systemd NOS images require a graceful shutdown | `vendor_docker_vm.py` `_terminate_container()` |
|
||||
| graceful stop | `GNS3_STOP_TIMEOUT=60` (seconds; default 60) | vendor containers are stopped with SIGTERM + a grace period (Docker SIGKILLs once it expires) instead of the base class' immediate kill — systemd NOS images require a graceful shutdown | `vendor_docker_vm.py` `_terminate_container()` |
|
||||
| host check | automatic at Docker connect | read-only `/proc` check of inotify/file-max/FUSE; warns with exact fix commands (server is unprivileged — it can only check) | `compute/docker/__init__.py` `_check_host_readiness()` |
|
||||
|
||||
`GNS3_*` variables are consumed host-side only and never forwarded into the
|
||||
@ -200,7 +200,7 @@ sequenceDiagram
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.3 | 2026-08-14 | Persistence corrected: XR's live data layer is `/xr-storage` (the image's symlink farm is materialized into real directories at bootstrap; `/xr-storage-shadow` is a pristine spare) — the appliance persists both. Vendor containers now stop gracefully (SIGTERM + 60 s grace) instead of being SIGKILLed on the spot. |
|
||||
| 1.3 | 2026-08-14 | Persistence corrected: XR's live data layer is `/xr-storage` (the image's symlink farm is materialized into real directories at bootstrap; `/xr-storage-shadow` is a pristine spare) — the appliance persists both. Vendor containers now stop gracefully (SIGTERM + `GNS3_STOP_TIMEOUT` grace, default 60 s) instead of being SIGKILLed on the spot. |
|
||||
| 1.2 | 2026-08-14 | Link UDP self-loop root-caused to a port-allocation race and fixed — see [link-udp-self-loop](../bugs/link-udp-self-loop.md) (now marked Fixed). |
|
||||
| 1.1 | 2026-08-14 | Datapath validated end-to-end (XRd brings its own interfaces up; ARP/ICMP bidirectional). Add troubleshooting entry for the one-way-link symptom (GNS3 link UDP self-loop bug, see bugs/link-udp-self-loop.md). |
|
||||
| 1.0 | 2026-08-14 | Initial documentation of the XRd control-plane adaptation: vendor path requirement, shm/devices/extra_configs/udev-mask mechanisms, host-disturbance root causes, appliance recipe. |
|
||||
|
||||
@ -55,6 +55,8 @@ class VendorDockerVM(DockerVM):
|
||||
(adapter order) instead of default ``eth{N}``.
|
||||
* ``GNS3_CONSOLE_CMD=/opt/srlinux/bin/sr_cli`` — command run inside the
|
||||
container by the ``docker_exec`` console (defaults to ``/bin/sh``).
|
||||
* ``GNS3_STOP_TIMEOUT=60`` — SIGTERM grace period in seconds when stopping
|
||||
the container (default 60; Docker SIGKILLs once it expires).
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -66,6 +68,7 @@ class VendorDockerVM(DockerVM):
|
||||
self._interface_names = []
|
||||
self._console_cmd = None
|
||||
self._console_exec_writer = None
|
||||
self._stop_timeout = 60
|
||||
|
||||
if self._environment:
|
||||
for _line in self._environment.splitlines():
|
||||
@ -78,6 +81,13 @@ class VendorDockerVM(DockerVM):
|
||||
]
|
||||
elif _line.startswith("GNS3_CONSOLE_CMD="):
|
||||
self._console_cmd = _line.split("=", 1)[1].strip()
|
||||
elif _line.startswith("GNS3_STOP_TIMEOUT="):
|
||||
try:
|
||||
timeout = int(_line.split("=", 1)[1].strip())
|
||||
if 1 <= timeout <= 600:
|
||||
self._stop_timeout = timeout
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# ---- hook overrides ---------------------------------------------------
|
||||
|
||||
@ -142,13 +152,14 @@ class VendorDockerVM(DockerVM):
|
||||
"""
|
||||
Override: vendor NOS containers run systemd and require a graceful
|
||||
shutdown (e.g. Cisco XRd treats an abrupt SIGKILL as an unclean
|
||||
shutdown). Send SIGTERM and wait up to 60 s for the services to stop;
|
||||
Docker SIGKILLs the container itself once the grace period expires,
|
||||
so no fallback kill is needed. The blocking stop call sits well
|
||||
inside the manager's default 300 s query timeout.
|
||||
shutdown). Send SIGTERM and wait up to ``GNS3_STOP_TIMEOUT`` seconds
|
||||
(default 60) for the services to stop; Docker SIGKILLs the container
|
||||
itself once the grace period expires, so no fallback kill is needed.
|
||||
The blocking stop call sits well inside the manager's default 300 s
|
||||
query timeout.
|
||||
"""
|
||||
try:
|
||||
await self.manager.query("POST", f"containers/{self._cid}/stop", params={"t": 60})
|
||||
await self.manager.query("POST", f"containers/{self._cid}/stop", params={"t": self._stop_timeout})
|
||||
except DockerHttp304Error:
|
||||
pass # already stopped
|
||||
|
||||
|
||||
@ -667,3 +667,30 @@ async def test_stop_uses_graceful_termination(compute_project, manager):
|
||||
) as mock_term:
|
||||
await vm.stop()
|
||||
mock_term.assert_called_once()
|
||||
|
||||
|
||||
def test_env_stop_timeout(compute_project, manager):
|
||||
vm = _make_vm(compute_project, manager, environment="GNS3_STOP_TIMEOUT=120")
|
||||
assert vm._stop_timeout == 120
|
||||
|
||||
|
||||
def test_env_stop_timeout_default_60(compute_project, manager):
|
||||
vm = _make_vm(compute_project, manager, environment="GNS3_SKIP_INIT=1")
|
||||
assert vm._stop_timeout == 60
|
||||
|
||||
|
||||
def test_env_stop_timeout_invalid_keeps_default(compute_project, manager):
|
||||
vm = _make_vm(compute_project, manager, environment="GNS3_STOP_TIMEOUT=abc")
|
||||
assert vm._stop_timeout == 60
|
||||
vm = _make_vm(compute_project, manager, environment="GNS3_STOP_TIMEOUT=9999")
|
||||
assert vm._stop_timeout == 60
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_terminate_container_uses_env_timeout(compute_project, manager):
|
||||
vm = _make_vm(compute_project, manager, environment="GNS3_STOP_TIMEOUT=120")
|
||||
manager.query = AsyncioMagicMock()
|
||||
|
||||
await vm._terminate_container()
|
||||
|
||||
manager.query.assert_called_once_with("POST", "containers/e90e34656842/stop", params={"t": 120})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user