Skip to content

Commit 49d584e

Browse files
committed
fix: reprise automatique sur cle hote SSH changee (VM reconstruite) dans --remote-cli/--remote-gui ; corrige aussi un trou du harnais de test (tail du log en direct mal classe en launch)
1 parent 0d5263f commit 49d584e

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

tests/_fake_rlidar2map_CLI_transport.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ def fake_ssh():
154154
)
155155
if helper_action in ("inventory", "copy"):
156156
kind = helper_action
157+
elif remote_tokens[:1] == ["tail"]:
158+
# print_remote_log_tail : pas de payload (aucun input piped), donc
159+
# sans ce cas explicite elle tombait dans le repli "launch" ci-dessous
160+
# (n'importe quel payload sans STATUS=) à chaque cycle de sondage.
161+
kind = "log_tail"
157162
else:
158163
kind = (
159164
"query"
@@ -178,6 +183,8 @@ def fake_ssh():
178183
if kind == "copy":
179184
remote_copy(payload)
180185
return 0
186+
if kind == "log_tail":
187+
return 0 # pas de nouvelles données -> no-op, cf. print_remote_log_tail
181188
if kind == "launch":
182189
return 0
183190
data = load_state()

tests/_test_rlidar2map_CLI.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,11 +1087,14 @@ def test_start_follow_and_final_scp_use_real_subprocess_boundaries(self):
10871087
"ssh:query",
10881088
"ssh:launch",
10891089
"ssh:query",
1090+
"ssh:log_tail",
10901091
"ssh:inventory",
10911092
"ssh:query",
1093+
"ssh:log_tail",
10921094
"ssh:inventory",
10931095
"ssh:copy",
10941096
"ssh:query",
1097+
"ssh:log_tail",
10951098
"ssh:inventory",
10961099
"scp:copy",
10971100
],

tools/rlidar2map_CLI.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,22 @@ def _safe_component(value: str) -> str:
14521452
return cleaned or "vm"
14531453

14541454

1455+
_HOST_KEY_CHANGED_MARKERS = (
1456+
b"Host key verification failed",
1457+
b"REMOTE HOST IDENTIFICATION HAS CHANGED",
1458+
)
1459+
1460+
1461+
def _is_host_key_changed(stderr_bytes: Optional[bytes]) -> bool:
1462+
"""True si un stderr SSH signale une clé hote qui a CHANGE (pas juste
1463+
une machine jamais vue : StrictHostKeyChecking=accept-new, deja pose
1464+
dans _connection_options, accepte deja celle-la sans broncher). Le cas
1465+
courant avec une VM ephemere (Hetzner...) reconstruite/reinstallee
1466+
depuis la derniere connexion, pas une anomalie."""
1467+
data = stderr_bytes or b""
1468+
return any(marker in data for marker in _HOST_KEY_CHANGED_MARKERS)
1469+
1470+
14551471
class VmController:
14561472
def __init__(self, options: Options, deps: Optional[RuntimeDeps] = None):
14571473
self.options = options
@@ -1528,6 +1544,16 @@ def reset_host_key(self) -> None:
15281544
if completed.returncode not in (0, 1):
15291545
raise RunOnVmError("impossible de supprimer l'ancienne clé SSH")
15301546

1547+
def _auto_reset_host_key(self) -> None:
1548+
"""Appelé UNIQUEMENT après un échec SSH confirmé (_is_host_key_changed)
1549+
avant de retenter une fois : annonce toujours l'action (rien de
1550+
silencieux), même si elle est automatique."""
1551+
host = self.options.vm.rsplit("@", 1)[-1]
1552+
print(f" SSH: host key for {host} has changed (VM rebuilt/reinstalled?) "
1553+
f"- clearing the stale known_hosts entry and retrying once...",
1554+
flush=True)
1555+
self.reset_host_key()
1556+
15311557
def query_state(self) -> RemoteState:
15321558
completed = subprocess.run(
15331559
self._ssh_command([self.options.session]),
@@ -1536,6 +1562,15 @@ def query_state(self) -> RemoteState:
15361562
stderr=subprocess.PIPE,
15371563
check=False,
15381564
)
1565+
if completed.returncode != 0 and _is_host_key_changed(completed.stderr):
1566+
self._auto_reset_host_key()
1567+
completed = subprocess.run(
1568+
self._ssh_command([self.options.session]),
1569+
input=REMOTE_QUERY_SCRIPT.encode("utf-8"),
1570+
stdout=subprocess.PIPE,
1571+
stderr=subprocess.PIPE,
1572+
check=False,
1573+
)
15391574
if completed.returncode != 0:
15401575
stderr = completed.stderr.decode("utf-8", errors="replace").strip()
15411576
raise SshError(
@@ -1566,9 +1601,19 @@ def launch(self) -> None:
15661601
completed = subprocess.run(
15671602
self._ssh_command(remote_args),
15681603
input=REMOTE_LAUNCH_SCRIPT.encode("utf-8"),
1604+
stderr=subprocess.PIPE,
15691605
check=False,
15701606
)
1607+
if completed.returncode != 0 and _is_host_key_changed(completed.stderr):
1608+
self._auto_reset_host_key()
1609+
completed = subprocess.run(
1610+
self._ssh_command(remote_args),
1611+
input=REMOTE_LAUNCH_SCRIPT.encode("utf-8"),
1612+
stderr=subprocess.PIPE,
1613+
check=False,
1614+
)
15711615
if completed.returncode != 0:
1616+
sys.stderr.buffer.write(completed.stderr or b"")
15721617
raise RunOnVmError(
15731618
"le lancement distant a échoué (code {})".format(
15741619
completed.returncode

0 commit comments

Comments
 (0)