From 41e877760905211a17b89b3fdba44d6265edb2d1 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sat, 25 Jul 2026 10:30:16 -0700 Subject: [PATCH] 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. --- gns3server/compute/docker/docker_vm.py | 2 +- tests/compute/docker/test_docker_vm.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index a5822d998..f71ec3776 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -869,7 +869,7 @@ class DockerVM(BaseNode): await self._fix_permissions() state = await self._get_container_state() - if state != "stopped" or state != "exited": + if state != "stopped" and state != "exited": # t=5 number of seconds to wait before killing the container try: await self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5}) diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 332a52c17..f43fb7460 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -1503,3 +1503,19 @@ async def test_read_console_output_with_binary_mode(vm): with asyncio_patch('gns3server.compute.docker.docker_vm.DockerVM.stop'): await vm._read_console_output(input_stream, output_stream) output_stream.feed_data.assert_called_once_with(b"test") + + +async def test_stop_exited_container_no_stop_query(vm): + + vm._ubridge_hypervisor = None + vm._fix_permissions = MagicMock() + + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="exited"): + with asyncio_patch("gns3server.compute.docker.Docker.query") as mock_query: + vm._permissions_fixed = False + await vm.stop() + assert not any( + call.args[:2] == ("POST", "containers/e90e34656842/stop") + for call in mock_query.mock_calls + ) + assert vm.status == "stopped"