mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
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:
4 files changed
+103
-16
No files matched your search
@@ -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.
|
||||
|
||||
@@ -35,7 +35,7 @@ import shutil
|
||||
|
||||
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
|
||||
from gns3server.compute.docker.docker_vm import DockerVM
|
||||
from gns3server.compute.docker.docker_error import DockerError, DockerHttp404Error
|
||||
from gns3server.compute.docker.docker_error import DockerError, DockerHttp304Error, DockerHttp404Error
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -138,6 +138,20 @@ class VendorDockerVM(DockerVM):
|
||||
pass
|
||||
self._console_exec_writer = None
|
||||
|
||||
async def _terminate_container(self):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
await self.manager.query("POST", f"containers/{self._cid}/stop", params={"t": 60})
|
||||
except DockerHttp304Error:
|
||||
pass # already stopped
|
||||
|
||||
async def start(self):
|
||||
await super().start()
|
||||
if self.status == "started" and not self._gns3_init:
|
||||
|
||||
Reference in new issue
Block a user