mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
537 lines
23 KiB
Python
537 lines
23 KiB
Python
"""Synthetic (desensitized) tests for extended ZTE biz_state parsers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from netx_api.biz_state.collect_session import resolve_aux_command
|
|
from netx_api.biz_state.command_match import expand_from_bindings, match_command
|
|
from netx_api.biz_state.enrich import apply_enrich_joins
|
|
from netx_api.biz_state.parsers.common.vrf_list import normalize_vrf_list
|
|
from netx_api.biz_state.parsers.zte import (
|
|
normalize_bgp_peer,
|
|
normalize_bgp_route,
|
|
normalize_ip_route,
|
|
normalize_ipv6_route,
|
|
normalize_l2vpn_pw,
|
|
normalize_l2vpn_pw_detail,
|
|
normalize_optical_brief,
|
|
normalize_ospf_neighbor,
|
|
normalize_vrrp,
|
|
)
|
|
from netx_api.biz_state.profiles import AuxCommand, get_profile, metric_field_map, reload_profiles
|
|
from netx_api.ntc_parse import apply_rule
|
|
|
|
|
|
_VRF_SAMPLE = """
|
|
Name Default RD Protocols VRF ID
|
|
CUST_A 100:1 ipv4 1
|
|
CUST_B 200:2 ipv4,ipv6 2
|
|
mng <not set> ipv4,ipv6 3
|
|
"""
|
|
|
|
_BGP_V4_SUMMARY = """
|
|
Neighbor Ver As MsgRcvd MsgSend Up/Down State/PfxRcd
|
|
10.0.0.1 4 65001 100 200 1w0d 5
|
|
10.0.0.2 4 65002 10 20 00:01:02 Connect
|
|
"""
|
|
|
|
_BGP_V6_SUMMARY = """
|
|
Neighbor Ver As MsgRcvd MsgSend Up/Down State/PfxRcd
|
|
FC00:1::1
|
|
4 65001 100 200 1w0d 0
|
|
FC00:1::2
|
|
4 65002 10 20 2d3h 3
|
|
"""
|
|
|
|
_OSPF_SAMPLE = """
|
|
OSPF Router with ID (1.1.1.1) (Process ID 1)
|
|
|
|
Neighbor ID Pri State DeadTime Address Interface
|
|
2.2.2.2 1 FULL/-- 00:00:39 10.0.0.2 smartgroup1
|
|
3.3.3.3 1 FULL/DR 00:00:30 10.0.0.3 gei-0/0/0/1
|
|
|
|
OSPF Router with ID (1.1.1.1) (Process ID 20)
|
|
|
|
Neighbor ID Pri State DeadTime Address Interface
|
|
4.4.4.4 1 FULL/-- 00:00:40 10.1.0.2 smartgroup2
|
|
"""
|
|
|
|
_VRRP_SAMPLE = """
|
|
Interface vrID Pri Time A P L State Master addr VRouter addr
|
|
vlan10 10 110 1000 P Master 10.0.0.2 10.0.0.1
|
|
gei-0/0/0/1.100 100 90 1000 P Backup 10.0.1.2 10.0.1.1
|
|
smartgroup1.5 5 110 1000 P Init 0.0.0.0 10.0.2.1
|
|
"""
|
|
|
|
_OPTICAL_SAMPLE = """
|
|
Interface Type Wavelength RxPower(dBm) TxPower(dBm) Status Intensity(Rx)
|
|
gei-0/0/0/1 1G-10km-SFP 1310nm -6.0/[-20.0,-3.0] -5.9/[-9.0,-3.0] Normal Normal
|
|
cgei-0/1/0/1 100G-10km-QSFP28 1310nm 1.5/[-10.6,4.5] 0.9/[-4.3,5.5] Normal Normal
|
|
2.1/[-10.6,4.5] 1.5/[-4.3,5.5] Normal Normal
|
|
1.6/[-10.6,4.5] 2.1/[-4.3,5.5] Normal Normal
|
|
0.9/[-10.6,4.5] 2.2/[-4.3,5.5] Normal Normal
|
|
7.5 7.7
|
|
gei-0/0/0/11 offline
|
|
"""
|
|
|
|
# 400G QSFP-DD inserts lane-count token "8X" between Type and Wavelength.
|
|
_OPTICAL_QSFP_DD_SAMPLE = """
|
|
Interface Type Wavelength RxPower(dBm) TxPower(dBm) Status Intensity(Rx)
|
|
cdgei-0/1/0/1 400G-10km-QSFP-DD 8X 1310nm N/A/[-9.0,6.1] 0.9/[-2.7,6.1] Unknown Unknown
|
|
N/A/[-9.0,6.1] 1.0/[-2.7,6.1] Unknown Unknown
|
|
N/A/[-9.0,6.1] 1.2/[-2.7,6.1] Unknown Unknown
|
|
N/A/[-9.0,6.1] 1.2/[-2.7,6.1] Unknown Unknown
|
|
7.1
|
|
cdgei-0/1/0/3 400G-10km-QSFP-DD 8X 1310nm 1.3/[-9.0,5.1] 2.2/[-2.7,6.1] Normal Normal
|
|
1.0/[-9.0,5.1] 2.1/[-2.7,6.1] Normal Normal
|
|
1.0/[-9.0,5.1] 2.2/[-2.7,6.1] Normal Normal
|
|
0.3/[-9.0,5.1] 1.7/[-2.7,6.1] Normal Normal
|
|
6.9 8.1
|
|
cdgei-0/1/0/5 400G-80km-QSFP-DD 8X 1547.715nm -8.8/[-20.0,3.0] -8.3/[-11.0,2.0] Normal Normal
|
|
-8.8 -8.3
|
|
cdgei-0/1/0/6 offline
|
|
cgei-0/1/0/2:1 400G-500m-QSFP-DD 8X 1310nm N/A/[-5.9,5.0] 2.3/[-2.9,5.0] Unknown Unknown
|
|
N/A/[-5.9,5.0] 2.4/[-2.9,5.0] Unknown Unknown
|
|
N/A/[-5.9,5.0] 2.3/[-2.9,5.0] Unknown Unknown
|
|
N/A/[-5.9,5.0] 2.2/[-2.9,5.0] Unknown Unknown
|
|
8.3
|
|
xgei-0/2/0/1 10G-10km-SFP+ 1310nm -2.4/[-14.4,0.5] -1.8/[-8.2,1.5] Normal Normal
|
|
"""
|
|
|
|
_BGP_ROUTE_IN = """
|
|
Routes Learned From This Neighbor:
|
|
Network Next Hop Metric LocPrf RtPrf Path
|
|
* 10.1.0.0/24 10.0.0.1 20 65001 ?
|
|
* 10.2.0.0/24 10.0.0.1 20 65001 65009 ?
|
|
"""
|
|
|
|
_IP_ROUTE = """
|
|
Dest Gw Interface Owner Pri Metric
|
|
*> 0.0.0.0/0 10.0.0.1 smartgroup1 BGP 200 1
|
|
*> 10.1.0.0/24 10.0.0.2 gei-0/0/0/1 connected 0 0
|
|
"""
|
|
|
|
_IPV6_ROUTE = """
|
|
Dest Protocol Pri Metric Flag
|
|
Nexthop,Interface
|
|
2001:db8::/32 B 200 0
|
|
2001:db8:1::1,smartgroup1
|
|
fc00:1::/64 C 0 0
|
|
::,gei-0/0/0/1.10
|
|
"""
|
|
|
|
_L2VPN_PW = """
|
|
PWName PeerIP FEC PWType State Llabel Rlabel VPNOwner
|
|
pw1 10.0.0.1 128 Ethernet H UP 100 200 L:VPN_A
|
|
pw2 10.0.0.2 128 Ethernet S DOWN - - L:VPN_B
|
|
pw3 10.0.0.3 128 Ethernet UP 101 201 W:100
|
|
"""
|
|
|
|
_L2VPN_PW_DETAIL = """
|
|
Service type and instance name:[VPLS qualified demo-vpls-1]
|
|
Peer IP address : 10.0.0.6 VCID : 19001
|
|
Connection mode : HUB VCID Extend : 0
|
|
Signaling protocol : LDP VC type : VLAN
|
|
Last status change time : 09:46:46 Create time : 3d 07:08:42
|
|
MPLS VC local label : 165840 Remote label : 167978
|
|
PW name : pw19001 Control Word : ENABLE
|
|
Activation status : ENABLE
|
|
Band Width : 0 kbps
|
|
Tunnel destination : 10.0.0.6
|
|
Related interface name : -
|
|
FRR type : NULL
|
|
VC status : UP
|
|
Remote status : ALLOK
|
|
MC selection : -
|
|
VCCV CC type : CWORD
|
|
VCCV CV type : LSP
|
|
|
|
Service type and instance name:[VPLS demo-vpls-2]
|
|
Peer IP address : 10.0.0.6 VCID : 19011
|
|
Connection mode : HUB VCID Extend : 0
|
|
Signaling protocol : LDP VC type : Ethernet
|
|
Last status change time : 09:46:48 Create time : 1d 01:00:00
|
|
MPLS VC local label : 165841 Remote label : 167981
|
|
PW name : pw19011 Control Word : DISABLE
|
|
Activation status : ENABLE
|
|
Band Width : 0 kbps
|
|
Tunnel destination : 10.0.0.6
|
|
Related interface name : -
|
|
FRR type : NULL
|
|
VC status : DOWN
|
|
Remote status : PWSA
|
|
MC selection : -
|
|
VCCV CC type : ALERT_LABEL
|
|
VCCV CV type : LSP|BFD_BASIC_HEAD
|
|
"""
|
|
|
|
_L2VPN_PW_DETAIL_SAMPLE = (
|
|
Path(__file__).resolve().parents[2] / "test" / "show-zte" / "show-l2vpn-forwarding-detail"
|
|
)
|
|
|
|
|
|
class ZteExtendedParserTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
reload_profiles()
|
|
|
|
def test_vrf_list_protocols(self) -> None:
|
|
rows = normalize_vrf_list(
|
|
raw_text=_VRF_SAMPLE,
|
|
vendor="zte",
|
|
device_type="zte_zxros",
|
|
command="show ip vrf",
|
|
)
|
|
by = {r["vrf_name"]: r for r in rows}
|
|
self.assertIn("CUST_A", by)
|
|
self.assertEqual(by["CUST_A"]["protocols"], "ipv4")
|
|
self.assertIn("ipv6", by["CUST_B"]["protocols"])
|
|
self.assertEqual(by["CUST_B"]["vrf_id"], "2")
|
|
|
|
def test_bgp_peer_ipv4_and_ipv6_wrap(self) -> None:
|
|
v4 = normalize_bgp_peer(
|
|
raw_text=_BGP_V4_SUMMARY, command="show bgp vpnv4 unicast summary"
|
|
)
|
|
self.assertEqual(len(v4), 2)
|
|
self.assertEqual(v4[0]["afi"], "vpnv4")
|
|
self.assertEqual(v4[0]["state"], "Established")
|
|
self.assertEqual(v4[0]["pfx_rcd"], "5")
|
|
self.assertEqual(v4[1]["state"], "Connect")
|
|
|
|
v6 = normalize_bgp_peer(
|
|
raw_text=_BGP_V6_SUMMARY, command="show bgp vpnv6 unicast summary"
|
|
)
|
|
self.assertEqual(len(v6), 2)
|
|
self.assertTrue(all(r["afi"] == "vpnv6" for r in v6))
|
|
self.assertEqual(v6[0]["neighbor"].upper(), "FC00:1::1")
|
|
|
|
vrf = normalize_bgp_peer(
|
|
raw_text=_BGP_V4_SUMMARY,
|
|
command="show bgp vpnv4 unicast vrf CUST_A summary",
|
|
params={"vrf": "CUST_A"},
|
|
)
|
|
self.assertTrue(all(r["vrf"] == "CUST_A" for r in vrf))
|
|
|
|
def test_bgp_summary_fsm(self) -> None:
|
|
rows = apply_rule(
|
|
platform="zte_zxros",
|
|
rule_key="zte_zxros_show_bgp_summary",
|
|
text=_BGP_V4_SUMMARY,
|
|
command="show bgp vpnv4 unicast summary",
|
|
)
|
|
self.assertGreaterEqual(len(rows), 2)
|
|
|
|
def test_ospf_vrrp_optical(self) -> None:
|
|
ospf = normalize_ospf_neighbor(raw_text=_OSPF_SAMPLE, command="show ip ospf neighbor")
|
|
self.assertEqual(len(ospf), 3)
|
|
procs = {r["process_id"] for r in ospf}
|
|
self.assertEqual(procs, {"1", "20"})
|
|
|
|
vrrp = normalize_vrrp(raw_text=_VRRP_SAMPLE, command="show vrrp ipv4 brief")
|
|
self.assertEqual(len(vrrp), 3)
|
|
self.assertTrue(all(r["af"] == "ipv4" for r in vrrp))
|
|
states = {r["state"] for r in vrrp}
|
|
self.assertIn("Master", states)
|
|
|
|
opt = normalize_optical_brief(
|
|
raw_text=_OPTICAL_SAMPLE,
|
|
vendor="zte",
|
|
device_type="zte_zxros",
|
|
command="show opticalinfo brief",
|
|
)
|
|
self.assertEqual(len(opt), 3)
|
|
by = {r["interface"]: r for r in opt}
|
|
self.assertEqual(by["gei-0/0/0/1"]["rx_power"], "-6.0")
|
|
self.assertEqual(by["gei-0/0/0/1"]["rx_threshold"], "[-20.0,-3.0]")
|
|
self.assertEqual(by["gei-0/0/0/1"]["tx_power"], "-5.9")
|
|
self.assertEqual(by["gei-0/0/0/1"]["tx_threshold"], "[-9.0,-3.0]")
|
|
self.assertEqual(by["gei-0/0/0/1"]["status"], "Normal")
|
|
self.assertEqual(by["cgei-0/1/0/1"]["rx_power"], "1.5,2.1,1.6,0.9")
|
|
self.assertEqual(by["cgei-0/1/0/1"]["tx_power"], "0.9,1.5,2.1,2.2")
|
|
self.assertEqual(by["cgei-0/1/0/1"]["rx_threshold"], "[-10.6,4.5]")
|
|
self.assertEqual(by["cgei-0/1/0/1"]["tx_threshold"], "[-4.3,5.5]")
|
|
self.assertEqual(by["gei-0/0/0/11"]["optic_type"], "offline")
|
|
|
|
def test_optical_qsfp_dd_lane_count_8x(self) -> None:
|
|
"""400G QSFP-DD prints '8X' between Type and Wavelength — must not shift Rx/Tx."""
|
|
opt = normalize_optical_brief(
|
|
raw_text=_OPTICAL_QSFP_DD_SAMPLE,
|
|
vendor="zte",
|
|
device_type="zte_zxros",
|
|
command="show opticalinfo brief | one-line",
|
|
)
|
|
by = {r["interface"]: r for r in opt}
|
|
self.assertIn("cdgei-0/1/0/1", by)
|
|
self.assertEqual(by["cdgei-0/1/0/1"]["rx_power"], "N/A,N/A,N/A,N/A")
|
|
self.assertEqual(by["cdgei-0/1/0/1"]["tx_power"], "0.9,1.0,1.2,1.2")
|
|
self.assertTrue(str(by["cdgei-0/1/0/1"]["wavelength"]).startswith("8X"))
|
|
self.assertEqual(by["cdgei-0/1/0/3"]["rx_power"], "1.3,1.0,1.0,0.3")
|
|
self.assertEqual(by["cdgei-0/1/0/3"]["tx_power"], "2.2,2.1,2.2,1.7")
|
|
# Summary-only continuation (bare numbers / tx-only) must not add lanes
|
|
self.assertEqual(by["cdgei-0/1/0/5"]["rx_power"], "-8.8")
|
|
self.assertEqual(by["cdgei-0/1/0/5"]["tx_power"], "-8.3")
|
|
self.assertEqual(by["cdgei-0/1/0/6"]["status"], "offline")
|
|
self.assertEqual(by["cgei-0/1/0/2:1"]["tx_power"], "2.3,2.4,2.3,2.2")
|
|
self.assertEqual(by["xgei-0/2/0/1"]["rx_power"], "-2.4")
|
|
|
|
def test_bgp_route_and_aux_render(self) -> None:
|
|
routes = normalize_bgp_route(
|
|
raw_text=_BGP_ROUTE_IN,
|
|
command="show bgp vpnv4 unicast vrf CUST_A neighbor in 10.0.0.1",
|
|
params={"vrf": "CUST_A", "neighbor": "10.0.0.1", "direction": "in"},
|
|
)
|
|
self.assertEqual(len(routes), 2)
|
|
self.assertEqual(routes[0]["network"], "10.1.0.0/24")
|
|
self.assertEqual(routes[0]["direction"], "in")
|
|
|
|
peers = normalize_bgp_peer(
|
|
raw_text=_BGP_V4_SUMMARY, command="show bgp vpnv4 unicast vrf CUST_A summary"
|
|
)
|
|
from netx_api.biz_state.enrich import EnrichJoin
|
|
|
|
apply_enrich_joins(
|
|
routes,
|
|
{"bgp_summary": peers},
|
|
[EnrichJoin(from_aux="bgp_summary", on="neighbor", take=("as_num", "state", "pfx_rcd"))],
|
|
)
|
|
self.assertEqual(routes[0]["as_num"], "65001")
|
|
self.assertEqual(routes[0]["state"], "Established")
|
|
|
|
ra = resolve_aux_command(
|
|
AuxCommand(key="bgp_summary", profile_id="zte.bgp_vpnv4_vrf_summary"),
|
|
params={"vrf": "CUST_A"},
|
|
)
|
|
self.assertEqual(ra.command, "show bgp vpnv4 unicast vrf CUST_A summary | one-line")
|
|
|
|
def test_ip_ipv6_route_and_pw(self) -> None:
|
|
ip = normalize_ip_route(
|
|
raw_text=_IP_ROUTE,
|
|
command="show ip forwarding route vrf CUST_A",
|
|
params={"vrf": "CUST_A"},
|
|
)
|
|
self.assertEqual(len(ip), 2)
|
|
self.assertEqual(ip[0]["dest"], "0.0.0.0/0")
|
|
self.assertEqual(ip[0]["vrf"], "CUST_A")
|
|
|
|
v6 = normalize_ipv6_route(
|
|
raw_text=_IPV6_ROUTE,
|
|
command="show ipv6 forwarding route vrf CUST_B",
|
|
params={"vrf": "CUST_B"},
|
|
)
|
|
self.assertGreaterEqual(len(v6), 1)
|
|
self.assertTrue(any(r["dest"].startswith("2001:db8") for r in v6))
|
|
|
|
pw = normalize_l2vpn_pw(raw_text=_L2VPN_PW, command="show l2vpn forwardinfo")
|
|
self.assertEqual(len(pw), 3)
|
|
self.assertEqual(pw[0]["state"].upper(), "UP")
|
|
self.assertEqual(pw[1]["state"].upper(), "DOWN")
|
|
|
|
def test_l2vpn_pw_detail(self) -> None:
|
|
rows = normalize_l2vpn_pw_detail(
|
|
raw_text=_L2VPN_PW_DETAIL,
|
|
vendor="zte",
|
|
device_type="zte_zxros",
|
|
command="show l2vpn forwardinfo detail",
|
|
)
|
|
self.assertEqual(len(rows), 2)
|
|
by = {r["pw_name"]: r for r in rows}
|
|
self.assertEqual(by["pw19001"]["vc_status"].upper(), "UP")
|
|
self.assertEqual(by["pw19001"]["remote_status"], "ALLOK")
|
|
self.assertEqual(by["pw19001"]["vcid"], "19001")
|
|
self.assertEqual(by["pw19001"]["peer"], "10.0.0.6")
|
|
self.assertIn("demo-vpls-1", by["pw19001"]["service_instance"])
|
|
self.assertEqual(by["pw19011"]["vc_status"].upper(), "DOWN")
|
|
self.assertEqual(by["pw19011"]["vccv_cv"], "LSP|BFD_BASIC_HEAD")
|
|
|
|
fsm_rows = apply_rule(
|
|
platform="zte_zxros",
|
|
rule_key="zte_zxros_show_l2vpn_forwardinfo_detail",
|
|
text=_L2VPN_PW_DETAIL,
|
|
)
|
|
self.assertGreaterEqual(len(fsm_rows), 2)
|
|
|
|
brief = match_command(
|
|
vendor_key="zte", command="show l2vpn forwardinfo detail | exclude Tunnel"
|
|
)
|
|
self.assertIsNotNone(brief)
|
|
assert brief is not None
|
|
self.assertEqual(brief.profile.profile_id, "zte.l2vpn_pw_detail")
|
|
no_detail = match_command(vendor_key="zte", command="show l2vpn forwardinfo | one-line")
|
|
self.assertIsNotNone(no_detail)
|
|
assert no_detail is not None
|
|
self.assertEqual(no_detail.profile.profile_id, "zte.l2vpn_pw")
|
|
|
|
@unittest.skipUnless(
|
|
_L2VPN_PW_DETAIL_SAMPLE.is_file(),
|
|
"test/show-zte/show-l2vpn-forwarding-detail not present",
|
|
)
|
|
def test_l2vpn_pw_detail_real_sample(self) -> None:
|
|
raw = _L2VPN_PW_DETAIL_SAMPLE.read_text(encoding="utf-8", errors="replace")
|
|
rows = normalize_l2vpn_pw_detail(
|
|
raw_text=raw,
|
|
vendor="zte",
|
|
device_type="zte_zxros",
|
|
command="show l2vpn forwardinfo detail",
|
|
)
|
|
self.assertEqual(len(rows), 6)
|
|
self.assertTrue(all(r["vc_status"].upper() == "UP" for r in rows))
|
|
self.assertEqual(rows[0]["pw_name"], "pw19001")
|
|
self.assertEqual(rows[-1]["pw_name"], "pw1230002")
|
|
|
|
def test_profiles_and_expand(self) -> None:
|
|
for mid in (
|
|
"ospf_neighbor",
|
|
"vrrp",
|
|
"optical_brief",
|
|
"bgp_route",
|
|
"ip_route",
|
|
"ipv6_route",
|
|
"l2vpn_pw",
|
|
"l2vpn_pw_detail",
|
|
"l2vpn_mac",
|
|
"evpn_mac",
|
|
"interface_detail",
|
|
):
|
|
self.assertIn(mid, metric_field_map())
|
|
|
|
self.assertIsNotNone(get_profile("zte.bgp_ipv6_summary"))
|
|
self.assertIsNotNone(get_profile("zte.bgp_vpnv4_vrf_summary"))
|
|
self.assertIsNotNone(get_profile("zte.ospf_neighbor"))
|
|
self.assertIsNotNone(get_profile("zte.interface_detail"))
|
|
self.assertIsNotNone(get_profile("zte.l2vpn_pw_detail"))
|
|
self.assertIsNotNone(get_profile("zte.bgp_vpnv6_neighbor_in"))
|
|
self.assertIsNotNone(get_profile("zte.bgp_vpnv6_neighbor_out"))
|
|
|
|
p = get_profile("zte.bgp_vpnv4_vrf_neighbor_in")
|
|
assert p is not None
|
|
pairs = expand_from_bindings(
|
|
profile=p,
|
|
bindings=[{"vrf": "CUST_A", "neighbor": "10.0.0.1"}],
|
|
)
|
|
self.assertEqual(len(pairs), 1)
|
|
cmd, params = pairs[0]
|
|
self.assertIn("vrf CUST_A", cmd)
|
|
self.assertIn("10.0.0.1", cmd)
|
|
hit = match_command(vendor_key="zte", command=cmd)
|
|
self.assertIsNotNone(hit)
|
|
assert hit is not None
|
|
self.assertEqual(hit.params.get("vrf"), "CUST_A")
|
|
self.assertEqual(hit.params.get("neighbor"), "10.0.0.1")
|
|
|
|
def test_interface_detail_and_vpnv6_neighbor(self) -> None:
|
|
from netx_api.biz_state.parsers.zte import normalize_interface_detail
|
|
|
|
sample = """
|
|
gei-0/0/0/1 is up, ifindex: 100
|
|
Description: uplink-a
|
|
The port is optical
|
|
Negotiation force
|
|
BW 1 Gbit/s
|
|
IP MTU 1500 bytes
|
|
MTU 1600 bytes
|
|
MPLS MTU 1550 bytes
|
|
Rate period : 120 s
|
|
Input : 100 bit/s 1 packet/s
|
|
Output : 200 bit/s 2 packet/s
|
|
Peak rate:
|
|
Input : 0 bit/s peak time N/A
|
|
Output : 300 bit/s peak time N/A
|
|
Intf utilization: input 1% output 2%
|
|
gei-0/0/0/2 is administratively down, ifindex: 101
|
|
Description: spare
|
|
BW 1 Gbit/s
|
|
IP MTU 8978 bytes
|
|
MTU 9000 bytes
|
|
IPv6 MTU 8978 bytes
|
|
MPLS MTU 8978 bytes
|
|
Rate period : 120 s
|
|
Input : 0 bit/s 0 packet/s
|
|
Output : 0 bit/s 0 packet/s
|
|
Intf utilization: input 0% output 0%
|
|
"""
|
|
rows = normalize_interface_detail(
|
|
raw_text=sample,
|
|
vendor="zte",
|
|
device_type="zte_zxros",
|
|
command="show interface",
|
|
)
|
|
self.assertEqual(len(rows), 2)
|
|
self.assertEqual(rows[0]["port_status"], "up")
|
|
self.assertEqual(rows[0]["input_bps"], "100")
|
|
self.assertEqual(rows[0]["out_util"], "2")
|
|
self.assertEqual(rows[0]["ip_mtu"], "1500")
|
|
self.assertEqual(rows[0]["mtu"], "1600")
|
|
self.assertEqual(rows[0]["mpls_mtu"], "1550")
|
|
self.assertEqual(rows[0]["ipv6_mtu"], "")
|
|
self.assertEqual(rows[1]["port_status"], "admin-down")
|
|
self.assertEqual(rows[1]["ip_mtu"], "8978")
|
|
self.assertEqual(rows[1]["mtu"], "9000")
|
|
self.assertEqual(rows[1]["ipv6_mtu"], "8978")
|
|
self.assertEqual(rows[1]["mpls_mtu"], "8978")
|
|
|
|
# No IPv6 MTU on second iface — must not inherit from previous Filldown.
|
|
leak_sample = """
|
|
gei-0/0/0/1 is up, ifindex: 1
|
|
IP MTU 1500 bytes
|
|
MTU 1600 bytes
|
|
IPv6 MTU 8978 bytes
|
|
MPLS MTU 1550 bytes
|
|
Rate period : 120 s
|
|
Input : 1 bit/s
|
|
Output : 2 bit/s
|
|
Intf utilization: input 1% output 2%
|
|
gei-0/0/0/2 is up, ifindex: 2
|
|
IP MTU 1500 bytes
|
|
MTU 1600 bytes
|
|
MPLS MTU 1550 bytes
|
|
Rate period : 120 s
|
|
Input : 0 bit/s
|
|
Output : 0 bit/s
|
|
Intf utilization: input 0% output 0%
|
|
"""
|
|
leak_rows = normalize_interface_detail(
|
|
raw_text=leak_sample,
|
|
vendor="zte",
|
|
device_type="zte_zxros",
|
|
command="show interface",
|
|
)
|
|
self.assertEqual(len(leak_rows), 2)
|
|
self.assertEqual(leak_rows[0]["ipv6_mtu"], "8978")
|
|
self.assertEqual(leak_rows[1]["ipv6_mtu"], "")
|
|
self.assertEqual(leak_rows[1]["ip_mtu"], "1500")
|
|
self.assertEqual(leak_rows[1]["mpls_mtu"], "1550")
|
|
|
|
cmd = (
|
|
"show interface | include ifindex|BW|The port is|MTU|Negotiation|"
|
|
"Description|Current|Rate|Peak|Input|Output|utilization"
|
|
)
|
|
hit = match_command(vendor_key="zte", command=cmd)
|
|
self.assertIsNotNone(hit)
|
|
assert hit is not None
|
|
self.assertEqual(hit.profile.profile_id, "zte.interface_detail")
|
|
field_names = {f.name for f in hit.profile.fields}
|
|
self.assertTrue({"ip_mtu", "mtu", "mpls_mtu", "ipv6_mtu"} <= field_names)
|
|
|
|
hit6 = match_command(
|
|
vendor_key="zte",
|
|
command="show bgp vpnv6 unicast neighbor in FC00:1::1 | one-line",
|
|
)
|
|
self.assertIsNotNone(hit6)
|
|
assert hit6 is not None
|
|
self.assertEqual(hit6.profile.profile_id, "zte.bgp_vpnv6_neighbor_in")
|
|
self.assertEqual(hit6.params.get("neighbor"), "FC00:1::1")
|
|
|
|
routes = normalize_bgp_route(
|
|
raw_text=_BGP_ROUTE_IN,
|
|
command="show bgp vpnv6 unicast neighbor in FC00:1::1",
|
|
params={"neighbor": "FC00:1::1", "direction": "in", "afi": "vpnv6"},
|
|
)
|
|
self.assertGreaterEqual(len(routes), 1)
|
|
self.assertEqual(routes[0]["afi"], "vpnv6")
|
|
self.assertEqual(routes[0]["direction"], "in")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|