From 2a90c9b8dd765da08eff9bba910bb87b1c224247 Mon Sep 17 00:00:00 2001 From: dv Date: Fri, 10 Apr 2026 15:10:42 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20ssh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/device_discovery.py | 70 ++++++++++++------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/backend/app/services/device_discovery.py b/backend/app/services/device_discovery.py index b33db66..f4de001 100644 --- a/backend/app/services/device_discovery.py +++ b/backend/app/services/device_discovery.py @@ -527,24 +527,29 @@ async def _discover_mikrotik_multi(host: str, port: int, options: list) -> str | return None -async def _check_if_slave_multi( - host: str, port: int, options: list, master_ip: str, config_path: str -) -> tuple[bool, str, str]: - """Multi-auth version of _check_if_slave.""" +async def _fetch_slave_config( + host: str, port: int, options: list, config_path: str +) -> str | None: + """Read config from a slave candidate via SSH (one connection). Returns None on failure.""" try: - config_text = await _ssh_run_multi(host, port, options, f"cat {config_path}", timeout=15) - parser = RawConfigParser(strict=False) - parser.read_string(config_text) - linking_cls = parser.get("plugin:linking", "cls", fallback="") - linking_uri = parser.get("plugin:linking", "uri", fallback="") - identifier = parser.get("caps.rack", "identifier", fallback="").strip() - if "secondary" in linking_cls.lower() and master_ip in linking_uri: - return True, identifier or host, config_text + return await _ssh_run_multi(host, port, options, f"cat {config_path}", timeout=15) except Exception: - pass + return None + + +def _parse_slave_config(config_text: str, master_ip: str, host: str) -> tuple[bool, str, str]: + """Check parsed config against one master_ip. No network I/O.""" + parser = RawConfigParser(strict=False) + parser.read_string(config_text) + linking_cls = parser.get("plugin:linking", "cls", fallback="") + linking_uri = parser.get("plugin:linking", "uri", fallback="") + identifier = parser.get("caps.rack", "identifier", fallback="").strip() + if "secondary" in linking_cls.lower() and master_ip in linking_uri: + return True, identifier or host, config_text return False, "", "" + async def _open_tunnel(tunnel_host: str, tunnel_port: int, ssh_options: list, target_host: str, target_port: int): """Try each ssh_option in order; return open asyncssh connection on first success or raise.""" last_exc: Exception = Exception("No SSH options provided") @@ -1194,30 +1199,27 @@ async def discover_via_ssh( for candidate_ip in slave_candidates: logger.info(f"[ssh_discovery] Checking slave candidate {candidate_ip}...") await _emit("disc_action", {"message": f"Проверяем слейв-кандидата {candidate_ip}..."}) - # Check each rack host that could be the master — slave's linking.uri must match + + # Fetch config once — then check against all masters without extra SSH connections + slave_config_text = await _fetch_slave_config(candidate_ip, ssh_port, ssh_options, config_path) is_slave = False slave_identifier = "" - slave_config_text = "" master_ip_for_slave = "" - for _, master_host, _, _ in rack_hosts: - ok, identifier, slave_cfg = await _check_if_slave_multi( - host=candidate_ip, - port=ssh_port, - options=ssh_options, - master_ip=master_host, - config_path=config_path, - ) - if ok: - is_slave = True - slave_config_text = slave_cfg - master_ip_for_slave = master_host - # Derive slave name from master's external_id: "lane-1-1" → "lane-1-1-slave" - master_ext_id = next( - (ext_id for ext_id, host, _, _ in rack_hosts if host == master_host), - master_host, - ) - slave_identifier = f"{master_ext_id}-slave" - break + + if slave_config_text is None: + logger.info(f"[ssh_discovery] {candidate_ip} — could not read config, skipping") + else: + for _, master_host, _, _ in rack_hosts: + ok, identifier, _ = _parse_slave_config(slave_config_text, master_host, candidate_ip) + if ok: + is_slave = True + master_ip_for_slave = master_host + master_ext_id = next( + (ext_id for ext_id, host, _, _ in rack_hosts if host == master_host), + master_host, + ) + slave_identifier = f"{master_ext_id}-slave" + break if not is_slave: logger.info(f"[ssh_discovery] {candidate_ip} — not a slave rack (no secondary linking pointing to known master)")