基于HCL的python脚本编程(2)

基于HCL的python脚本编程(2)

1. 脚本编程

1.1 手搓部分1:搭建基础拓扑及基本配置

按如图搭建拓扑结构,启动,配置好ip,给各个路由器都配置好telnet(及ssh登录),用户名为各设备的名称,所有登录密码为Admin@12345,这样,就可以从宿主机远程登录各设备。

1.2 手搓部分2:基础配置

如R14配置文件,disp cur:

python 复制代码
#
 version 7.1.064, Release 0427P22
#
 sysname R14
#
 telnet server enable
#
 system-working-mode standard
 xbar load-single
 password-recovery enable
 lpu-type f-series
#
vlan 1
#
interface Serial1/0
#
interface Serial2/0
#
interface Serial3/0
#
interface Serial4/0
#
interface NULL0
#
interface GigabitEthernet0/0
 port link-mode route
 combo enable copper
 ip address 192.168.0.174 255.255.255.0
#
interface GigabitEthernet0/1
 port link-mode route
 combo enable copper
#
interface GigabitEthernet0/2
 port link-mode route
 combo enable copper
#
interface GigabitEthernet5/0
 port link-mode route
 combo enable copper
#
interface GigabitEthernet5/1
 port link-mode route
 combo enable copper
#
interface GigabitEthernet6/0
 port link-mode route
 combo enable copper
#
interface GigabitEthernet6/1
 port link-mode route
 combo enable copper
#
 scheduler logfile size 16
#
line class aux
 user-role network-operator
#
line class console
 user-role network-admin
#
line class tty
 user-role network-operator
#
line class vty
 user-role network-operator
#
line aux 0
 user-role network-operator
#
line con 0
 user-role network-admin
#
line vty 0 63
 authentication-mode scheme
 user-role network-operator
 idle-timeout 0 0
#
 ssh server enable
 ssh user R14 service-type stelnet authentication-type password
#
domain system
#
 domain default enable system
#
role name level-0
 description Predefined level-0 role
#
role name level-1
 description Predefined level-1 role
#
role name level-2
 description Predefined level-2 role
#
role name level-3
 description Predefined level-3 role
#
role name level-4
 description Predefined level-4 role
#
role name level-5
 description Predefined level-5 role
#
role name level-6
 description Predefined level-6 role
#
role name level-7
 description Predefined level-7 role
#
role name level-8
 description Predefined level-8 role
#
role name level-9
 description Predefined level-9 role
#
role name level-10
 description Predefined level-10 role
#
role name level-11
 description Predefined level-11 role
#
role name level-12
 description Predefined level-12 role
#
role name level-13
 description Predefined level-13 role
#
role name level-14
 description Predefined level-14 role
#
user-group system
#
local-user R14 class manage
 password hash $h$6$cCG2CWWS+FmHQ/zc$g5K50g0rZznQo/Xq93tL/ub2n9EvI3pRoTzdxWqx83pLwm6fsa+rcGKA55EuCdiDa9aiZqaPcOUpmgcrexu/ww==
 service-type ssh telnet
 authorization-attribute user-role network-admin
 authorization-attribute user-role network-operator
#

将上述内容拷贝下来,按照其他设备的名称和ip修改:

python 复制代码
#
 version 7.1.064, Release 0427P22
#
 sysname R13
#
......
 ip address 192.168.0.173 255.255.255.0
#
......
#
 ssh server enable
 ssh user R13 service-type stelnet authentication-type password
#
domain system
......
#
user-group system
#
local-user R13 class manage
 ......
#

进入R13,进入系统视图,将以上修改后的内容,贴进去,保存即可。

1.3 在vscode里求助workbuddy,创建目录结构

将交换机路由器常见的、常用的功能,写成一个工具集,

python 复制代码
# -*- coding: utf-8 -*-
"""
文件名 :hclnet.py
功能   :HCL 自动化公共库(登录 / 下发 / 保存 / 采集 / 结果汇总)
说明   :
    1. 所有案例脚本都 import 这个文件,不用每个脚本重复写 ConnectHandler。
    2. 登录会自动尝试多种认证方式:devices.yaml 里的账号 -> none 认证 -> admin。
       HCL 里有的设备开了 scheme 认证,有的还是 telnet none 认证,这个兜底很好使。
    3. 下发后自动扫描回显里的 "%"、"Wrong parameter" 等字样,
       避免出现"脚本说成功、其实命令没配进去"的假成功。
    4. HCL 是模拟器,比真机慢,所以默认 fast_cli=False + 更长 read_timeout。
作者  :wuzz@cqie
时间  :
"""
from __future__ import annotations

import argparse
import logging
import re
import time
from pathlib import Path

import yaml
from netmiko import ConnectHandler

BASE_DIR = Path(__file__).resolve().parent.parent   # 即hcl-lab/
DEVICES_FILE = BASE_DIR / "devices.yaml"
OUTPUT_DIR = BASE_DIR / "output"
DEFAULT_PASSWORD = "Admin@12345"

log = logging.getLogger("hcl")

# 接口简写 -> 全称(display interface brief 里显示的是简写)
INTF_PREFIX = {
    "XGE": "Ten-GigabitEthernet",
    "FGE": "FortyGigE",
    "M-GE": "M-GigabitEthernet",
    "GE": "GigabitEthernet",
    "BAGG": "Bridge-Aggregation",
    "RAGG": "Route-Aggregation",
    "Vlan": "Vlan-interface",
    "Loop": "LoopBack",
    "Tun": "Tunnel",
    "Ser": "Serial",
    "NULL": "NULL",
}

# 这些字样出现在回显里,说明命令没被设备接受
ERR_PATTERNS = (
    "% ",
    "%^",
    "Wrong parameter",
    "Unrecognized command",
    "Incomplete command",
    "Too many parameters",
    "Error:",
    "Failed to",
)

_ABBR_GROUP = "|".join(sorted(INTF_PREFIX, key=len, reverse=True))
_IF_ROW = re.compile(
    rf"^(?P<short>(?:{_ABBR_GROUP})[\w/.:-]*)\s+(?P<link>UP|DOWN|ADM)\s+(?P<rest>.*)$"
)


# --------------------------------------------------------------------------
# 日志 / 参数
# --------------------------------------------------------------------------
def setup_logging(debug: bool = False) -> None:
    logging.basicConfig(
        level=logging.DEBUG if debug else logging.INFO,
        format="%(asctime)s %(levelname)-7s %(message)s",
        datefmt="%H:%M:%S",
    )
    logging.getLogger("netmiko").setLevel(logging.WARNING)
    logging.getLogger("paramiko").setLevel(logging.WARNING)


def build_parser(description: str) -> argparse.ArgumentParser:
    """每个案例脚本统一用这套命令行参数。"""
    p = argparse.ArgumentParser(
        description=description, formatter_class=argparse.RawDescriptionHelpFormatter
    )
    p.add_argument("--dry-run", action="store_true", help="只打印要下发的命令,不连设备")
    p.add_argument("-y", "--yes", action="store_true", help="跳过交互确认")
    p.add_argument("--only", help="只操作部分设备,逗号分隔,如 R1,R2")
    p.add_argument("--no-save", action="store_true", help="下发后不执行 save force")
    p.add_argument("--debug", action="store_true", help="输出调试日志")
    return p


# --------------------------------------------------------------------------
# 设备清单
# --------------------------------------------------------------------------
def load_devices(only: str | None = None, path: Path | str = DEVICES_FILE) -> list[dict]:
    data = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
    devs = []
    for d in data.get("devices") or []:
        if not d.get("enabled", True):
            continue
        d.setdefault("device_type", "hp_comware_telnet")
        d.setdefault("port", 23)
        d.setdefault("username", "")
        d.setdefault("password", "")
        devs.append(d)
    if only:
        want = {x.strip().upper() for x in only.split(",") if x.strip()}
        devs = [d for d in devs if str(d["name"]).upper() in want]
    return devs


def full_name(short: str) -> str:
    """GE0/0 -> GigabitEthernet0/0"""
    for abbr in sorted(INTF_PREFIX, key=len, reverse=True):
        if short.lower().startswith(abbr.lower()):
            return INTF_PREFIX[abbr] + short[len(abbr):]
    return short


def short_name(name: str) -> str:
    """GigabitEthernet0/0 -> GE0/0"""
    for full in sorted(INTF_PREFIX.values(), key=len, reverse=True):
        if name.lower().startswith(full.lower()):
            return _abbr_of(full) + name[len(full):]
    return name


def _abbr_of(full: str) -> str:
    for abbr, value in INTF_PREFIX.items():
        if value == full:
            return abbr
    return full


# --------------------------------------------------------------------------
# 连接 / 下发 / 保存 / 采集
# --------------------------------------------------------------------------
def _auth_candidates(dev: dict) -> list[tuple[str, str]]:
    """可能的认证方式,按命中概率排序:
    1) devices.yaml 里写的账号
    2) 设备名当用户名(本项目约定 local-user <主机名> / Admin@12345)
    3) 空账号(HCL 出厂默认 telnet none 认证)
    4) admin
    """
    cands = []
    if dev.get("username"):
        cands.append((dev["username"], dev.get("password") or ""))
    cands.append((str(dev["name"]), dev.get("password") or DEFAULT_PASSWORD))
    cands.append(("", ""))                                    # HCL 默认 telnet none
    cands.append(("admin", dev.get("password") or DEFAULT_PASSWORD))
    seen, uniq = set(), []
    for c in cands:
        if c not in seen:
            seen.add(c)
            uniq.append(c)
    return uniq


def connect(dev: dict, session_log: bool = False):
    """登录一台设备,返回 netmiko 连接对象。失败抛 RuntimeError。"""
    name = dev["name"]
    errors = []
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    for user, pwd in _auth_candidates(dev):
        params = {
            "device_type": dev.get("device_type", "hp_comware_telnet"),
            "host": dev["host"],
            "port": int(dev.get("port", 23)),
            "username": user,
            "password": pwd,
            "timeout": 15,
            "conn_timeout": 15,
            "banner_timeout": 30,
            "auth_timeout": 30,
            "fast_cli": False,          # HCL 卡,开 fast_cli 容易读不全回显
            "global_delay_factor": 1,
        }
        if session_log:
            params["session_log"] = str(OUTPUT_DIR / f"session_{name}.log")
        t0 = time.time()
        try:
            conn = ConnectHandler(**params)
        except Exception as exc:                      # noqa: BLE001
            errors.append(f"{user or '<none>'}: {type(exc).__name__}")
            continue
        log.info("[%s] 登录成功(%s,%.1fs)", name, user or "none 认证", time.time() - t0)
        return conn

    raise RuntimeError(
        f"{name}({dev['host']}) 登录失败:{'; '.join(errors)} | "
        "排查:ping 通吗 / telnet server enable / VTY 认证方式 / 账号密码"
    )


def find_errors(text: str) -> list[str]:
    bad = []
    for line in (text or "").splitlines():
        s = line.strip()
        if s and any(p in s for p in ERR_PATTERNS):
            bad.append(s[:120])
    return bad


def push_config(conn, commands: list[str], name: str = "", read_timeout: int = 120) -> str:
    if not commands:
        return ""
    log.info("[%s] 进入 system-view,下发 %d 条命令", name, len(commands))
    out = conn.send_config_set(commands, cmd_verify=False, read_timeout=read_timeout)
    problems = find_errors(out)
    if problems:
        log.warning("[%s] 回显中发现 %d 处异常", name, len(problems))
        for p in problems:
            log.warning("        > %s", p)
    return out


def save_config(conn, name: str = "") -> bool:
    """Comware 用 save force,不会弹 Y/N,一次搞定。"""
    try:
        out = conn.send_command_timing("save force", read_timeout=180)
    except Exception as exc:                          # noqa: BLE001
        log.error("[%s] 保存失败:%s", name, exc)
        return False
    ok = "successfully" in out.lower()
    log.info("[%s] 保存配置 %s", name, "[OK]" if ok else "[WARN] 请手工确认")
    return ok


def show(conn, command: str, read_timeout: int = 120) -> str:
    return conn.send_command(command, read_timeout=read_timeout)


