diff --git a/src/devx/molecule/start_docker.py b/src/devx/molecule/start_docker.py index 4d71ef2..16c0665 100644 --- a/src/devx/molecule/start_docker.py +++ b/src/devx/molecule/start_docker.py @@ -223,13 +223,35 @@ def start_docker_daemon(timeout: int = DEFAULT_TIMEOUT) -> bool: # data root and socket. The inner dockerd uses the container's # overlay (38G, often 100% full). Killing it frees up the # socket and any space used by its containers/volumes. + # Use SIGKILL (-9) since the inner dockerd may not respond to SIGTERM. + # Try multiple patterns to match different dockerd invocations. + for pattern in ["dockerd", "dockerd-entrypoint.sh"]: + with contextlib.suppress(Exception): + subprocess.run( # nosec B603 B607 + ["pkill", "-9", "-f", pattern], + check=False, + timeout=5, + ) + time.sleep(3) + + # Verify the inner dockerd is actually dead. If we can still + # connect to /var/run/docker.sock, the old daemon is still running + # and we need to use a different socket path. + old_daemon_alive = False with contextlib.suppress(Exception): - subprocess.run( # nosec B603 B607 - ["pkill", "-f", "dockerd.*--host fd://"], - check=False, + result = subprocess.run( # nosec B603 B607 + ["docker", "info"], + env={**os.environ, "DOCKER_HOST": f"unix://{DOCKER_SOCK}"}, + capture_output=True, timeout=5, ) - time.sleep(2) + old_daemon_alive = result.returncode == 0 + + if old_daemon_alive: + click.echo(" Inner dockerd still alive, using alternate socket") + local_sock = "/dev/shm/docker.sock" # nosec B108 + else: + local_sock = DOCKER_SOCK # Clean up the inner dockerd's data root to free space. # The inner dockerd stores images, containers, and volumes here. @@ -243,11 +265,9 @@ def start_docker_daemon(timeout: int = DEFAULT_TIMEOUT) -> bool: timeout=30, ) - # Use the inner dockerd's socket path (now free after pkill) - # and a fresh data root on the container's overlay. + # Use a fresh data root on the container's overlay. # /dev/shm is a small tmpfs (16G) — too small for images. # The container's overlay (38G) has more space after cleanup. - local_sock = DOCKER_SOCK docker_data_root = "/tmp/docker-data" # nosec B108 # Remove stale socket if present diff --git a/tests/unit/test_start_docker.py b/tests/unit/test_start_docker.py index 33ee0c9..695fe12 100644 --- a/tests/unit/test_start_docker.py +++ b/tests/unit/test_start_docker.py @@ -218,9 +218,52 @@ class TestStartDockerDaemon: mock_ntf.return_value = MagicMock(name="/tmp/dockerd.log") mock_ready.side_effect = [True, False, False, False, False, True] mock_free.return_value = 5 * 1024**3 + # mock_run: pkill calls (x2), docker info check (returncode=1 = dead), rm call + mock_run.side_effect = [ + MagicMock(), + MagicMock(), + MagicMock(returncode=1), + MagicMock(), + ] assert start_docker_daemon(timeout=5) is True mock_popen.assert_called_once() + @patch("devx.molecule.start_docker._diagnose_socket") + @patch("devx.molecule.start_docker._get_docker_free_bytes") + @patch("devx.molecule.start_docker.is_docker_ready") + @patch("devx.molecule.start_docker.os.path.exists", side_effect=_exists_map({DOCKER_SOCK})) + @patch("devx.molecule.start_docker.glob.glob", return_value=[]) + @patch("devx.molecule.start_docker.subprocess.run") + @patch("devx.molecule.start_docker.subprocess.Popen") + @patch("devx.molecule.start_docker.tempfile.NamedTemporaryFile") + @patch("devx.molecule.start_docker.time.sleep") + def test_inner_dockerd_alive_uses_alt_sock( + self, + mock_sleep: MagicMock, + mock_ntf: MagicMock, + mock_popen: MagicMock, + mock_run: MagicMock, + mock_glob: MagicMock, + mock_exists: MagicMock, + mock_ready: MagicMock, + mock_free: MagicMock, + mock_diag: MagicMock, + ) -> None: + """Should use /dev/shm/docker.sock if inner dockerd can't be killed.""" + mock_ntf.return_value = MagicMock(name="/tmp/dockerd.log") + mock_ready.side_effect = [True, False, False, False, False, True] + mock_free.return_value = 5 * 1024**3 + # mock_run: pkill calls (x2), docker info check (returncode=0 = alive), rm call + mock_run.side_effect = [ + MagicMock(), + MagicMock(), + MagicMock(returncode=0), + MagicMock(), + ] + assert start_docker_daemon(timeout=5) is True + mock_popen.assert_called_once() + assert os.environ.get("DOCKER_HOST") == "unix:///dev/shm/docker.sock" + @patch("devx.molecule.start_docker._diagnose_socket") @patch("devx.molecule.start_docker._get_docker_free_bytes") @patch("devx.molecule.start_docker.is_docker_ready")