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.
This commit is contained in:
YueGuobin committed 2026-08-14 22:02:58 +08:00
1 parent 350f2b24e7
commit e9339faaa7
4 files changed
+103 -16

No files matched your search

+13 -5
View File
@@ -1206,12 +1206,8 @@ class DockerVM(BaseNode):
state = await self._get_container_state()
if state != "stopped" and state != "exited":
# SIGKILL immediately. GNS3 has already persisted container state
# (permissions via _fix_permissions, /gns3volumes) before this
# point, and the business process (often an interactive shell)
# ignores SIGTERM — so a stop grace period buys nothing but latency.
try:
await self.manager.query("POST", f"containers/{self._cid}/kill")
await self._terminate_container()
log.debug(f"Docker container '{self._name}' [{self._image}] stopped")
except DockerHttp409Error:
# Container is already stopped
@@ -1222,6 +1218,18 @@ class DockerVM(BaseNode):
return
self.status = "stopped"
async def _terminate_container(self):
"""
Final termination of a still-running container: immediate SIGKILL.
GNS3 has already persisted container state (permissions via
_fix_permissions, /gns3volumes) before this point, and the business
process (often an interactive shell) ignores SIGTERM — a stop grace
period buys nothing but latency. Vendor NOS containers override this
with a graceful SIGTERM shutdown (see VendorDockerVM).
"""
await self.manager.query("POST", f"containers/{self._cid}/kill")
async def pause(self):
"""
Pauses this Docker container.