def get_interfaces(conn) -> list[dict]:
    """解析 display interface brief,返回 [{short, full, link, protocol, ip}]"""
    out = show(conn, "display interface brief")
    result = []
    for line in out.splitlines():
        m = _IF_ROW.match(line.strip())
        if not m:
            continue
        rest = m.group("rest").split()
        protocol, ip = "-", ""
        if rest:
            if rest[0] in ("UP", "DOWN"):
                protocol = rest[0]
                rest = rest[1:]
            else:
                protocol = "UP" if m.group("link") == "UP" else "-"
        if rest and re.match(r"^\d+\.\d+\.\d+\.\d+$", rest[0]):
            ip = rest[0]
        result.append(
            {
                "short": m.group("short"),
                "full": full_name(m.group("short")),
                "link": m.group("link"),
                "protocol": protocol,
                "ip": ip,
            }
        )
    return result


def check_interfaces(conn, dev_name: str, wanted: list[str]) -> list[str]:
    """预检:设备上是否真的存在这些接口(拼错端口名时能立刻发现)。"""
    have = {i["short"].lower() for i in get_interfaces(conn)}
    if not have:
        return []
    missing = [w for w in wanted if short_name(w).lower() not in have]
    if not missing:
        return []
    sample = ", ".join(sorted(have)[:12])
    return [f"设备上找不到接口 {', '.join(missing)};实际有:{sample} ...; 请改脚本顶部的接口名"]


# --------------------------------------------------------------------------
# 统一的"打印计划 -> 确认 -> 批量执行 -> 汇总"流程
# --------------------------------------------------------------------------
def run_plan(
    title: str,
    plan: dict[str, list[str]],
    devices: list[dict],
    save: bool = True,
    dry_run: bool = False,
    assume_yes: bool = False,
    verify: tuple[str, ...] = (),
    precheck=None,
) -> list[dict]:
    plan = {k: v for k, v in plan.items() if v}
    dev_map = {d["name"]: d for d in devices}
    skipped = [k for k in plan if k not in dev_map]
    targets = [dev_map[k] for k in plan if k in dev_map]
    if skipped:
        log.debug("以下设备在计划里但未加载(被 --only 过滤或 enabled:false):%s", ", ".join(skipped))

    print("\n" + "=" * 74)
    print(title)
    print(f"目标设备 {len(targets)} 台:{', '.join(d['name'] for d in targets) or '无'}")
    print("=" * 74)
    for d in targets:
        print(f"\n--- {d['name']}  {d['host']}:{d.get('port', 23)} ---")
        for c in plan[d["name"]]:
            print("   " + c)

    if not targets:
        print("\n没有要执行的设备。")
        return []
    if dry_run:
        print(f"\n[dry-run] 以上是 {len(targets)} 台设备要下发的命令,未连接设备。")
        return []
    if not assume_yes:
        ans = input(f"\n确认把以上配置下发到 {len(targets)} 台设备?输入 yes 继续:").strip().lower()
        if ans not in ("yes", "y"):
            print("已取消。")
            return []

    results = []
    for d in targets:
        name = d["name"]
        rec = {"name": name, "ok": False, "problems": [], "error": "", "saved": None, "verify": {}}
        t0 = time.time()
        try:
            conn = connect(d)
            try:
                blocked = precheck(conn, d) if precheck else []
                if blocked:
                    rec["problems"] = blocked
                    log.error("[%s] 预检未通过,跳过下发", name)
                else:
                    out = push_config(conn, plan[name], name)
                    rec["problems"] = find_errors(out)
                    for cmd in verify:
                        rec["verify"][cmd] = show(conn, cmd)
                    if save and not rec["problems"]:
                        rec["saved"] = save_config(conn, name)
                    rec["ok"] = not rec["problems"]
            finally:
                conn.disconnect()
        except Exception as exc:                      # noqa: BLE001
            rec["error"] = f"{type(exc).__name__}: {exc}"
            log.error("[%s] 执行失败:%s", name, rec["error"])
        rec["elapsed"] = time.time() - t0
        results.append(rec)

    print_report(results, verify)
    return results


def print_report(results: list[dict], verify: tuple[str, ...] = ()) -> None:
    print("\n" + "=" * 74)
    print("执行结果")
    print("=" * 74)
    width = max([len(r["name"]) for r in results] + [6])
    ok_cnt = 0
    for r in results:
        if r.get("error"):
            status = "[FAIL] " + r["error"]
        elif r["problems"]:
            status = f"[WARN] 回显异常 {len(r['problems'])} 处"
        else:
            ok_cnt += 1
            status = "[OK]"
            if r.get("saved"):
                status += "  已保存"
            elif r.get("saved") is False:
                status += "  保存失败,请手工 save"
        print(f"{r['name']:<{width}}  {status}  ({r.get('elapsed', 0):.1f}s)")
        for p in r["problems"][:6]:
            print("        > " + p)
    print(f"\n合计 {len(results)} 台,成功 {ok_cnt} 台。")

    for r in results:
        for cmd, out in (r.get("verify") or {}).items():
            print(f"\n--- {r['name']} : {cmd} ---")
            print((out or "").strip())

