mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 23:20:51 +08:00
Fix empty Cisco IOSv CLI output and harden Windows stop/start.
Drain leftover prompts before Netmiko send_command, and make stop/start tolerate orphan workers and locked log files. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3612dd6e45
commit
bd01440e9d
5 changed files with 187 additions and 49 deletions
|
|
@ -97,6 +97,14 @@ def _collect_on_device(
|
|||
except Exception:
|
||||
_log.debug("collection paging disable failed", exc_info=True)
|
||||
prompt = str(conn.find_prompt() or "")
|
||||
# find_prompt / paging-off often leave an extra prompt in the channel;
|
||||
# drain before the first show or Netmiko returns empty (IOSv).
|
||||
try:
|
||||
from .ne_netmiko import drain_read_channel
|
||||
|
||||
drain_read_channel(conn)
|
||||
except Exception:
|
||||
_log.debug("collection channel drain failed", exc_info=True)
|
||||
for command in commands:
|
||||
if conn_holder is not None and conn_holder.get("timed_out"):
|
||||
raise TimeoutError("collection_aborted")
|
||||
|
|
|
|||
|
|
@ -95,18 +95,45 @@ def disable_target_paging(
|
|||
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).
|
||||
def drain_read_channel(
|
||||
conn: Any,
|
||||
*,
|
||||
idle_reads: int = 4,
|
||||
pause_sec: float = 0.05,
|
||||
) -> str:
|
||||
"""Read until the SSH/Telnet channel is quiet.
|
||||
|
||||
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.
|
||||
Cisco IOSv (and similar) often leave an extra ``R2#`` in the buffer after
|
||||
``find_prompt`` / ``terminal length 0``. Netmiko ``send_command`` then matches
|
||||
that leftover prompt immediately and returns empty after strip — while a
|
||||
human terminal still echoes show output normally.
|
||||
"""
|
||||
cmd = str(command or "").strip()
|
||||
if not cmd:
|
||||
chunks: list[str] = []
|
||||
idle = 0
|
||||
read = getattr(conn, "read_channel", None)
|
||||
if not callable(read):
|
||||
clear = getattr(conn, "clear_buffer", None)
|
||||
if callable(clear):
|
||||
try:
|
||||
clear()
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
while idle < max(1, int(idle_reads)):
|
||||
try:
|
||||
part = read()
|
||||
except Exception:
|
||||
break
|
||||
if part:
|
||||
chunks.append(str(part))
|
||||
idle = 0
|
||||
continue
|
||||
idle += 1
|
||||
time.sleep(max(0.0, float(pause_sec)))
|
||||
return "".join(chunks)
|
||||
|
||||
|
||||
def _send_command_expect_prompt(conn: Any, cmd: str, *, read_timeout: int) -> str:
|
||||
try:
|
||||
return str(
|
||||
conn.send_command(
|
||||
|
|
@ -119,3 +146,36 @@ def send_show_command(conn: Any, command: str, *, read_timeout: int = 120) -> st
|
|||
except TypeError:
|
||||
# Older Netmiko without cmd_verify kwarg.
|
||||
return str(conn.send_command(command_string=cmd, read_timeout=read_timeout) or "")
|
||||
|
||||
|
||||
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`` as the primary path for Cisco config
|
||||
collection: long idle during ``Building configuration...`` is treated as
|
||||
end-of-output and truncates the config. Timing is only a fallback when
|
||||
expect-prompt returns empty after a channel drain (IOSv leftover-prompt bug).
|
||||
|
||||
``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 ""
|
||||
|
||||
drain_read_channel(conn)
|
||||
out = _send_command_expect_prompt(conn, cmd, read_timeout=read_timeout)
|
||||
if out.strip():
|
||||
return out
|
||||
|
||||
# Leftover prompt matched before command echo — drain again and retry once.
|
||||
drain_read_channel(conn)
|
||||
out = _send_command_expect_prompt(conn, cmd, read_timeout=read_timeout)
|
||||
if out.strip():
|
||||
return out
|
||||
|
||||
drain_read_channel(conn)
|
||||
try:
|
||||
return str(conn.send_command_timing(cmd, read_timeout=read_timeout) or "")
|
||||
except Exception:
|
||||
return ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue