фикс парсинга

This commit is contained in:
dv 2026-04-15 17:02:44 +03:00
parent 4c422741c5
commit 8a7a2ff9e1
2 changed files with 56 additions and 40 deletions

View file

@ -3,6 +3,7 @@ CAPS INI config parsing utilities.
All functions are pure (no network I/O, no DB).
"""
import configparser as _cp
import logging
import re
from configparser import RawConfigParser
@ -36,7 +37,12 @@ def parse_config(
all_rack_hosts = [rack_host]
parser = RawConfigParser(strict=False)
parser.read_string(config_text)
try:
parser.read_string(config_text)
except _cp.Error as exc:
# Malformed lines in the config — parser is already partially populated
# with all sections up to the error. Log and continue with what we have.
logger.warning("[discovery] Config parse warning for %s: %s", rack_host, exc)
discovered: list[DiscoveredDevice] = []
seen_ips: set[str] = set()

View file

@ -123,63 +123,73 @@ async def discover_via_ssh(
neighbor_candidates: set[str] = set()
discovered_device_ips: set[str] = set()
all_rack_ips = [h for _, h, _, _ in rack_hosts]
for rack_idx, (external_id, rack_host, rack_name, _rack_type_db) in enumerate(rack_hosts):
await _emit("disc_rack_start", {
"rack_name": rack_name, "rack_host": rack_host,
"index": rack_idx, "total": len(rack_hosts),
})
# Allow per-device config_path override stored in connection_params.
effective_config_path = await _resolve_rack_config_path(
db, obj, rack_host, config_path
)
_rack_found: int = 0
_rack_ssh_ok: bool = False
_flush_error: Exception | None = None
rack_ssh_ok, config_text = await _ssh_fetch_rack_config(
rack_host, ssh_port, ssh_options, effective_config_path, rack_name, result, _emit
)
try:
# Allow per-device config_path override stored in connection_params.
effective_config_path = await _resolve_rack_config_path(
db, obj, rack_host, config_path
)
await _upsert_rack_device(
db, obj, external_id, rack_host, rack_name,
rack_id_mapping, rack_ssh_ok, now, result
)
rack_ssh_ok, config_text = await _ssh_fetch_rack_config(
rack_host, ssh_port, ssh_options, effective_config_path, rack_name, result, _emit
)
_rack_ssh_ok = rack_ssh_ok
await _upsert_rack_device(
db, obj, external_id, rack_host, rack_name,
rack_id_mapping, rack_ssh_ok, now, result
)
if rack_ssh_ok:
neigh_ips = await get_ip_neighbors_multi(rack_host, ssh_port, ssh_options)
neighbor_candidates.update(neigh_ips)
discovered_device_ips.add(rack_host)
await _emit("disc_action", {"message": f"Парсинг конфигурации {rack_name}..."})
devices_found = parse_config(config_text, rack_host, all_rack_ips)
logger.info("[discovery] Found %d devices from plugins in %s", len(devices_found), rack_host)
_rack_found = len(devices_found)
for found in devices_found:
await _upsert_plugin_device(
db, obj, found, external_id, rack_id_mapping, result
)
discovered_device_ips.add(found.ip)
if not rack_ssh_ok:
try:
await db.flush()
except Exception as exc:
raise RuntimeError(f"DB flush failed for offline rack {rack_host}: {exc}") from exc
await _emit("disc_rack_done", {
"rack_name": rack_name, "rack_host": rack_host, "found": 0, "ssh_ok": False
})
continue
# Session is now invalid after a failed flush — capture the error so we can
# still emit disc_rack_done before propagating and aborting discovery.
_flush_error = RuntimeError(f"DB flush failed after rack {rack_host}: {exc}") from exc
neigh_ips = await get_ip_neighbors_multi(rack_host, ssh_port, ssh_options)
neighbor_candidates.update(neigh_ips)
discovered_device_ips.add(rack_host)
all_rack_ips = [h for _, h, _, _ in rack_hosts]
await _emit("disc_action", {"message": f"Парсинг конфигурации {rack_name}..."})
devices_found = parse_config(config_text, rack_host, all_rack_ips)
logger.info("[discovery] Found %d devices from plugins in %s", len(devices_found), rack_host)
for found in devices_found:
await _upsert_plugin_device(
db, obj, found, external_id, rack_id_mapping, result
)
discovered_device_ips.add(found.ip)
try:
await db.flush()
except Exception as exc:
# Session is now invalid — re-raise so the outer handler emits disc_done with error.
# Continuing would cause every subsequent DB call to fail silently, producing
# an incomplete discovery with no clear termination signal to the frontend.
raise RuntimeError(f"DB flush failed after rack {rack_host}: {exc}") from exc
# Unexpected per-rack error (e.g. SSH timeout, config parse failure not caught
# at a lower level). Record it and fall through to emit disc_rack_done.
result.errors.append(f"Rack {rack_name} ({rack_host}): {exc}")
logger.error("[discovery] Rack %s (%s) error: %s", rack_host, rack_name, exc, exc_info=True)
# disc_rack_done is always emitted — frontend must never be left with a spinner
# for a rack that already received disc_rack_start.
await _emit("disc_rack_done", {
"rack_name": rack_name, "rack_host": rack_host,
"found": len(devices_found), "ssh_ok": True,
"found": _rack_found, "ssh_ok": _rack_ssh_ok,
})
if _flush_error:
raise _flush_error
# ── Phase 2.5: Slave rack discovery via ARP ───────────────────────────────
all_rack_ips_set = {h for _, h, _, _ in rack_hosts}
infra_ips = {ip for ip in [obj.server_ip, db_host] if ip}