diff --git a/backend/app/services/discovery/config_parser.py b/backend/app/services/discovery/config_parser.py index b3afaa6..9b0d28d 100644 --- a/backend/app/services/discovery/config_parser.py +++ b/backend/app/services/discovery/config_parser.py @@ -158,7 +158,10 @@ def parse_slave_check(config_text: str, master_ip: str, host: str) -> tuple[bool (is_slave, identifier, config_text) — config_text is passed through unchanged. """ parser = RawConfigParser(strict=False) - parser.read_string(config_text) + try: + parser.read_string(config_text) + except _cp.Error as exc: + logger.warning("[discovery] Config parse warning for %s: %s", host, exc) linking_cls = parser.get("plugin:linking", "cls", fallback="") linking_uri = parser.get("plugin:linking", "uri", fallback="") identifier = parser.get("caps.rack", "identifier", fallback="").strip() diff --git a/backend/app/services/discovery/orchestrator.py b/backend/app/services/discovery/orchestrator.py index 2a8c5ed..4b286b7 100644 --- a/backend/app/services/discovery/orchestrator.py +++ b/backend/app/services/discovery/orchestrator.py @@ -692,58 +692,64 @@ async def _discover_slave_racks( logger.info("[discovery] Checking slave candidate %s...", candidate_ip) await _emit("disc_action", {"message": f"Проверяем слейв-кандидата {candidate_ip}..."}) - slave_config = await fetch_slave_config(candidate_ip, ssh_port, ssh_options, config_path) - is_slave = False - slave_identifier = "" - master_ip_for_slave = "" - - if slave_config is None: - logger.info("[discovery] %s — could not read config, skipping", candidate_ip) - else: - for _, master_host, _, _ in rack_hosts: - ok, _, _ = parse_slave_check(slave_config, master_host, candidate_ip) - if ok: - is_slave = True - master_ip_for_slave = master_host - master_ext_id = next( - (ext_id for ext_id, h, _, _ in rack_hosts if h == master_host), - master_host, - ) - slave_identifier = f"{master_ext_id}-slave" - break - - if not is_slave: - logger.info("[discovery] %s — not a slave rack", candidate_ip) - continue - - logger.info("[discovery] %s — SLAVE confirmed (name=%s, master=%s)", candidate_ip, slave_identifier, master_ip_for_slave) - await _emit("disc_action", {"message": f"Слейв подтверждён: {slave_identifier} ({candidate_ip})"}) - - slave_rack_id = await _ensure_slave_rack(db, obj, slave_identifier, result) - - await _upsert_slave_device(db, obj, candidate_ip, slave_identifier, slave_rack_id, master_ip_for_slave, now, result) - - all_rack_ips_with_slave = list(all_rack_ips_set) + [candidate_ip] - slave_devices = parse_config(slave_config, candidate_ip, all_rack_ips_with_slave) - logger.info("[discovery] Slave %s: found %d plugin devices", candidate_ip, len(slave_devices)) - await _emit("disc_slave_found", { - "rack_name": slave_identifier, "ip": candidate_ip, "found": len(slave_devices) - }) - - for found in slave_devices: - await _upsert_plugin_device( - db, obj, found, - external_id="", # not used for slaves (is_slave=True path) - rack_id_mapping={}, - result=result, - slave_rack_id=slave_rack_id, - slave_identifier=slave_identifier, - ) - try: - await db.flush() + slave_config = await fetch_slave_config(candidate_ip, ssh_port, ssh_options, config_path) + is_slave = False + slave_identifier = "" + master_ip_for_slave = "" + + if slave_config is None: + logger.info("[discovery] %s — could not read config, skipping", candidate_ip) + else: + for _, master_host, _, _ in rack_hosts: + ok, _, _ = parse_slave_check(slave_config, master_host, candidate_ip) + if ok: + is_slave = True + master_ip_for_slave = master_host + master_ext_id = next( + (ext_id for ext_id, h, _, _ in rack_hosts if h == master_host), + master_host, + ) + slave_identifier = f"{master_ext_id}-slave" + break + + if not is_slave: + logger.info("[discovery] %s — not a slave rack", candidate_ip) + continue + + logger.info("[discovery] %s — SLAVE confirmed (name=%s, master=%s)", candidate_ip, slave_identifier, master_ip_for_slave) + await _emit("disc_action", {"message": f"Слейв подтверждён: {slave_identifier} ({candidate_ip})"}) + + slave_rack_id = await _ensure_slave_rack(db, obj, slave_identifier, result) + + await _upsert_slave_device(db, obj, candidate_ip, slave_identifier, slave_rack_id, master_ip_for_slave, now, result) + + all_rack_ips_with_slave = list(all_rack_ips_set) + [candidate_ip] + slave_devices = parse_config(slave_config, candidate_ip, all_rack_ips_with_slave) + logger.info("[discovery] Slave %s: found %d plugin devices", candidate_ip, len(slave_devices)) + await _emit("disc_slave_found", { + "rack_name": slave_identifier, "ip": candidate_ip, "found": len(slave_devices) + }) + + for found in slave_devices: + await _upsert_plugin_device( + db, obj, found, + external_id="", + rack_id_mapping={}, + result=result, + slave_rack_id=slave_rack_id, + slave_identifier=slave_identifier, + ) + + try: + await db.flush() + except Exception as exc: + result.errors.append(f"Flush error after slave {candidate_ip}: {exc}") + logger.error("[discovery] Flush error after slave %s: %s", candidate_ip, exc) + except Exception as exc: - result.errors.append(f"Flush error after slave {candidate_ip}: {exc}") + result.errors.append(f"Slave candidate {candidate_ip}: {exc}") + logger.error("[discovery] Error processing slave candidate %s: %s", candidate_ip, exc, exc_info=True) async def _ensure_slave_rack( @@ -875,7 +881,11 @@ async def _discover_mikrotik( ip=mikrotik_ip, hostname="mikrotik", action="ERROR", reason=str(exc), category="router", role="other", )) - await db.flush() + try: + await db.flush() + except Exception as exc: + result.errors.append(f"Flush error after mikrotik upsert: {exc}") + logger.error("[discovery] Flush error after mikrotik: %s", exc) async def _discover_proxmox( @@ -934,7 +944,11 @@ async def _discover_proxmox( ip=proxmox_ip, hostname=hostname, action="ERROR", reason=str(exc), category="vm", role="other", )) - await db.flush() + try: + await db.flush() + except Exception as exc: + result.errors.append(f"Flush error after proxmox upsert: {exc}") + logger.error("[discovery] Flush error after proxmox: %s", exc) # ── Summary logging ───────────────────────────────────────────────────────────