有了工具,马上安排第一个HCL脚本:

python 复制代码
"""
FilePath: /h3hcl/hcl-lab/scripts/01_telnet_test.py
Author: wuzz@cqie
Date: 2026-10-24 08:17:40
LastEditTime: 2026-10-24 17:47:28
LastEditors: Do not edit
Version: 1.0.0
Description: 
License: CQIE
"""
# -*- coding: utf-8 -*-
"""第一个脚本:Telnet 连接 HCL 设备,采集版本信息。
运行前提:HCL 拓扑已搭好、设备配好 IP 和 Telnet、宿主机能 ping 通设备。
"""
from netmiko import ConnectHandler

dev = {
    "device_type": "hp_comware_telnet", 
    "host": "192.168.0.175",
    "port": 23, 
    "username": "R15", 
    "password": "Admin@12345",
    "fast_cli": True, 
    "session_log": "step1.log"
    }

def main():
    try:
        with ConnectHandler(**dev) as conn:
            conn.send_command("screen-length disable")
            output = conn.send_command("display version")
            print(output)
    except Exception as e:
        print(f"连接失败:{e}")
        print("排查顺序:1) ping 通吗  2) telnet server enable 了吗  3) VTY 配置对吗")

if __name__ == "__main__":
    main()

运行结果:

powershell 复制代码
(venv) PS F:\workbuddywork\h3hcl\hcl-lab\scripts> python .\01_telnet_test.py
F:\workbuddywork\h3hcl\hcl-lab\scripts\01_telnet_test.py:2: SyntaxWarning: "\h" is an invalid escape sequence. Such sequences will not work in the future. Did you mean "\\h"? A raw string is also an option.
  FilePath: \h3hcl\hcl-lab\scripts\01_telnet_test.py
H3C Comware Software, Version 7.1.064, Release 0427P22
Copyright (c) 2004-2021 New H3C Technologies Co., Ltd. All rights reserved.
H3C MSR36-20 uptime is 0 weeks, 0 days, 5 hours, 41 minutes
Last reboot reason: User reboot
Boot image: flash:/msr36-cmw710-boot-r0424p22.bin
Boot image version: 7.1.064, Release 0427P22
  Compiled Mar 16 2021 15:00:00
Boot image: flash:/msr36-cmw710-system-r0424p22.bin
Boot image version: 7.1.064, Release 0427P22
  Compiled Mar 16 2021 15:00:00

CPU ID: 0x2
384M bytes DDR3 SDRAM Memory
1024M bytes Flash Memory
PCB               Version:  2.0
CPLD              Version:  1.0
Basic    BootWare Version:  1.42
Extended BootWare Version:  1.42

查看各设备的接口等情况,为后边的连接提供基础数据:

powershell 复制代码
# -*- coding: utf-8 -*-
# @FilePath:        \h3hcl\hcl-lab\scripts\02_discover.py
# @Author:          wuzz@cqie
# @Date:            2026-10-24 08:17:40
# @LastEditTime:    2026-10-24 17:47:28
# @LastEditors:     wuzz@cqie
# @Version:         1.0.0
# @Description:
# @License:         CQIE
"""
文件名 :02_discover.py
功能   :巡检摸底 ------ 登录 devices.yaml 里所有设备,采集版本 + 接口清单
用法   :
    python scripts\\02_discover.py                 # 全部设备
    python scripts\\02_discover.py --only R1,R2     # 只摸两台
产出   :
    output/discover_<时间>/<设备名>.txt   原始回显
    output/inventory.json                 每台设备的接口摘要(给后面的脚本对照用)
为什么要先跑这个:
    自动配置最容易翻车的地方就是接口名写错(GE0/0 还是 GE0/1?)
    跑一遍就能看清每台机器到底有几个口、哪个口 up、现在什么 IP。
"""
from __future__ import annotations

import json
from datetime import datetime

from hclnet import (
    OUTPUT_DIR,
    build_parser,
    connect,
    get_interfaces,
    load_devices,
    setup_logging,
    show,
)

CMDS = {
    "version": "display version",
    "ip_brief": "display ip interface brief",
    "interface_brief": "display interface brief",
}

def main() -> None:
    args = build_parser(__doc__).parse_args()
    setup_logging(args.debug)
    devices = load_devices(args.only)
    if not devices:
        print("devices.yaml 里没有匹配的设备。")
        return

    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    out_dir = OUTPUT_DIR / f"discover_{stamp}"
    out_dir.mkdir(parents=True, exist_ok=True)

    inventory = {}
    for dev in devices:
        name = dev["name"]
        print("\n" + "=" * 74)
        print(f"{name}  ({dev['host']})")
        print("=" * 74)
        try:
            conn = connect(dev)
        except Exception as exc:                      # noqa: BLE001
            print(f"[FAIL] {exc}")
            inventory[name] = {"host": dev["host"], "error": str(exc)}
            continue

        try:
            prompt = conn.find_prompt()
            raw = {k: show(conn, c) for k, c in CMDS.items()}
            intfs = get_interfaces(conn)
        finally:
            conn.disconnect()

        # 原始回显落盘,方便事后对照
        (out_dir / f"{name}.txt").write_text(
            f"host: {dev['host']}\nprompt: {prompt}\n\n"
            + "\n\n".join(f"##### {k}\n{v}" for k, v in raw.items()),
            encoding="utf-8",
        )

        ips = [i["ip"] for i in intfs if i["ip"]]
        print(f"提示符 {prompt}   接口 {len(intfs)} 个   已配 IP {len(ips)} 个")
        for i in intfs:
            flag = "up  " if i["link"] == "UP" else "down"
            print(f"   {i['short']:<12} {flag}  {i['ip'] or '-'}")

        inventory[name] = {
            "host": dev["host"],
            "prompt": prompt,
            "interfaces": intfs,
        }

    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    inv_file = OUTPUT_DIR / "inventory.json"
    inv_file.write_text(json.dumps(inventory, indent=2, ensure_ascii=False), encoding="utf-8")

    print("\n" + "=" * 74)
    print(f"采集完成:{len(inventory)} 台")
    print(f"原始回显:{out_dir}")
    print(f"接口清单:{inv_file}")
    print("下一步:打开 output/inventory.json 确认接口名,再改 03_ip_basic.py 顶部的 INTERFACES")

if __name__ == "__main__":
    main()

运行结果:

powershell 复制代码
###运行
==========================================================================
R1  (192.168.0.161)
==========================================================================
18:27:34 INFO    [R1] 登录成功(R1,3.3s)
提示符 <R1>   接口 12 个   已配 IP 2 个
   GE0/0        up    192.168.0.161
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       up    12.12.12.1
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R2  (192.168.0.162)
==========================================================================
18:27:41 INFO    [R2] 登录成功(R2,3.4s)
提示符 <R2>   接口 12 个   已配 IP 2 个
   GE0/0        up    192.168.0.162
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       up    12.12.12.2
   Ser2/0       up    -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R3  (192.168.0.163)
==========================================================================
18:27:47 INFO    [R3] 登录成功(R3,3.4s)
提示符 <R3>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.0.163
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       up    -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R4  (192.168.0.164)
==========================================================================
18:27:54 INFO    [R4] 登录成功(R4,3.3s)
提示符 <R4>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.0.164
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R5  (192.168.100.166)
==========================================================================
18:28:00 INFO    [R5] 登录成功(R5,3.3s)
提示符 <R5>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.100.166
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R11  (192.168.0.171)
==========================================================================
18:28:06 INFO    [R11] 登录成功(R11,3.3s)
提示符 <R11>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.0.171
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R12  (192.168.0.172)
==========================================================================
18:28:13 INFO    [R12] 登录成功(R12,3.4s)
提示符 <R12>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.0.172
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R13  (192.168.0.173)
==========================================================================
18:28:19 INFO    [R13] 登录成功(R13,3.3s)
提示符 <R13>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.0.173
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R14  (192.168.0.174)
==========================================================================
18:28:26 INFO    [R14] 登录成功(R14,3.4s)
提示符 <R14>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.0.174
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
R15  (192.168.0.175)
==========================================================================
18:28:32 INFO    [R15] 登录成功(R15,3.3s)
提示符 <R15>   接口 12 个   已配 IP 1 个
   GE0/0        up    192.168.0.175
   GE0/1        down  -
   GE0/2        down  -
   GE5/0        down  -
   GE5/1        down  -
   GE6/0        down  -
   GE6/1        down  -
   NULL0        up    -
   Ser1/0       down  -
   Ser2/0       down  -
   Ser3/0       down  -
   Ser4/0       down  -

