diff --git a/device-discovery/custom_napalm/brocade_netiron.py b/device-discovery/custom_napalm/brocade_netiron.py index 3dc4d6d6..c1cbc38a 100644 --- a/device-discovery/custom_napalm/brocade_netiron.py +++ b/device-discovery/custom_napalm/brocade_netiron.py @@ -354,6 +354,65 @@ def _netiron_aggregate_to_switchport(per_port: dict) -> SwitchportInfo: # --------------------------------------------------------------------------- +# "show vrf" rows: name, default RD, A|A|A status flags, route count, then +# space-separated member interfaces wrapping onto indented continuations. +_NETIRON_VRF_ROW_RE = re.compile( + r"^(?P\S+)\s+(?P\S+(?:\s+[Ss]et)?)\s+[ADI](?:\s*\|\s*[ADI-]?){2}\s+" + r"(?P\d+)\s*(?P.*)$" +) +# Member tokens in the Interfaces column: ve150, e1/5, eth 2/3, lag5, +# loopback1, po10 (case-insensitive). +_NETIRON_VRF_MEMBER_RE = re.compile( + r"\b(e(?:th(?:ernet)?)?\s?\d+/\d+|ve\s?\d+|lag\s?\d+|loopback\s?\d+|po\s?\d+)\b", + re.IGNORECASE, +) +# Ethernet member shorthand ("e1/5", "eth 2/3") → bare slot/port key used +# by the canonical-name map. +_NETIRON_VRF_ETH_RE = re.compile(r"^e(?:th(?:ernet)?)?\s?(\d+/\d+)$", re.IGNORECASE) +# RD column sentinels NetIron prints when no RD is configured. The row +# regex's rd group accepts an optional trailing "Set" token so the +# two-word "Not Set" form reaches this check intact. +_NETIRON_RD_UNSET = frozenset( + {"(null)", "null", "-", "n/a", "none", "not set", "notset"} +) + + +def _netiron_parse_show_vrf(raw: str) -> dict[str, tuple[str, list[str]]]: + """Parse ``show vrf`` into vrf name → (rd, raw member tokens).""" + out: dict[str, tuple[str, list[str]]] = {} + current: str | None = None + for line in raw.splitlines(): + if not line.strip(): + continue + m = _NETIRON_VRF_ROW_RE.match(line) + if m: + current = m.group("name") + rd = m.group("rd").strip() + if rd.lower() in _NETIRON_RD_UNSET: + rd = "" + members = _NETIRON_VRF_MEMBER_RE.findall(m.group("ifaces")) + out[current] = (rd, members) + continue + if current is not None and line[:1].isspace(): + rd, members = out[current] + members.extend(_NETIRON_VRF_MEMBER_RE.findall(line)) + out[current] = (rd, members) + else: + current = None + return out + + +def _netiron_canonical_member(token: str, canonical_map: dict[str, str]) -> str: + """Canonicalise a show-vrf member token via the bare-id name map.""" + token = token.strip() + m = _NETIRON_VRF_ETH_RE.match(token) + if m: + bare = m.group(1) + else: + bare = token.replace(" ", "").lower() + return canonical_map.get(bare, token) + + class NetIronDriver(_napalm_base.NetworkDriver): """Brocade/Extreme NetIron NAPALM driver (read-only subset for device-discovery).""" @@ -719,3 +778,47 @@ def _netiron_canonical_name_map(self) -> dict[str, str]: continue out[f"{prefix.lower()}{m.group(2)}"] = full return out + + def get_network_instances(self, name: str = "") -> dict: + """ + Return network instances (NetIron VRFs), NAPALM OC shape. + + Parsed driver-locally from ``show vrf`` (no ntc-template exists): + one row per VRF with the default RD and a space-separated member + interface list that may wrap onto indented continuation lines. + Member tokens (``ve150``, ``e1/5``) are canonicalised through the + same bare-id map get_interfaces_vlans() uses so they join the + canonical names get_interfaces() emits (``Ve150``, + ``GigabitEthernet1/5``). The global routing table is seeded as + ``default-vrf`` (DEFAULT_INSTANCE, empty membership). + + NOTE: built from the vendor-documented output format; not yet + validated against a live NetIron device. + """ + instances: dict = { + "default-vrf": { + "name": "default-vrf", + "type": "DEFAULT_INSTANCE", + "state": {"route_distinguisher": ""}, + "interfaces": {"interface": {}}, + }, + } + raw = self.device.send_command("show vrf") + rows = _netiron_parse_show_vrf(raw or "") + canonical_map = self._netiron_canonical_name_map() if rows else {} + for vrf_name, (rd, members) in rows.items(): + # Never let a row overwrite the seeded DEFAULT_INSTANCE. + if vrf_name == "default-vrf": + continue + interfaces = { + _netiron_canonical_member(m, canonical_map): {} for m in members + } + instances[vrf_name] = { + "name": vrf_name, + "type": "L3VRF", + "state": {"route_distinguisher": rd}, + "interfaces": {"interface": interfaces}, + } + if name: + return {name: instances[name]} if name in instances else {} + return instances diff --git a/device-discovery/custom_napalm/dell_ftos.py b/device-discovery/custom_napalm/dell_ftos.py index 5beff95b..0be2be35 100644 --- a/device-discovery/custom_napalm/dell_ftos.py +++ b/device-discovery/custom_napalm/dell_ftos.py @@ -715,3 +715,200 @@ def get_interfaces_vlans(self) -> dict[str, dict]: info = _ftos_row_to_switchport_info(row) result[ifname] = classify_switchport(info) return result + + def get_network_instances(self, name: str = "") -> dict: + """ + Return network instances (OS9 VRFs), NAPALM OC shape. + + Parsed driver-locally from ``show ip vrf`` (no ntc-template + exists): one row per VRF with abbreviated comma-separated member + interfaces that may wrap onto indented continuation lines and + use trailing-number ranges (``Gi 1/3-1/5``). Member abbreviations + expand to the full template forms (``Gi 1/2`` → + ``GigabitEthernet 1/2``) because that is how this driver's + get_interfaces()/get_interfaces_ip() key interfaces — the + VRF→IP join is by exact name. The VRF named + ``default`` (id 0) is the global routing table + (DEFAULT_INSTANCE, empty membership); the ``management`` VRF + (id 511) is a real VRF and is kept. OS9 keeps the RD in + per-VRF BGP config — not collected in this pass. + + NOTE: built from the vendor-documented output format; not yet + validated against a live OS9 device. + """ + instances: dict = { + "default": { + "name": "default", + "type": "DEFAULT_INSTANCE", + "state": {"route_distinguisher": ""}, + "interfaces": {"interface": {}}, + }, + } + raw = self.device.send_command("show ip vrf") + for vrf_name, members in _ftos_parse_show_ip_vrf(raw or "").items(): + # Never let a row overwrite the seeded DEFAULT_INSTANCE. + if vrf_name == "default": + continue + instances[vrf_name] = { + "name": vrf_name, + "type": "L3VRF", + "state": {"route_distinguisher": ""}, + "interfaces": {"interface": {m: {} for m in members}}, + } + if name: + return {name: instances[name]} if name in instances else {} + return instances + + +# "show ip vrf" rows: name, numeric VRF id, comma-separated abbreviated +# member interfaces that may wrap onto indented continuation lines. The +# header ("VRF-Name VRF-ID Interfaces") never matches — its second +# column is not numeric. +_FTOS_VRF_ROW_RE = re.compile(r"^\s*(?P\S+)\s+(?P\d+)\b(?P.*)$") +# Abbreviated member groups: one interface abbreviation followed by a +# comma-compressed number list — "Gi 1/2", "Te 1/3-1/5", and Dell's +# compressed forms "Te 0/14,16-17" / "Fo 0/48,52,56,60" where the numbers +# after the first inherit its slot head. A comma followed by a space starts +# a new group ("Gi 1/2, Vl 100"), matching the documented column layout. +_FTOS_VRF_GROUP_RE = re.compile( + r"\b([A-Z][a-zA-Z]{0,2}) (\d[\d/.\-]*(?:,[\d/.\-]+)*)" +) + + +def _ftos_member_tokens(text: str) -> list[str]: + """ + Expand abbreviated member groups into one " " token each. + + Within a group's comma list, items carrying a "/" set the slot head; + bare numbers and bare ranges inherit the most recent head + ("Te 0/14,16-17" → "Te 0/14", "Te 0/16-17"). + """ + tokens: list[str] = [] + for abbrev, numlist in _FTOS_VRF_GROUP_RE.findall(text): + head = "" + for item in numlist.split(","): + item = item.strip() + if not item: + continue + if "/" in item: + # Derive the inheritable head from the LEFT side of a + # range so "1/3-1/5" yields head "1", not "1/3-1". + left = item.partition("-")[0] + if "/" in left: + head = left.rsplit("/", 1)[0] + tokens.append(f"{abbrev} {item}") + elif head: + tokens.append(f"{abbrev} {head}/{item}") + else: + tokens.append(f"{abbrev} {item}") + return tokens +# show ip vrf abbreviations → the full interface names the dell_force10 +# ntc-templates emit (and therefore how get_interfaces()/get_interfaces_ip() +# key interfaces — the VRF→IP join is by exact name). Unknown abbreviations +# pass through unexpanded and simply never join. +_FTOS_ABBREV_TO_FULL = { + "Fa": "FastEthernet", + "Gi": "GigabitEthernet", + "Te": "TenGigabitEthernet", + # OS9 displays the higher speeds lowercase-first ("fortyGigE 0/48 + # is up") and the getters key with device casing. + "Tf": "twentyFiveGigE", + "Fo": "fortyGigE", + "Fi": "fiftyGigE", + "Hu": "hundredGigE", + "Ma": "ManagementEthernet", + "Vl": "Vlan", + "Po": "Port-channel", + "Lo": "Loopback", + "Tu": "Tunnel", +} + + +def _ftos_expand_member_range(token: str) -> list[str]: + """ + Expand a trailing-number member range ("Gi 1/3-1/5" → Gi 1/3..1/5). + + Tokens without a range (or with an unparseable or cross-slot one — + "Gi 1/3-2/5") pass through unchanged — an unexpanded token simply + never joins an interface name, which is safer than guessing. + """ + if "-" not in token: + return [token] + prefix, _, value = token.partition(" ") + left, _, right = value.partition("-") + left_head, _, left_last = left.rpartition("/") + right_head, _, right_last = right.rpartition("/") + if right_head and right_head != left_head: + # Cross-slot range: expanding only the trailing number would + # fabricate interface names that may belong to other VRFs. + return [token] + try: + start, end = int(left_last), int(right_last) + except ValueError: + return [token] + if end < start or end - start > 512: + return [token] + head = f"{left_head}/" if left_head else "" + return [f"{prefix} {head}{n}" for n in range(start, end + 1)] + + +def _ftos_expand_member_name(token: str) -> str: + """Expand "Gi 1/2" to the full "GigabitEthernet 1/2" template form.""" + abbrev, _, rest = token.partition(" ") + full = _FTOS_ABBREV_TO_FULL.get(abbrev) + return f"{full} {rest}" if full and rest else token + + +# Wrapped member lines align under the Interfaces column (far right of the +# 34-char name + id columns); VRF rows start at the left margin. Requiring +# deep indentation keeps a short uppercase VRF name row ("RED 1 Gi 1/7") +# from ever being mistaken for a continuation of the previous VRF. +_FTOS_CONTINUATION_MIN_INDENT = 8 + + +def _ftos_is_member_continuation(line: str) -> bool: + """ + True when a line holds only wrapped member tokens (no name/id columns). + + A continuation like ``Te 1/20`` would otherwise satisfy the row regex + ("Te" as the name, "1" as the id) — but unlike a real row, a + continuation is deeply indented under the Interfaces column AND + removing every member token (plus separators) leaves nothing behind. + """ + indent = len(line) - len(line.lstrip()) + if indent < _FTOS_CONTINUATION_MIN_INDENT: + return False + residue = _FTOS_VRF_GROUP_RE.sub("", line).replace(",", "").strip() + return not residue + + +def _ftos_parse_show_ip_vrf(raw: str) -> dict[str, list[str]]: + """Parse ``show ip vrf`` into vrf name → expanded member interface names.""" + members_by_vrf: dict[str, list[str]] = {} + current: str | None = None + for line in raw.splitlines(): + if not line.strip(): + continue + if _ftos_is_member_continuation(line): + # Continuation with no owning row (e.g. orphaned by a paging + # header): drop it rather than letting the row regex turn it + # into a phantom VRF named after an abbreviation. + if current is None: + continue + member_text = line + else: + m = _FTOS_VRF_ROW_RE.match(line) + if not m: + # Header / footer / unparseable row: reset so a later + # orphaned continuation can't attach to a stale VRF. + current = None + continue + current = m.group("name") + members_by_vrf.setdefault(current, []) + member_text = m.group("rest") + for token in _ftos_member_tokens(member_text): + members_by_vrf[current].extend( + _ftos_expand_member_name(t) + for t in _ftos_expand_member_range(token) + ) + return members_by_vrf diff --git a/device-discovery/custom_napalm/extreme_slx.py b/device-discovery/custom_napalm/extreme_slx.py index a9934fc4..9508adf5 100644 --- a/device-discovery/custom_napalm/extreme_slx.py +++ b/device-discovery/custom_napalm/extreme_slx.py @@ -257,6 +257,67 @@ def _parse_intf_hw_addresses(text: str) -> dict[str, str]: r"^\s*(Management\s+\S+)\s+(\d[\d.]+(?:/\d+)?)\s", re.IGNORECASE, ) +# Same pre-stripped Management rows, but capturing the Vrf column so VRF +# discovery doesn't lose mgmt-vrf membership (or the VRF itself when it +# appears only on Management interfaces). +_MGMT_VRF_RE = re.compile( + r"^\s*(Management\s+\S+)\s+\S+\s+(\S+)\s", + re.IGNORECASE, +) +# Regex fallback for VRF discovery when the ntc-template fails entirely — +# the interface + Vrf columns of the same interface-type set +# _INTF_IP_FALLBACK_RE covers, so a single unparseable row can't empty the +# whole VRF discovery result. Other row types (e.g. Tunnel) are skipped by +# design: interface discovery doesn't emit them either, so their VRF +# membership could never join an Interface/IP entity. +_INTF_VRF_FALLBACK_RE = re.compile( + r"^\s*((?:Ethernet|Management|Port-channel|Loopback|Ve)\s+\S+)\s+\S+\s+(\S+)\s", + re.IGNORECASE, +) + + +def _slx_vrf_memberships(output: str) -> list[tuple[str, str]]: + """ + Extract (interface, vrf) pairs from ``show ip interface brief`` output. + + Management rows are pre-stripped before template parsing (the + ntc-template error-exits on them) and recovered with _MGMT_VRF_RE; + when the template fails on any other row, the whole output falls back + to _INTF_VRF_FALLBACK_RE — the same contract get_interfaces_ip() + follows for addresses. + """ + memberships: list[tuple[str, str]] = [] + filtered = "\n".join( + line for line in output.splitlines() if not _MGMT_LINE_RE.match(line) + ) + try: + rows = parse_output( + platform="extreme_slxos", + command="show ip interface brief", + data=filtered, + ) + except (TextFSMError, ParsingException): + logger.warning( + "slxos: ntc-template failed for 'show ip interface brief'; " + "falling back to regex for VRF discovery", + exc_info=True, + ) + for line in output.splitlines(): + m = _INTF_VRF_FALLBACK_RE.match(line) + if m: + memberships.append((m.group(1).strip(), m.group(2).strip())) + return memberships + memberships.extend( + ((row.get("interface") or "").strip(), (row.get("vrf") or "").strip()) + for row in rows + ) + # Recover the pre-stripped Management rows' Vrf column (the fallback + # branch above already covers them). + for line in output.splitlines(): + m = _MGMT_VRF_RE.match(line) + if m: + memberships.append((m.group(1).strip(), m.group(2).strip())) + return memberships # Regex fallback covering all known interface types, used when ntc-template # parse_output() fails entirely so no interface address is silently dropped. _INTF_IP_FALLBACK_RE = re.compile( @@ -696,3 +757,49 @@ def get_interfaces_vlans(self) -> dict[str, dict]: info = _slx_aggregate_to_switchport(data) result[port] = classify_switchport(info) return result + + def get_network_instances(self, name: str = "") -> dict: + """ + Return network instances (SLX-OS VRFs), NAPALM OC shape. + + Derived from the Vrf column of ``show ip interface brief`` — the + same template-parsed rows get_interfaces_ip() consumes, so member + names join exactly. ``default-vrf`` is the global routing table + (DEFAULT_INSTANCE, empty membership); ``mgmt-vrf`` is a real VRF + and is kept. Management interfaces are pre-stripped before + template parsing (the ntc-template error-exits on those rows) + and recovered with a dedicated regex — mirroring how + get_interfaces_ip() recovers their addresses — so mgmt-vrf + survives even when it appears only on Management interfaces. + Limitations: enumeration is membership-derived (an interface-less + VRF does not appear) and route distinguishers are not collected + (they live in ``show vrf detail``). + """ + instances: dict = { + "default-vrf": { + "name": "default-vrf", + "type": "DEFAULT_INSTANCE", + "state": {"route_distinguisher": ""}, + "interfaces": {"interface": {}}, + }, + } + output = self.device.send_command("show ip interface brief") + memberships: list[tuple[str, str]] = [] + if output and output.strip(): + memberships = _slx_vrf_memberships(output) + for ifname, vrf_name in memberships: + # default-vrf rows belong to the seeded DEFAULT_INSTANCE. + if not vrf_name or not ifname or vrf_name == "default-vrf": + continue + instances.setdefault( + vrf_name, + { + "name": vrf_name, + "type": "L3VRF", + "state": {"route_distinguisher": ""}, + "interfaces": {"interface": {}}, + }, + )["interfaces"]["interface"][ifname] = {} + if name: + return {name: instances[name]} if name in instances else {} + return instances diff --git a/device-discovery/custom_napalm/extreme_vsp.py b/device-discovery/custom_napalm/extreme_vsp.py index 7af7cb9b..b81a5876 100644 --- a/device-discovery/custom_napalm/extreme_vsp.py +++ b/device-discovery/custom_napalm/extreme_vsp.py @@ -354,6 +354,27 @@ def _vsp_row_to_switchport_info(row: dict) -> SwitchportInfo: # --------------------------------------------------------------------------- +# "show ip vrf" rows: name + numeric VRF id (the header's second column is +# "NAME"/"ID" text and footer lines have non-numeric second tokens, so +# neither matches). +_VSP_VRF_ROW_RE = re.compile(r"^(?P\S+)\s+(?P\d+)\b") + + +def _vsp_parse_show_ip_vrf(raw: str) -> list[str]: + """Return VRF names from ``show ip vrf``, in display order.""" + names: list[str] = [] + for line in raw.splitlines(): + stripped = line.strip() + if not stripped or stripped.startswith(("=", "-")): + continue + # Match against the stripped line so indented VRF rows (releases + # vary in table padding) are not silently skipped. + m = _VSP_VRF_ROW_RE.match(stripped) + if m and m.group("name") not in names: + names.append(m.group("name")) + return names + + class VSPDriver(_napalm_base.NetworkDriver): """Avaya/Extreme VSP NAPALM driver (read-only subset for device-discovery).""" @@ -551,3 +572,51 @@ def get_interfaces_vlans(self) -> dict[str, dict]: info = _vsp_row_to_switchport_info(row) result[port] = classify_switchport(info) return result + + def get_network_instances(self, name: str = "") -> dict: + """ + Return network instances (VOSS VRFs / L3VSNs), NAPALM OC shape. + + ``show ip vrf`` enumerates the VRFs (parsed driver-locally — no + ntc-template exists); one ``show ip interface vrf `` per + VRF lists its L3 interfaces, parsed with the same row shape + get_interfaces_ip() uses so member names join exactly. + ``GlobalRouter`` (VRF id 0) is the global routing table + (DEFAULT_INSTANCE, empty membership); the segmented management + instance (id 512) is a real VRF and is kept. VOSS keeps the RD + in per-VRF IP-VPN config — not collected in this pass. + + NOTE: built from the vendor-documented output format; not yet + validated against a live VOSS device. + """ + instances: dict = { + "GlobalRouter": { + "name": "GlobalRouter", + "type": "DEFAULT_INSTANCE", + "state": {"route_distinguisher": ""}, + "interfaces": {"interface": {}}, + }, + } + raw = self.device.send_command("show ip vrf") + vrf_names = _vsp_parse_show_ip_vrf(raw or "") + if name: + # Apply the name filter before the per-VRF membership commands + # so a single-VRF request doesn't pay N+1 CLI round-trips. + vrf_names = [v for v in vrf_names if v == name] + for vrf_name in vrf_names: + # Never let a row overwrite the seeded DEFAULT_INSTANCE. + if vrf_name == "GlobalRouter": + continue + members_raw = self.device.send_command( + f"show ip interface vrf {vrf_name}" + ) + members = _parse_interfaces_ip(members_raw or "") + instances[vrf_name] = { + "name": vrf_name, + "type": "L3VRF", + "state": {"route_distinguisher": ""}, + "interfaces": {"interface": {m: {} for m in members}}, + } + if name: + return {name: instances[name]} if name in instances else {} + return instances diff --git a/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/no_vrfs/expected_result.json b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/no_vrfs/expected_result.json new file mode 100644 index 00000000..a197f3f4 --- /dev/null +++ b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/no_vrfs/expected_result.json @@ -0,0 +1,12 @@ +{ + "default-vrf": { + "interfaces": { + "interface": {} + }, + "name": "default-vrf", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + } +} diff --git a/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/expected_result.json b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/expected_result.json new file mode 100644 index 00000000..a7d1c894 --- /dev/null +++ b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/expected_result.json @@ -0,0 +1,50 @@ +{ + "blue": { + "interfaces": { + "interface": { + "GigabitEthernet1/2": {}, + "Loopback1": {} + } + }, + "name": "blue", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "default-vrf": { + "interfaces": { + "interface": {} + }, + "name": "default-vrf", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + }, + "green": { + "interfaces": { + "interface": { + "GigabitEthernet1/1": {}, + "Ve2": {} + } + }, + "name": "green", + "state": { + "route_distinguisher": "1:1" + }, + "type": "L3VRF" + }, + "mgmt": { + "interfaces": { + "interface": { + "Ve9": {} + } + }, + "name": "mgmt", + "state": { + "route_distinguisher": "65000:99" + }, + "type": "L3VRF" + } +} diff --git a/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/show_interfaces.txt b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/show_interfaces.txt new file mode 100644 index 00000000..224600a3 --- /dev/null +++ b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/show_interfaces.txt @@ -0,0 +1,73 @@ +GigabitEthernet1/1 is up, line protocol is up + STP Root Guard is disabled, STP BPDU Guard is disabled + Hardware is GigabitEthernet, address is 0024.38a5.1c00 (bia 0024.38a5.1c00) + Configured speed auto, actual 1Gbit, configured duplex fdx, actual fdx + Member of Control VLAN 4095, 10 L2 VLAN(S) (tagged), port is in tagged mode, port state is Forwarding + STP configured to ON, Priority is level0, flow control enabled + Priority force disabled, Drop precedence level 0, Drop precedence force disabled + dhcp-snooping-trust configured to OFF + mirror disabled, monitor disabled + LACP BPDU Forwarding:Disabled + LLDP BPDU Forwarding:Disabled + Not member of any active trunks + Not member of any configured trunks + Port name is uplink-core + Port is not enabled to receive all vlan packets for pbr + MTU 9216 bytes, encapsulation ethernet + Openflow: Disabled, Openflow Index 1 + Cluster L2 protocol forwarding enabled + 300 second input rate: 100000 bits/sec, 100 packets/sec, 0.01% utilization + 300 second output rate: 200000 bits/sec, 200 packets/sec, 0.02% utilization + 1000 packets input, 128000 bytes, 0 no buffer + Received 10 broadcasts, 5 multicasts, 985 unicasts + 0 input errors, 0 CRC, 0 frame, 0 ignored + 0 runts, 0 giants + NP received 1000 packets, Sent to TM 1000 packets + NP Ingress dropped 0 packets + 2000 packets output, 256000 bytes, 0 underruns + Transmitted 15 broadcasts, 10 multicasts, 1975 unicasts + 0 output errors, 0 collisions + NP transmitted 2000 packets, Received from TM 2000 packets +GigabitEthernet1/2 is disabled, line protocol is down + STP Root Guard is disabled, STP BPDU Guard is disabled + Hardware is GigabitEthernet, address is 0024.38a5.1c01 (bia 0024.38a5.1c01) + Configured speed auto, actual unknown, configured duplex fdx, actual unknown + Member of Control VLAN 4095, VLAN 1 (untagged), port is in untagged mode, port state is Disabled + STP configured to ON, Priority is level0, flow control enabled + Priority force disabled, Drop precedence level 0, Drop precedence force disabled + dhcp-snooping-trust configured to OFF + mirror disabled, monitor disabled + LACP BPDU Forwarding:Disabled + LLDP BPDU Forwarding:Disabled + Not member of any active trunks + Not member of any configured trunks + No port name + Port is not enabled to receive all vlan packets for pbr + MTU 9216 bytes, encapsulation ethernet + Openflow: Disabled, Openflow Index 2 + Cluster L2 protocol forwarding enabled + 300 second input rate: 0 bits/sec, 0 packets/sec, 0.00% utilization + 300 second output rate: 0 bits/sec, 0 packets/sec, 0.00% utilization + 0 packets input, 0 bytes, 0 no buffer + Received 0 broadcasts, 0 multicasts, 0 unicasts + 0 input errors, 0 CRC, 0 frame, 0 ignored + 0 runts, 0 giants + NP received 0 packets, Sent to TM 0 packets + NP Ingress dropped 0 packets + 0 packets output, 0 bytes, 0 underruns + Transmitted 0 broadcasts, 0 multicasts, 0 unicasts + 0 output errors, 0 collisions + NP transmitted 0 packets, Received from TM 0 packets +Ve2 is up, line protocol is up + Type is Vlan (Vlan Id: 2) + Hardware is Virtual Ethernet, address is 0024.38a5.1c00 (bia 0024.38a5.1c00) + Port name is Main Ethernet VLAN + Vlan id: 2 + Internet address is 192.168.1.1/24, IP MTU 1500 bytes, encapsulation ethernet +Loopback1 is up, line protocol is up + Hardware is Loopback + Port name is MPLS Loopback + Internet address is 10.0.0.1/32, IP MTU 1500 bytes, encapsulation LOOPBACK +Ve9 is up, line protocol is up + Hardware is Virtual Ethernet, address is 0024.38a5.1c10 (bia 0024.38a5.1c10) + Internet address is 172.16.9.1/24, IP MTU 1500 bytes, encapsulation ethernet diff --git a/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/show_vrf.txt b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/show_vrf.txt new file mode 100644 index 00000000..24bb669a --- /dev/null +++ b/device-discovery/tests/custom_drivers/brocade_netiron/mock_data/test_get_network_instances/normal/show_vrf.txt @@ -0,0 +1,9 @@ +Total number of VRFs configured: 3 +Status Codes - A:active, D:deleted, I:inactive +Name Default RD vrf|v4|v6 Routes Interfaces +green 1:1 A | A| A 12 ve2 e1/1 +blue (null) A | A| A 5 e 1/2 + loopback1 +mgmt 65000:99 A | A| A 2 ve9 +Total number of IPv4 unicast route for all non-default VRF is 19 +Total number of IPv6 unicast route for all non-default VRF is 0 diff --git a/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/no_vrfs/expected_result.json b/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/no_vrfs/expected_result.json new file mode 100644 index 00000000..b67d8c24 --- /dev/null +++ b/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/no_vrfs/expected_result.json @@ -0,0 +1,12 @@ +{ + "default": { + "interfaces": { + "interface": {} + }, + "name": "default", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + } +} diff --git a/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/normal/expected_result.json b/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/normal/expected_result.json new file mode 100644 index 00000000..d0cefcec --- /dev/null +++ b/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/normal/expected_result.json @@ -0,0 +1,95 @@ +{ + "RED": { + "interfaces": { + "interface": { + "GigabitEthernet 1/7": {} + } + }, + "name": "RED", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "blue": { + "interfaces": { + "interface": { + "GigabitEthernet 1/2": {}, + "Vlan 100": {} + } + }, + "name": "blue", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "default": { + "interfaces": { + "interface": {} + }, + "name": "default", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + }, + "green": { + "interfaces": { + "interface": { + "TenGigabitEthernet 0/14": {}, + "TenGigabitEthernet 0/16": {}, + "TenGigabitEthernet 0/17": {}, + "fortyGigE 0/48": {}, + "fortyGigE 0/52": {} + } + }, + "name": "green", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "management": { + "interfaces": { + "interface": { + "ManagementEthernet 1/1": {} + } + }, + "name": "management", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "orange": { + "interfaces": { + "interface": { + "GigabitEthernet 2/3": {}, + "GigabitEthernet 2/4": {}, + "GigabitEthernet 2/5": {}, + "GigabitEthernet 2/7": {} + } + }, + "name": "orange", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "red": { + "interfaces": { + "interface": { + "GigabitEthernet 1/3": {}, + "GigabitEthernet 1/4": {}, + "GigabitEthernet 1/5": {}, + "Vlan 200": {} + } + }, + "name": "red", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + } +} diff --git a/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/normal/show_ip_vrf.txt b/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/normal/show_ip_vrf.txt new file mode 100644 index 00000000..f7916817 --- /dev/null +++ b/device-discovery/tests/custom_drivers/dell_ftos/mock_data/test_get_network_instances/normal/show_ip_vrf.txt @@ -0,0 +1,11 @@ + + VRF-Name VRF-ID Interfaces + default 0 Gi 1/1,Gi 1/10, + Te 1/20 + management 511 Ma 1/1 + blue 1 Gi 1/2,Vl 100 + red 2 Gi 1/3-1/5, + Vl 200 + RED 3 Gi 1/7 + green 4 Te 0/14,16-17,Fo 0/48,52 + orange 5 Gi 2/3-2/5,7 diff --git a/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/no_vrfs/expected_result.json b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/no_vrfs/expected_result.json new file mode 100644 index 00000000..a197f3f4 --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/no_vrfs/expected_result.json @@ -0,0 +1,12 @@ +{ + "default-vrf": { + "interfaces": { + "interface": {} + }, + "name": "default-vrf", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + } +} diff --git a/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/normal/expected_result.json b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/normal/expected_result.json new file mode 100644 index 00000000..4394d2e4 --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/normal/expected_result.json @@ -0,0 +1,79 @@ +{ + "ABC-PUBLIC-LEGACY": { + "interfaces": { + "interface": { + "Ve 734": {} + } + }, + "name": "ABC-PUBLIC-LEGACY", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "DEMO-VLT-LAB-01": { + "interfaces": { + "interface": { + "Ve 535": {}, + "Ve 543": {}, + "Ve 548": {} + } + }, + "name": "DEMO-VLT-LAB-01", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "DEMO-VLT-LAB-02": { + "interfaces": { + "interface": { + "Ve 544": {}, + "Ve 545": {}, + "Ve 546": {}, + "Ve 547": {} + } + }, + "name": "DEMO-VLT-LAB-02", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "MGMT": { + "interfaces": { + "interface": { + "Ve 10": {}, + "Ve 11": {}, + "Ve 4": {} + } + }, + "name": "MGMT", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "default-vrf": { + "interfaces": { + "interface": {} + }, + "name": "default-vrf", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + }, + "mgmt-vrf": { + "interfaces": { + "interface": { + "Management 0": {} + } + }, + "name": "mgmt-vrf", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + } +} diff --git a/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/normal/show_ip_interface_brief.txt b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/normal/show_ip_interface_brief.txt new file mode 100644 index 00000000..7f229001 --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/normal/show_ip_interface_brief.txt @@ -0,0 +1,27 @@ +Flags: I - Insight Enabled U - Unnumbered interface +Interface IP-Address Vrf Status Protocol +================== ========== ======================================== ==================== ======== +Port-channel 7 unassigned default-vrf up up +Port-channel 22 unassigned default-vrf up up +Port-channel 250(I) unassigned default-vrf up up +Loopback 1 172.16.128.1 default-vrf up up +Loopback 61 185.206.188.1 default-vrf up up +Loopback 62 185.209.116.1 default-vrf up up +Ethernet 0/1 172.16.128.44 default-vrf up up +Ethernet 0/2 unassigned default-vrf up up +Ethernet 0/8 unassigned default-vrf administratively down down +Ethernet 0/14 unassigned default-vrf administratively down down +Ethernet 0/15 172.16.128.34 default-vrf up up +Ethernet 0/125 unassigned default-vrf up up +Ve 4 10.17.17.1 MGMT up up +Ve 10 10.8.0.2 MGMT up up +Ve 11 10.8.1.1 MGMT up up +Ve 535 10.11.80.2 DEMO-VLT-LAB-01 up up +Ve 543 10.11.81.2 DEMO-VLT-LAB-01 up up +Ve 544 10.11.82.2 DEMO-VLT-LAB-02 up up +Ve 545 10.11.83.2 DEMO-VLT-LAB-02 up up +Ve 546 10.11.84.2 DEMO-VLT-LAB-02 up up +Ve 547 10.11.85.2 DEMO-VLT-LAB-02 up up +Ve 548 172.16.0.2 DEMO-VLT-LAB-01 up up +Ve 734 10.224.8.137 ABC-PUBLIC-LEGACY administratively down down +Management 0 10.255.255.1/24 mgmt-vrf up up diff --git a/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/template_fallback/expected_result.json b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/template_fallback/expected_result.json new file mode 100644 index 00000000..3afef1cd --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/template_fallback/expected_result.json @@ -0,0 +1,36 @@ +{ + "MGMT": { + "interfaces": { + "interface": { + "Ve 4": {} + } + }, + "name": "MGMT", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "default-vrf": { + "interfaces": { + "interface": {} + }, + "name": "default-vrf", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + }, + "mgmt-vrf": { + "interfaces": { + "interface": { + "Management 0": {} + } + }, + "name": "mgmt-vrf", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + } +} diff --git a/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/template_fallback/show_ip_interface_brief.txt b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/template_fallback/show_ip_interface_brief.txt new file mode 100644 index 00000000..20086af3 --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_slx/mock_data/test_get_network_instances/template_fallback/show_ip_interface_brief.txt @@ -0,0 +1,7 @@ +Flags: I - Insight Enabled U - Unnumbered interface +Interface IP-Address Vrf Status Protocol +================== ========== ======================================== ==================== ======== +Ethernet 0/1 172.16.128.44 default-vrf up up +Tunnel 7 10.50.0.1 CUST-TUN up up +Ve 4 10.17.17.1 MGMT up up +Management 0 10.255.255.1/24 mgmt-vrf up up diff --git a/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/no_vrfs/expected_result.json b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/no_vrfs/expected_result.json new file mode 100644 index 00000000..d565704b --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/no_vrfs/expected_result.json @@ -0,0 +1,12 @@ +{ + "GlobalRouter": { + "interfaces": { + "interface": {} + }, + "name": "GlobalRouter", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + } +} diff --git a/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/expected_result.json b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/expected_result.json new file mode 100644 index 00000000..dfff5af4 --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/expected_result.json @@ -0,0 +1,37 @@ +{ + "GlobalRouter": { + "interfaces": { + "interface": {} + }, + "name": "GlobalRouter", + "state": { + "route_distinguisher": "" + }, + "type": "DEFAULT_INSTANCE" + }, + "MgmtRouter": { + "interfaces": { + "interface": { + "Mgmt1": {} + } + }, + "name": "MgmtRouter", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + }, + "vrf-red": { + "interfaces": { + "interface": { + "Vlan200": {}, + "Vlan201": {} + } + }, + "name": "vrf-red", + "state": { + "route_distinguisher": "" + }, + "type": "L3VRF" + } +} diff --git a/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_interface_vrf_MgmtRouter.txt b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_interface_vrf_MgmtRouter.txt new file mode 100644 index 00000000..70d6f806 --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_interface_vrf_MgmtRouter.txt @@ -0,0 +1,7 @@ +========================================================================================= + IP Interface - VRF MgmtRouter +========================================================================================= +INTERFACE IP NET BCASTADDR REASM VLAN BROUTER + ADDRESS MASK FORMAT MAXSIZE ID BROUTER +----------------------------------------------------------------------------------------- +Mgmt1 172.16.5.2 255.255.255.0 ones 1500 0 false diff --git a/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_interface_vrf_vrf-red.txt b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_interface_vrf_vrf-red.txt new file mode 100644 index 00000000..67005d77 --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_interface_vrf_vrf-red.txt @@ -0,0 +1,8 @@ +========================================================================================= + IP Interface - VRF vrf-red +========================================================================================= +INTERFACE IP NET BCASTADDR REASM VLAN BROUTER + ADDRESS MASK FORMAT MAXSIZE ID PORT +----------------------------------------------------------------------------------------- +Vlan200 10.20.0.1 255.255.255.0 ones 1500 200 false +Vlan201 10.20.1.1 255.255.255.0 ones 1500 201 false diff --git a/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_vrf.txt b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_vrf.txt new file mode 100644 index 00000000..1692d41c --- /dev/null +++ b/device-discovery/tests/custom_drivers/extreme_vsp/mock_data/test_get_network_instances/normal/show_ip_vrf.txt @@ -0,0 +1,10 @@ +========================================================================================== + Vrf Information +========================================================================================== +VRF NAME VRF ID CONTEXT NAME CONTEXT ID ARP/IP/UDPFWD/RSMLT +------------------------------------------------------------------------------------------ +GlobalRouter 0 default 1 T/T/F/F +MgmtRouter 512 mgmt 2 T/T/F/F +vrf-red 2 red-ctx 3 T/T/F/F + +3 out of 3 Total Num of VRF Entries displayed. diff --git a/device-discovery/tests/test_runner_vrf_dispatch.py b/device-discovery/tests/test_runner_vrf_dispatch.py index 854fb1b4..909f3add 100644 --- a/device-discovery/tests/test_runner_vrf_dispatch.py +++ b/device-discovery/tests/test_runner_vrf_dispatch.py @@ -18,10 +18,14 @@ from napalm.base.base import NetworkDriver from custom_napalm.aruba_aoscx import AOSCXDriver +from custom_napalm.brocade_netiron import NetIronDriver from custom_napalm.cisco_viptela_ssh import ViptelaSSHDriver from custom_napalm.cumulus_linux import CumulusDriver +from custom_napalm.dell_ftos import FTOSDriver from custom_napalm.dell_sonic import SONiCDriver from custom_napalm.eos import EOSDriver +from custom_napalm.extreme_slx import SLXOSDriver +from custom_napalm.extreme_vsp import VSPDriver from custom_napalm.hp_comware import ComwareDriver from custom_napalm.huawei_vrp import VRPDriver from custom_napalm.ios import IOSDriver @@ -153,6 +157,10 @@ def test_collect_network_instances_swallows_not_implemented(caplog) -> None: pytest.param(ViptelaSSHDriver, "custom_napalm.", id="cisco_viptela_ssh"), pytest.param(PANOSDriver, "custom_napalm.", id="paloalto_panos"), pytest.param(PANOSSHDriver, "custom_napalm.", id="paloalto_panos_ssh"), + pytest.param(SLXOSDriver, "custom_napalm.", id="extreme_slx"), + pytest.param(FTOSDriver, "custom_napalm.", id="dell_ftos"), + pytest.param(NetIronDriver, "custom_napalm.", id="brocade_netiron"), + pytest.param(VSPDriver, "custom_napalm.", id="extreme_vsp"), ], ) def test_driver_implements_get_network_instances(