From 45b5f8d7e2ca8079dbf815d5b0417e20c3dcd48d Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 11:51:55 +0800 Subject: [PATCH 1/8] Fix Docker container status detection on node creation When a Docker container is created (e.g., when loading a project), the node status should reflect the actual container state. Previously, the node status was always set to 'stopped' even if the container was already running. This fix checks the container state after creation and updates the node status accordingly: - If container is running: status = 'started' - If container is paused: status = 'suspended' - If container is exited: status = 'stopped' (default) This ensures that project.is_running() correctly detects running Docker containers when attempting to export/duplicate a project, fixing the issue where running Docker nodes were not detected and prompted for shutdown. --- gns3server/compute/docker/docker_vm.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 988d61cf3..393b21581 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -587,6 +587,20 @@ class DockerVM(BaseNode): log.info(f"CPU limit set to {self._cpus} CPUs") if self._memory > 0: log.info(f"Memory limit set to {self._memory} MB") + + # Check if the container is already running and update the node status accordingly + # This can happen when the server restarts and the container continues running + try: + state = await self._get_container_state() + if state == "running": + self.status = "started" + log.info(f"Docker container '{self._name}' is already running") + elif state == "paused": + self.status = "suspended" + log.info(f"Docker container '{self._name}' is paused") + except DockerError as e: + log.warning(f"Could not check container state for '{self._name}': {e}") + return True def _format_env(self, variables, env): From c4440d882d199003992c08b84071612183516ac8 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 12:15:27 +0800 Subject: [PATCH 2/8] Add running project check for fast duplication Add is_running() check at the beginning of _fast_duplication() to prevent duplicating a project while nodes are running. Previously, only the export/import fallback path had this check, which meant running nodes were not detected when fast duplication succeeded. This aligns with the duplicate API behavior and provides a consistent safeguard against data inconsistencies. --- gns3server/controller/project.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index f6697dfaa..0526df838 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -1404,6 +1404,10 @@ class Project: :param reset_mac_addresses: Reset MAC addresses for the duplicated project """ + # We don't duplicate a running project + if self.is_running(): + raise ControllerError("Project must be stopped in order to duplicate it") + # remote replication is not supported with remote computes for compute in self.computes: if compute.id != "local": From b6e1f847401b2affd88b30d85f2f649f4e8b0f9e Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 12:18:28 +0800 Subject: [PATCH 3/8] Move running project check before fast duplication Move the is_running() check from _fast_duplication() to duplicate() to avoid the error message being wrapped by the except Exception handler. This ensures the error message is clean and prevents wasted fast duplication attempts on running projects. --- gns3server/controller/project.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 0526df838..aa3b361e1 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -1334,6 +1334,9 @@ class Project: self.dump() assert self._status != "closed" + if self.is_running(): + raise ControllerError("Project must be stopped in order to duplicate it") + try: proj = await self._fast_duplication(name, reset_mac_addresses) if proj: From 7ab04a0f9ab8f507b8d22b586e488b74db8e65f2 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 12:20:26 +0800 Subject: [PATCH 4/8] Add running project check for fast duplication --- gns3server/controller/project.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index aa3b361e1..0526df838 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -1334,9 +1334,6 @@ class Project: self.dump() assert self._status != "closed" - if self.is_running(): - raise ControllerError("Project must be stopped in order to duplicate it") - try: proj = await self._fast_duplication(name, reset_mac_addresses) if proj: From 2adc09604c46ba784801ec0bb89584329f9311e5 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 22:17:43 +0800 Subject: [PATCH 5/8] Fix Docker VM tests for container status detection on node creation The create() method now calls _get_container_state() after creating the container to detect if the container is already running. Mock this method in all test_create_* tests so the new code path doesn't fail with KeyError: 'State' when the Docker.query mock response lacks a State field. --- tests/compute/docker/test_docker_vm.py | 1434 ++++++++++++------------ 1 file changed, 727 insertions(+), 707 deletions(-) diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 33eea1cbb..1d5f07b70 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -95,48 +95,49 @@ async def test_create(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -146,48 +147,49 @@ async def test_create_with_tag(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:16.04") - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:16.04") + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:16.04", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:16.04", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -198,60 +200,61 @@ async def test_create_vnc(compute_project, manager): "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", console_type="vnc", console=5900) - vm._start_vnc = MagicMock() - vm._display = 42 - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": f"/tmp/.X11-unix/X{vm._display}", - "Target": f"/tmp/.X11-unix/X{vm._display}", - "ReadOnly": True - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", console_type="vnc", console=5900) + vm._start_vnc = MagicMock() + vm._display = 42 + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": f"/tmp/.X11-unix/X{vm._display}", + "Target": f"/tmp/.X11-unix/X{vm._display}", + "ReadOnly": True + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "QT_GRAPHICSSYSTEM=native", + "DISPLAY=:42" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "QT_GRAPHICSSYSTEM=native", - "DISPLAY=:42" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._start_vnc.called - assert vm._cid == "e90e34656806" - assert vm._console_type == "vnc" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._start_vnc.called + assert vm._cid == "e90e34656806" + assert vm._console_type == "vnc" @pytest.mark.asyncio @@ -263,13 +266,14 @@ async def test_create_with_extra_hosts(compute_project, manager): "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) - await vm.create() - called_kwargs = mock.call_args[1] - assert "GNS3_EXTRA_HOSTS=199.199.199.1\ttest\n199.199.199.1\ttest2" in called_kwargs["data"]["Env"] - assert vm._extra_hosts == extra_hosts + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) + await vm.create() + called_kwargs = mock.call_args[1] + assert "GNS3_EXTRA_HOSTS=199.199.199.1\ttest\n199.199.199.1\ttest2" in called_kwargs["data"]["Env"] + assert vm._extra_hosts == extra_hosts @pytest.mark.asyncio async def test_create_with_colon_in_project_name(compute_project, manager): @@ -312,12 +316,13 @@ async def test_create_with_empty_extra_hosts(compute_project, manager): "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) - await vm.create() - called_kwargs = mock.call_args[1] - assert len([ e for e in called_kwargs["data"]["Env"] if "GNS3_EXTRA_HOSTS" in e]) == 0 + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) + await vm.create() + called_kwargs = mock.call_args[1] + assert len([ e for e in called_kwargs["data"]["Env"] if "GNS3_EXTRA_HOSTS" in e]) == 0 @pytest.mark.asyncio @@ -333,15 +338,16 @@ async def test_create_with_project_variables(compute_project, manager): {"name": "VAR3", "value": "2x${VAR2}"} ] - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") - await vm.create() - called_kwargs = mock.call_args[1] - assert "VAR1=" in called_kwargs["data"]["Env"] - assert "VAR2=VAL1" in called_kwargs["data"]["Env"] - assert "VAR3=2xVAL1" in called_kwargs["data"]["Env"] - compute_project.variables = None + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") + await vm.create() + called_kwargs = mock.call_args[1] + assert "VAR1=" in called_kwargs["data"]["Env"] + assert "VAR2=VAL1" in called_kwargs["data"]["Env"] + assert "VAR3=2xVAL1" in called_kwargs["data"]["Env"] + compute_project.variables = None @pytest.mark.asyncio @@ -351,49 +357,50 @@ async def test_create_start_cmd(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") - vm._start_command = "/bin/ls" - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/ls"], - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" - ] - }) - assert vm._cid == "e90e34656806" + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") + vm._start_command = "/bin/ls" + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/ls"], + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" + ] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -407,18 +414,19 @@ async def test_create_environment(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") - vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2" - await vm.create() - assert mock.call_args[1]['data']['Env'] == [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "YES=1", - "NO=0" - ] + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") + vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2" + await vm.create() + assert mock.call_args[1]['data']['Env'] == [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "YES=1", + "NO=0" + ] @pytest.mark.asyncio @@ -432,18 +440,19 @@ async def test_create_environment_with_last_new_line_character(compute_project, "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") - vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2\n" - await vm.create() - assert mock.call_args[1]['data']['Env'] == [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "YES=1", - "NO=0" - ] + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") + vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2\n" + await vm.create() + assert mock.call_args[1]['data']['Env'] == [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "YES=1", + "NO=0" + ] @pytest.mark.asyncio @@ -466,48 +475,49 @@ async def test_create_image_not_available(compute_project, manager): vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") vm._get_image_information = MagicMock() vm._get_image_information.side_effect = information - with asyncio_patch("gns3server.compute.docker.DockerVM.pull_image", return_value=True) as mock_pull: - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.DockerVM.pull_image", return_value=True) as mock_pull: + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" - mock_pull.assert_called_with("ubuntu:latest") + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" + mock_pull.assert_called_with("ubuntu:latest") @pytest.mark.asyncio @@ -520,50 +530,51 @@ async def test_create_with_user(compute_project, manager): "User" : "test", }, } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "User": "root", - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "User": "root", + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host", + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "GNS3_USER=test" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host", - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "GNS3_USER=test" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -620,53 +631,54 @@ async def test_create_with_extra_volumes_duplicate_1_image(compute_project, mana }, }, } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "1"), - "Target": "/gns3volumes/vol/1" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "1"), + "Target": "/gns3volumes/vol/1" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol/1" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol/1" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -676,53 +688,54 @@ async def test_create_with_extra_volumes_duplicate_2_user(compute_project, manag "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1", "/vol/1"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "1"), - "Target": "/gns3volumes/vol/1" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1", "/vol/1"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "1"), + "Target": "/gns3volumes/vol/1" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol/1" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol/1" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -732,53 +745,54 @@ async def test_create_with_extra_volumes_duplicate_3_subdir(compute_project, man "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1/", "/vol"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol"), - "Target": "/gns3volumes/vol" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1/", "/vol"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol"), + "Target": "/gns3volumes/vol" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -788,53 +802,54 @@ async def test_create_with_extra_volumes_duplicate_4_backslash(compute_project, "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol//", "/vol"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol"), - "Target": "/gns3volumes/vol" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol//", "/vol"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol"), + "Target": "/gns3volumes/vol" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -844,48 +859,49 @@ async def test_create_with_extra_volumes_duplicate_5_subdir_issue_1595(compute_p "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc"), - "Target": "/gns3volumes/etc" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc"), + "Target": "/gns3volumes/etc" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -895,48 +911,49 @@ async def test_create_with_extra_volumes_duplicate_6_subdir_issue_1595(compute_p "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc/test", "/etc"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc"), - "Target": "/gns3volumes/etc" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc/test", "/etc"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc"), + "Target": "/gns3volumes/etc" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -952,58 +969,59 @@ async def test_create_with_extra_volumes(compute_project, manager): }, } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/2"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "1"), - "Target": "/gns3volumes/vol/1" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "2"), - "Target": "/gns3volumes/vol/2" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/2"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "1"), + "Target": "/gns3volumes/vol/1" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "2"), + "Target": "/gns3volumes/vol/2" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol/1:/vol/2" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol/1:/vol/2" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -1787,48 +1805,49 @@ async def test_cpus(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", cpus=0.5) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", cpus=0.5) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 500000000, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 500000000, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -1838,45 +1857,46 @@ async def test_memory(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", memory=32) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", memory=32) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 33554432, # 32MB in bytes + "NanoCpus": 0, + "UsernsMode": "host", + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" ], - "Privileged": True, - "Memory": 33554432, # 32MB in bytes - "NanoCpus": 0, - "UsernsMode": "host", - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" - ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" From 4adaba8e8c5c6d739577ef51e1bb254c10410fd1 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 22:20:34 +0800 Subject: [PATCH 6/8] Revert container state detection in create() The _get_container_state() call in create() has no practical effect: Docker's POST /containers/create only creates the container without starting it, so a newly created container can never be in 'running' or 'paused' state. --- gns3server/compute/docker/docker_vm.py | 13 - tests/compute/docker/test_docker_vm.py | 1434 ++++++++++++------------ 2 files changed, 707 insertions(+), 740 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 393b21581..7e8550f09 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -588,19 +588,6 @@ class DockerVM(BaseNode): if self._memory > 0: log.info(f"Memory limit set to {self._memory} MB") - # Check if the container is already running and update the node status accordingly - # This can happen when the server restarts and the container continues running - try: - state = await self._get_container_state() - if state == "running": - self.status = "started" - log.info(f"Docker container '{self._name}' is already running") - elif state == "paused": - self.status = "suspended" - log.info(f"Docker container '{self._name}' is paused") - except DockerError as e: - log.warning(f"Could not check container state for '{self._name}': {e}") - return True def _format_env(self, variables, env): diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 1d5f07b70..33eea1cbb 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -95,49 +95,48 @@ async def test_create(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -147,49 +146,48 @@ async def test_create_with_tag(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:16.04") - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:16.04", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:16.04") + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:16.04", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -200,61 +198,60 @@ async def test_create_vnc(compute_project, manager): "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", console_type="vnc", console=5900) - vm._start_vnc = MagicMock() - vm._display = 42 - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": f"/tmp/.X11-unix/X{vm._display}", - "Target": f"/tmp/.X11-unix/X{vm._display}", - "ReadOnly": True - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "QT_GRAPHICSSYSTEM=native", - "DISPLAY=:42" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", console_type="vnc", console=5900) + vm._start_vnc = MagicMock() + vm._display = 42 + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": f"/tmp/.X11-unix/X{vm._display}", + "Target": f"/tmp/.X11-unix/X{vm._display}", + "ReadOnly": True + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._start_vnc.called - assert vm._cid == "e90e34656806" - assert vm._console_type == "vnc" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "QT_GRAPHICSSYSTEM=native", + "DISPLAY=:42" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._start_vnc.called + assert vm._cid == "e90e34656806" + assert vm._console_type == "vnc" @pytest.mark.asyncio @@ -266,14 +263,13 @@ async def test_create_with_extra_hosts(compute_project, manager): "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) - await vm.create() - called_kwargs = mock.call_args[1] - assert "GNS3_EXTRA_HOSTS=199.199.199.1\ttest\n199.199.199.1\ttest2" in called_kwargs["data"]["Env"] - assert vm._extra_hosts == extra_hosts + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) + await vm.create() + called_kwargs = mock.call_args[1] + assert "GNS3_EXTRA_HOSTS=199.199.199.1\ttest\n199.199.199.1\ttest2" in called_kwargs["data"]["Env"] + assert vm._extra_hosts == extra_hosts @pytest.mark.asyncio async def test_create_with_colon_in_project_name(compute_project, manager): @@ -316,13 +312,12 @@ async def test_create_with_empty_extra_hosts(compute_project, manager): "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) - await vm.create() - called_kwargs = mock.call_args[1] - assert len([ e for e in called_kwargs["data"]["Env"] if "GNS3_EXTRA_HOSTS" in e]) == 0 + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu", extra_hosts=extra_hosts) + await vm.create() + called_kwargs = mock.call_args[1] + assert len([ e for e in called_kwargs["data"]["Env"] if "GNS3_EXTRA_HOSTS" in e]) == 0 @pytest.mark.asyncio @@ -338,16 +333,15 @@ async def test_create_with_project_variables(compute_project, manager): {"name": "VAR3", "value": "2x${VAR2}"} ] - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") - await vm.create() - called_kwargs = mock.call_args[1] - assert "VAR1=" in called_kwargs["data"]["Env"] - assert "VAR2=VAL1" in called_kwargs["data"]["Env"] - assert "VAR3=2xVAL1" in called_kwargs["data"]["Env"] - compute_project.variables = None + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") + await vm.create() + called_kwargs = mock.call_args[1] + assert "VAR1=" in called_kwargs["data"]["Env"] + assert "VAR2=VAL1" in called_kwargs["data"]["Env"] + assert "VAR3=2xVAL1" in called_kwargs["data"]["Env"] + compute_project.variables = None @pytest.mark.asyncio @@ -357,50 +351,49 @@ async def test_create_start_cmd(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") - vm._start_command = "/bin/ls" - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/ls"], - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" - ] - }) - assert vm._cid == "e90e34656806" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") + vm._start_command = "/bin/ls" + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } + ], + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/ls"], + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" + ] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -414,19 +407,18 @@ async def test_create_environment(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") - vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2" - await vm.create() - assert mock.call_args[1]['data']['Env'] == [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "YES=1", - "NO=0" - ] + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") + vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2" + await vm.create() + assert mock.call_args[1]['data']['Env'] == [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "YES=1", + "NO=0" + ] @pytest.mark.asyncio @@ -440,19 +432,18 @@ async def test_create_environment_with_last_new_line_character(compute_project, "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") - vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2\n" - await vm.create() - assert mock.call_args[1]['data']['Env'] == [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "YES=1", - "NO=0" - ] + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") + vm.environment = "YES=1\nNO=0\nGNS3_MAX_ETHERNET=eth2\n" + await vm.create() + assert mock.call_args[1]['data']['Env'] == [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "YES=1", + "NO=0" + ] @pytest.mark.asyncio @@ -475,49 +466,48 @@ async def test_create_image_not_available(compute_project, manager): vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu") vm._get_image_information = MagicMock() vm._get_image_information.side_effect = information - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.DockerVM.pull_image", return_value=True) as mock_pull: - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" + with asyncio_patch("gns3server.compute.docker.DockerVM.pull_image", return_value=True) as mock_pull: + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" - mock_pull.assert_called_with("ubuntu:latest") + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" + mock_pull.assert_called_with("ubuntu:latest") @pytest.mark.asyncio @@ -530,51 +520,50 @@ async def test_create_with_user(compute_project, manager): "User" : "test", }, } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "User": "root", - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host", - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network", - "GNS3_USER=test" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "User": "root", + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host", + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network", + "GNS3_USER=test" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -631,54 +620,53 @@ async def test_create_with_extra_volumes_duplicate_1_image(compute_project, mana }, }, } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "1"), - "Target": "/gns3volumes/vol/1" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol/1" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "1"), + "Target": "/gns3volumes/vol/1" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol/1" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -688,54 +676,53 @@ async def test_create_with_extra_volumes_duplicate_2_user(compute_project, manag "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1", "/vol/1"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "1"), - "Target": "/gns3volumes/vol/1" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol/1" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1", "/vol/1"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "1"), + "Target": "/gns3volumes/vol/1" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol/1" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -745,54 +732,53 @@ async def test_create_with_extra_volumes_duplicate_3_subdir(compute_project, man "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1/", "/vol"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol"), - "Target": "/gns3volumes/vol" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1/", "/vol"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol"), + "Target": "/gns3volumes/vol" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -802,54 +788,53 @@ async def test_create_with_extra_volumes_duplicate_4_backslash(compute_project, "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol//", "/vol"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol"), - "Target": "/gns3volumes/vol" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol//", "/vol"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol"), + "Target": "/gns3volumes/vol" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -859,49 +844,48 @@ async def test_create_with_extra_volumes_duplicate_5_subdir_issue_1595(compute_p "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc"), - "Target": "/gns3volumes/etc" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc"), + "Target": "/gns3volumes/etc" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -911,49 +895,48 @@ async def test_create_with_extra_volumes_duplicate_6_subdir_issue_1595(compute_p "Id": "e90e34656806", "Warnings": [], } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc/test", "/etc"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc"), - "Target": "/gns3volumes/etc" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc/test", "/etc"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc"), + "Target": "/gns3volumes/etc" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -969,59 +952,58 @@ async def test_create_with_extra_volumes(compute_project, manager): }, } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/2"]) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "1"), - "Target": "/gns3volumes/vol/1" - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "vol", "2"), - "Target": "/gns3volumes/vol/2" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 0, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network:/vol/1:/vol/2" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/2"]) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "1"), + "Target": "/gns3volumes/vol/1" + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "vol", "2"), + "Target": "/gns3volumes/vol/2" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 0, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network:/vol/1:/vol/2" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -1805,49 +1787,48 @@ async def test_cpus(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", cpus=0.5) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 0, - "NanoCpus": 500000000, - "UsernsMode": "host" - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", cpus=0.5) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 0, + "NanoCpus": 500000000, + "UsernsMode": "host" + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" @pytest.mark.asyncio @@ -1857,46 +1838,45 @@ async def test_memory(compute_project, manager): "Id": "e90e34656806", "Warnings": [] } - with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): - with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): - with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", memory=32) - await vm.create() - mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "HostConfig": - { - "CapAdd": ["ALL"], - "Mounts": [ - { - "Type": "bind", - "Source": Docker.resources_path(), - "Target": "/gns3", - "ReadOnly": True - }, - { - "Type": "bind", - "Source": os.path.join(vm.working_dir, "etc", "network"), - "Target": "/gns3volumes/etc/network" - } - ], - "Privileged": True, - "Memory": 33554432, # 32MB in bytes - "NanoCpus": 0, - "UsernsMode": "host", - }, - "Volumes": {}, - "NetworkDisabled": True, - "Hostname": "test", - "Image": "ubuntu:latest", - "Env": [ - "container=docker", - "GNS3_MAX_ETHERNET=eth0", - "GNS3_VOLUMES=/etc/network" + with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]): + with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock: + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", memory=32) + await vm.create() + mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={ + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "HostConfig": + { + "CapAdd": ["ALL"], + "Mounts": [ + { + "Type": "bind", + "Source": Docker.resources_path(), + "Target": "/gns3", + "ReadOnly": True + }, + { + "Type": "bind", + "Source": os.path.join(vm.working_dir, "etc", "network"), + "Target": "/gns3volumes/etc/network" + } ], - "Entrypoint": ["/gns3/init.sh"], - "Cmd": ["/bin/sh"] - }) - assert vm._cid == "e90e34656806" + "Privileged": True, + "Memory": 33554432, # 32MB in bytes + "NanoCpus": 0, + "UsernsMode": "host", + }, + "Volumes": {}, + "NetworkDisabled": True, + "Hostname": "test", + "Image": "ubuntu:latest", + "Env": [ + "container=docker", + "GNS3_MAX_ETHERNET=eth0", + "GNS3_VOLUMES=/etc/network" + ], + "Entrypoint": ["/gns3/init.sh"], + "Cmd": ["/bin/sh"] + }) + assert vm._cid == "e90e34656806" From e8e1930e0b3db4a411e6891f6325910fe55f53be Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 22:22:03 +0800 Subject: [PATCH 7/8] Remove extra blank line from merge --- gns3server/compute/docker/docker_vm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 7e8550f09..988d61cf3 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -587,7 +587,6 @@ class DockerVM(BaseNode): log.info(f"CPU limit set to {self._cpus} CPUs") if self._memory > 0: log.info(f"Memory limit set to {self._memory} MB") - return True def _format_env(self, variables, env): From fb1a5a3385a65a9adff230bf0c866bb1a5fb78ad Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 3 Jun 2026 22:47:48 +0800 Subject: [PATCH 8/8] Add project memory: Docker container stop delay analysis --- .claude/memory/MEMORY.md | 3 ++ .claude/memory/docker-container-stop-delay.md | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 .claude/memory/docker-container-stop-delay.md diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 62343521b..9050f7d54 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -24,3 +24,6 @@ ### uBridge Permission - **[uBridge Permission Issue](./gns3-ubridge-permission.md)** - Docker containers fail to start due to missing CAP_NET_ADMIN/CAP_NET_RAW capabilities on uBridge + +### Docker Container Stop Delay +- **[Docker Container Stop Delay](./docker-container-stop-delay.md)** - Some containers take ~5s to stop because they don't handle SIGTERM (AlpiNet, OstinatoWireshark) diff --git a/.claude/memory/docker-container-stop-delay.md b/.claude/memory/docker-container-stop-delay.md new file mode 100644 index 000000000..ce38b8d20 --- /dev/null +++ b/.claude/memory/docker-container-stop-delay.md @@ -0,0 +1,41 @@ +--- +name: docker-container-stop-delay +description: Docker containers not responding to SIGTERM cause ~5s stop delays when closing a project +metadata: + type: reference +--- + +# Docker Container Stop Delay Analysis + +## Background +When stopping a GNS3 project, some Docker containers take ~5s to exit while others stop instantly. + +## Root Cause +Docker's `stop` command sends SIGTERM and waits `t` seconds (GNS3 sets `t=5`) before sending SIGKILL. Containers that don't handle SIGTERM are stuck waiting for the full timeout. + +## Affected Containers + +| Container | PID 1 | Why it's slow | +|-----------|-------|---------------| +| **AlpiNet** (alpine) | `dumb-init` → `bash -i` | Interactive bash ignores SIGTERM by design | +| **OstinatoWireshark** | `bash` (PID 1) | Linux kernel won't apply default signal actions to PID 1 without an explicit handler; interactive bash doesn't install one | + +## Normal Containers (for comparison) + +| Container | PID 1 | Why fast | +|-----------|-------|----------| +| Chromium | `/usr/bin/chromium` | Chromium handles SIGTERM natively | +| webterm | `dumb-init` → firefox | Firefox responds to SIGTERM immediately | + +## Related Files +- `gns3-registry/docker/alpinet/Dockerfile` +- `gns3-registry/docker/ostinato-wireshark/Dockerfile` +- `gns3-registry/docker/ostinato-wireshark/entry.sh` +- `gns3-registry/docker/chromium/Dockerfile` +- `gns3-registry/docker/ipterm/web/Dockerfile` +- `gns3-server/gns3server/compute/docker/docker_vm.py:1040` — stop timeout parameter `t=5` + +## Note +This is not a GNS3 server bug (except a minor `or` vs `and` logic issue at `docker_vm.py:1037` which doesn't affect behavior). The root cause is in the Docker images themselves. + +See also: [[docker-container-stop-delay]]