==========================================================================
SW1  (192.168.0.201)
==========================================================================
[FAIL] SW1(192.168.0.201) 登录失败:SW1: NetmikoAuthenticationException; <none>: NetmikoAuthenticationException; admin: NetmikoAuthenticationException | 排查:ping 通吗 / telnet server enable / VTY 认证方式 / 账号密码

==========================================================================
采集完成:11 台
原始回显:F:\workbuddywork\h3hcl\hcl-lab\output\discover_20261024_182731
接口清单:F:\workbuddywork\h3hcl\hcl-lab\output\inventory.json
下一步:打开 output/inventory.json 确认接口名,再改 03_ip_basic.py 顶部的 INTERFACES

02_discover.py(摸清接口名)

​ ↓

03_ip_basic.py(批量配 IP / 描述 / 环回口) ← 就是它

​ ↓

04_ospf.py / 05_vlan_gw.py(跑路由、配 VLAN)

down -

GE6/1 down -

NULL0 up -

Ser1/0 down -

Ser2/0 down -

Ser3/0 down -

Ser4/0 down -

==========================================================================

SW1 (192.168.0.201)

FAIL SW1(192.168.0.201) 登录失败:SW1: NetmikoAuthenticationException; : NetmikoAuthenticationException; admin: NetmikoAuthenticationException | 排查:ping 通吗 / telnet server enable / VTY 认证方式 / 账号密码

==========================================================================

采集完成:11 台

原始回显:F:\workbuddywork\h3hcl\hcl-lab\output\discover_20261024_182731

接口清单:F:\workbuddywork\h3hcl\hcl-lab\output\inventory.json

下一步:打开 output/inventory.json 确认接口名,再改 03_ip_basic.py 顶部的 INTERFACES

复制代码
  #   

02_discover.py(摸清接口名)

​    ↓

03_ip_basic.py(批量配 IP / 描述 / 环回口)  ← 就是它

​    ↓

04_ospf.py / 05_vlan_gw.py(跑路由、配 VLAN)
相关推荐
骑着蜗牛撵大象32744 分钟前
手写一个 HTTP 服务器:从 socket 到 epoll,Linux 上到底怎么把请求变成响应
linux·服务器·网络·http·epoll·socket编程·并发模型
神仙别闹1 小时前
基于Python+MySQL实现(Web)个人博客系统
python·mysql·php
二十七日秋1 小时前
算法刷题笔记「Day 1|四道 OJ 入门题里的深坑」
c语言·汇编·数据结构·c++·笔记·python·算法
乌恩大侠1 小时前
从真实 5G 网络采集 AI 训练数据:基于 NVIDIA Aerial Data Lake 构建 AI-RAN 数据平台
网络·人工智能·5g·aerial·o-ru·ai-ran
小咕聊编程1 小时前
基于Django的个人摄影博客网站设计与开发
后端·python·django
论文复现现场2 小时前
DeepSeek V4.1 Flash 和 V4 Pro 有什么区别?API 价格、性能与迁移建议
java·服务器·人工智能·python·大模型api·deepseek·deepseek v4.1
沫璃染墨2 小时前
从零入门计算机网络系列(十):HTTP协议进阶——从资源路径解析到状态码与请求头详解
linux·运维·服务器·网络·c++·http
Sayai2 小时前
Ubuntu 开发环境搭建避坑(一):网卡 EEE 致 60% 周期性丢包,Realtek RTL8125 网络黑洞排查与修复
linux·运维·网络·ubuntu·性能优化
墨染天姬2 小时前
【linux 网络1】数据怎么进网卡到内核?
linux·运维·网络