netx/netx_api/ne_netmiko.py
oliver fbf73cbaaf Disable target paging after hop login and keep CLI echo on timeouts.
LLDP discover and config collect were timing out on --More--; send vendor paging-off after nested/bastion auth, and attach session-log tails so failures show where the CLI stuck.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 12:30:33 +08:00

121 lines
4.4 KiB
Python

"""Shared Netmiko helpers for managed NE connect/collect."""
from __future__ import annotations
import time
from typing import Any
def normalize_netmiko_device_type(device_type: str, protocol: str) -> str:
dt = str(device_type or "").strip()
proto = str(protocol or "ssh").strip().lower()
low = dt.lower()
# Raw / SecureCRT-style sessions (WebCRT quick-connect stores device_type=generic).
if low in ("generic", "generic_ssh", "generic_telnet", "terminal_server", "generic_termserver"):
return "generic_telnet" if proto == "telnet" or "telnet" in low else "generic_termserver_ssh"
# Netmiko ships linux / linux_ssh but not linux_telnet — use generic_telnet.
if low in ("linux", "linux_ssh", "linux_telnet") or low.startswith("linux_"):
if proto == "telnet" or "telnet" in low:
return "generic_telnet"
return "linux_ssh"
if "zte" in low:
if dt == "zte":
return f"zte_zxros_{proto}"
if "telnet" not in low and "ssh" not in low:
return f"{dt}_{proto}"
return dt
if "telnet" not in low and "ssh" not in low:
return f"{dt}_{proto}"
return dt
def is_cisco_ios_device_type(device_type: str) -> bool:
dt = str(device_type or "").strip().lower()
return "cisco_ios" in dt or dt in {"cisco_ios", "cisco_ios_ssh", "cisco_ios_telnet"}
def is_zte_device_type(device_type: str) -> bool:
return "zte" in str(device_type or "").strip().lower()
def is_huawei_device_type(device_type: str) -> bool:
return "huawei" in str(device_type or "").strip().lower()
def paging_disable_commands(*, vendor: str = "", device_type: str = "") -> list[str]:
"""Vendor CLI commands to disable --More-- paging (collection / LLDP / sync)."""
dt = str(device_type or "").strip().lower()
v = str(vendor or "").strip().lower()
blob = f"{dt} {v}"
if "huawei" in blob or "华为" in v:
return ["screen-length 0 temporary"]
if "hp_comware" in blob or "h3c" in blob or "comware" in blob:
return ["screen-length disable"]
if "juniper" in blob or "junos" in blob:
return ["set cli screen-length 0"]
if "nokia" in blob or "alcatel_sros" in blob or "sros" in blob:
return ["environment no more"]
if "alcatel_aos" in blob:
return ["terminal length 0"]
# Cisco / ZTE / generic SSH CLIs
return ["terminal length 0"]
def disable_target_paging(
conn: Any,
*,
vendor: str = "",
device_type: str = "",
) -> str:
"""Send paging-off on the *current* CLI (critical after CLI hop / bastion jump).
Nested stelnet/telnet lands on the target without Netmiko ``session_preparation``,
so --More-- stays enabled and long ``display/show lldp`` / config dumps time out.
Uses timing reads so a wrong hop ``base_prompt`` does not block.
"""
cmds = paging_disable_commands(vendor=vendor, device_type=device_type)
chunks: list[str] = []
for cmd in cmds:
try:
out = conn.send_command_timing(cmd, read_timeout=15)
chunks.append(str(out or ""))
continue
except Exception:
pass
try:
ret = getattr(conn, "RETURN", None) or "\n"
conn.write_channel(str(cmd) + ret)
time.sleep(0.35)
if hasattr(conn, "read_channel"):
part = conn.read_channel()
if part:
chunks.append(str(part))
except Exception:
pass
return "".join(chunks)
def send_show_command(conn: Any, command: str, *, read_timeout: int = 120) -> str:
"""Send a show/display command via ``send_command`` (wait for device prompt).
Do not use ``send_command_timing`` for Cisco config collection: long idle during
``Building configuration...`` is treated as end-of-output and truncates the config.
``cmd_verify=False``: Netmiko's default echo check often raises
``Pattern not detected: 'show\\ lldp\\ ...'`` on IOSv / hop / slow echo paths.
"""
cmd = str(command or "").strip()
if not cmd:
return ""
try:
return str(
conn.send_command(
command_string=cmd,
read_timeout=read_timeout,
cmd_verify=False,
)
or ""
)
except TypeError:
# Older Netmiko without cmd_verify kwarg.
return str(conn.send_command(command_string=cmd, read_timeout=read_timeout) or "")