Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3632b21135 |
No files matched your search
@@ -18,6 +18,21 @@ from nisse_core.execution.ssh import SSHHost
|
|||||||
|
|
||||||
SSHD = "/usr/sbin/sshd"
|
SSHD = "/usr/sbin/sshd"
|
||||||
|
|
||||||
|
# Set in every session of a local sshd, to its root directory.
|
||||||
|
SESSION_MARK = "NISSE_TEST_SSHD"
|
||||||
|
|
||||||
|
|
||||||
|
def started_by(host: SSHHost, pid: str) -> bool:
|
||||||
|
"""Whether process ``pid`` was started in a session of ``host``'s local
|
||||||
|
sshd: it carries that sshd's SESSION_MARK. A process that's gone, or
|
||||||
|
another user's, wasn't."""
|
||||||
|
mark = f"{SESSION_MARK}={host._key.parent}".encode() # the sshd root
|
||||||
|
try:
|
||||||
|
environ = Path(f"/proc/{pid}/environ").read_bytes()
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return mark in environ.split(b"\0")
|
||||||
|
|
||||||
|
|
||||||
def _free_port() -> int:
|
def _free_port() -> int:
|
||||||
with socket.socket() as sock:
|
with socket.socket() as sock:
|
||||||
@@ -61,7 +76,12 @@ def local_sshd(root: Path) -> Iterator[SSHHost]:
|
|||||||
"MaxStartups 50",
|
"MaxStartups 50",
|
||||||
# Stands in for a provisioned build VM: sessions see the test
|
# Stands in for a provisioned build VM: sessions see the test
|
||||||
# runner's PATH, so its Python and tools are "installed".
|
# runner's PATH, so its Python and tools are "installed".
|
||||||
f"SetEnv PATH={os.environ.get('PATH', '/usr/bin:/bin')}",
|
# SESSION_MARK tags every process this sshd starts, so a test
|
||||||
|
# can tell its own from another worker's (#172).
|
||||||
|
(
|
||||||
|
f"SetEnv PATH={os.environ.get('PATH', '/usr/bin:/bin')} "
|
||||||
|
f"{SESSION_MARK}={root}"
|
||||||
|
),
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from pathlib import Path
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from nisse_core.execution.ssh import SSHHost
|
from nisse_core.execution.ssh import SSHHost
|
||||||
|
from nisse_core.execution.testing import started_by
|
||||||
from nisse_dev.git import GitError, GitRepo, HTTPSToken, SSHKey
|
from nisse_dev.git import GitError, GitRepo, HTTPSToken, SSHKey
|
||||||
|
|
||||||
|
|
||||||
@@ -113,9 +114,9 @@ def test_the_key_is_gone_when_the_command_ends(
|
|||||||
repo.configure_identity("t", "t@example.test")
|
repo.configure_identity("t", "t@example.test")
|
||||||
(repo.root / "a.txt").write_text("a")
|
(repo.root / "a.txt").write_text("a")
|
||||||
repo.commit_all("first")
|
repo.commit_all("first")
|
||||||
before = _agents()
|
before = _agents(ssh_host)
|
||||||
repo.push(url, "main", credential=key)
|
repo.push(url, "main", credential=key)
|
||||||
assert _agents() == before # the throwaway ssh-agent was killed
|
assert _agents(ssh_host) == before # the throwaway ssh-agent was killed
|
||||||
|
|
||||||
|
|
||||||
def test_a_failed_push_still_kills_its_agent(
|
def test_a_failed_push_still_kills_its_agent(
|
||||||
@@ -124,19 +125,35 @@ def test_a_failed_push_still_kills_its_agent(
|
|||||||
_, key = ssh_remote
|
_, key = ssh_remote
|
||||||
repo = GitRepo(ssh_host, tmp_path / "w", known_hosts=str(tmp_path / "kh"))
|
repo = GitRepo(ssh_host, tmp_path / "w", known_hosts=str(tmp_path / "kh"))
|
||||||
repo.init()
|
repo.init()
|
||||||
before = _agents()
|
before = _agents(ssh_host)
|
||||||
with pytest.raises(GitError):
|
with pytest.raises(GitError):
|
||||||
repo.push(
|
repo.push(
|
||||||
f"ssh://nobody@127.0.0.1:{ssh_host.port}/nowhere.git",
|
f"ssh://nobody@127.0.0.1:{ssh_host.port}/nowhere.git",
|
||||||
"main",
|
"main",
|
||||||
credential=key,
|
credential=key,
|
||||||
)
|
)
|
||||||
assert _agents() == before
|
assert _agents(ssh_host) == before
|
||||||
|
|
||||||
|
|
||||||
def _agents() -> set[str]:
|
def test_an_agent_started_elsewhere_is_not_counted(ssh_host: SSHHost) -> None:
|
||||||
"""Live ssh-agents. A killed one lingers as a zombie in a CI container,
|
# The unit tier runs in parallel: another worker's push starts its own
|
||||||
where nothing reaps orphans, so zombies don't count."""
|
# ssh-agent at any moment (#172). Only this sshd's count.
|
||||||
|
before = _agents(ssh_host)
|
||||||
|
out = subprocess.run(
|
||||||
|
["ssh-agent", "-s"], capture_output=True, text=True, check=True
|
||||||
|
).stdout
|
||||||
|
pid = out.split("SSH_AGENT_PID=")[1].split(";")[0]
|
||||||
|
try:
|
||||||
|
assert _agents(ssh_host) == before
|
||||||
|
assert pid in _agents(None)
|
||||||
|
finally:
|
||||||
|
subprocess.run(["kill", pid], check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _agents(host: SSHHost | None) -> set[str]:
|
||||||
|
"""Live ssh-agents started through ``host``'s sshd, or any when None. A
|
||||||
|
killed one lingers as a zombie in a CI container, where nothing reaps
|
||||||
|
orphans, so zombies don't count."""
|
||||||
out = subprocess.run(
|
out = subprocess.run(
|
||||||
["pgrep", "-x", "ssh-agent"], capture_output=True, text=True, check=False
|
["pgrep", "-x", "ssh-agent"], capture_output=True, text=True, check=False
|
||||||
).stdout
|
).stdout
|
||||||
@@ -146,7 +163,9 @@ def _agents() -> set[str]:
|
|||||||
stat = Path(f"/proc/{pid}/stat").read_text()
|
stat = Path(f"/proc/{pid}/stat").read_text()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
continue
|
continue
|
||||||
if stat.rsplit(")", 1)[1].split()[0] != "Z":
|
if stat.rsplit(")", 1)[1].split()[0] == "Z":
|
||||||
|
continue
|
||||||
|
if host is None or started_by(host, pid):
|
||||||
live.add(pid)
|
live.add(pid)
|
||||||
return live
|
return live
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user