Skip to content

Commit

Permalink
Merge pull request #1923 from napalm-automation/issue-1922
Browse files Browse the repository at this point in the history
eos: refactor vrf parsing

closes #1919 and #1922
  • Loading branch information
bewing committed May 23, 2023
2 parents f1ce50e + f9103a1 commit b64d005
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 28 deletions.
14 changes: 13 additions & 1 deletion napalm/base/utils/string_parsers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" Common methods to normalize a string """
import re
from typing import Union, List, Iterable, Dict, Optional
import struct
from typing import Union, List, Iterable, Dict, Optional, Tuple


def convert(text: str) -> Union[str, int]:
Expand Down Expand Up @@ -133,3 +134,14 @@ def convert_uptime_string_seconds(uptime: str) -> int:
raise Exception("Unrecognized uptime string:{}".format(uptime))

return uptime_seconds


def parse_fixed_width(text: str, *fields: int) -> List[Tuple[str, ...]]:
len = sum(fields)
fmtstring = " ".join(f"{fw}s" for fw in fields)
unpack = struct.Struct(fmtstring).unpack_from

def parse(line: str) -> Tuple[str, ...]:
return tuple([str(s.decode()) for s in unpack(line.ljust(len).encode())])

return [parse(s) for s in text.splitlines()]
83 changes: 65 additions & 18 deletions napalm/eos/eos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2130,15 +2130,59 @@ def get_config(self, retrieve="all", full=False, sanitized=False):
else:
raise Exception("Wrong retrieve filter: {}".format(retrieve))

def _show_vrf(self):
def _show_vrf_json(self):
commands = ["show vrf"]

vrfs = self._run_commands(commands)[0]["vrfs"]
return [
{
"name": k,
"interfaces": [i for i in v["interfaces"]],
"route_distinguisher": v["routeDistinguisher"],
}
for k, v in vrfs.items()
]

def _show_vrf_text(self):
commands = ["show vrf"]

# This command has no JSON yet
# This command has no JSON in EOS < 4.23
raw_output = self._run_commands(commands, encoding="text")[0].get("output", "")

output = napalm.base.helpers.textfsm_extractor(self, "vrf", raw_output)
width_line = raw_output.splitlines()[2] # Line with dashes
fields = width_line.split(" ")
widths = [len(f) + 1 for f in fields]
widths[-1] -= 1

parsed_lines = string_parsers.parse_fixed_width(raw_output, *widths)

vrfs = []
vrf = {}
current_vrf = None
for line in parsed_lines[3:]:
line = [t.strip() for t in line]
if line[0]:
if current_vrf:
vrfs.append(vrf)
current_vrf = line[0]
vrf = {
"name": current_vrf,
"interfaces": list(),
}
if line[1]:
vrf["route_distinguisher"] = line[1]
if line[4]:
vrf["interfaces"].extend([t.strip() for t in line[4].split(",") if t])
if current_vrf:
vrfs.append(vrf)

return vrfs

return output
def _show_vrf(self):
if self.cli_version == 2:
return self._show_vrf_json()
else:
return self._show_vrf_text()

def _get_vrfs(self):
output = self._show_vrf()
Expand Down Expand Up @@ -2171,23 +2215,26 @@ def get_network_instances(self, name=""):
interfaces[str(line.strip())] = {}
all_vrf_interfaces[str(line.strip())] = {}

vrfs[str(vrf["name"])] = {
"name": str(vrf["name"]),
"type": "L3VRF",
vrfs[vrf["name"]] = {
"name": vrf["name"],
"type": "DEFAULT_INSTANCE" if vrf["name"] == "default" else "L3VRF",
"state": {"route_distinguisher": vrf["route_distinguisher"]},
"interfaces": {"interface": interfaces},
}
all_interfaces = self.get_interfaces_ip().keys()
vrfs["default"] = {
"name": "default",
"type": "DEFAULT_INSTANCE",
"state": {"route_distinguisher": ""},
"interfaces": {
"interface": {
k: {} for k in all_interfaces if k not in all_vrf_interfaces.keys()
}
},
}
if "default" not in vrfs:
all_interfaces = self.get_interfaces_ip().keys()
vrfs["default"] = {
"name": "default",
"type": "DEFAULT_INSTANCE",
"state": {"route_distinguisher": ""},
"interfaces": {
"interface": {
k: {}
for k in all_interfaces
if k not in all_vrf_interfaces.keys()
}
},
}

if name:
if name in vrfs:
Expand Down
36 changes: 35 additions & 1 deletion test/base/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@
from napalm.base.netmiko_helpers import netmiko_args
import napalm.base.exceptions
from napalm.base.base import NetworkDriver
from napalm.base.utils.string_parsers import convert_uptime_string_seconds
from napalm.base.utils.string_parsers import (
convert_uptime_string_seconds,
parse_fixed_width,
)


class TestBaseHelpers(unittest.TestCase):
Expand Down Expand Up @@ -672,6 +675,37 @@ def test_ttp_parse(self):
)
self.assertEqual(result, _EXPECTED_RESULT)

def test_parse_fixed_width(self):
_TEST_STRING = """ VRF RD Protocols State Interfaces
---------------- --------------- --------------- ------------------- ---------------------------
123456789012345671234567890123456123456789012345612345678901234567890123456789012345678901234567
""" # noqa: E501
_EXPECTED_RESULT = [
(
" VRF ",
" RD ",
" Protocols ",
" State ",
"Interfaces ",
),
(
"---------------- ",
"--------------- ",
"--------------- ",
"------------------- ",
"---------------------------",
),
(
"12345678901234567",
"1234567890123456",
"1234567890123456",
"12345678901234567890",
"123456789012345678901234567",
),
]
result = parse_fixed_width(_TEST_STRING, 17, 16, 16, 20, 27)
self.assertEqual(result, _EXPECTED_RESULT)


class FakeNetworkDriver(NetworkDriver):
def __init__(self):
Expand Down
13 changes: 13 additions & 0 deletions test/eos/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None)

self.patched_attrs = ["device"]
self.device = FakeEOSDevice()
self._cli_version = 1

@property
def cli_version(self):
try:
full_path = self.device.find_file("cli_version.txt")
except IOError:
return self._cli_version
return int(self.device.read_txt_file(full_path))

@cli_version.setter
def cli_version(self, value):
self._cli_version = value


class FakeEOSDevice(BaseTestDouble):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"management": {
"name": "management",
"type": "L3VRF",
"state": { "route_distinguisher": "" },
"interfaces": { "interface": { "Management1": {} } }
},
"default": {
"interfaces": {
"interface": {
"Ethernet1": {},
"Ethernet2": {},
"Ethernet3": {},
"Ethernet4": {},
"Ethernet49/1": {},
"Ethernet5": {},
"Ethernet50/1": {},
"Ethernet52/1": {},
"Ethernet53/1": {},
"Ethernet54/1": {},
"Ethernet55/1": {},
"Ethernet56/1": {},
"Loopback0": {}
}
},
"state": { "route_distinguisher": "" },
"type": "DEFAULT_INSTANCE",
"name": "default"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Maximum number of vrfs allowed: 1023
VRF RD Protocols State Interfaces
---------------- --------------- --------------- ------------------- ---------------------------
default <not set> ipv4,ipv6 v4:routing, Ethernet1, Ethernet2,
v6:no routing Ethernet3, Ethernet4,
Ethernet49/1, Ethernet5,
Ethernet50/1, Ethernet52/1,
Ethernet53/1, Ethernet54/1,
Ethernet55/1, Ethernet56/1,
Loopback0
management <not set> ipv4,ipv6 v4:routing, Management1
v6:no routing
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Maximum number of vrfs allowed: 14
Vrf RD Protocols State Interfaces
------- ------------ ------------ ------------------- -------------------
VRFA0 201:201 ipv4 v4:routing; multicast, Vlan2, Vlan102
v6:no routing
Vrf RD Protocols State Interfaces
------- --------- ------------ ------------------------ -------------------
VRFA0 201:201 ipv4 v4:routing; multicast, Vlan2, Vlan102
v6:no routing

VRFA1 203:203 ipv4 v4:routing; multicast, Vlan3, Vlan103
v6:no routing
VRFA1 203:203 ipv4 v4:routing; multicast, Vlan3, Vlan103
v6:no routing

VRFA2 205:205 ipv4 v4:routing; multicast, Ethernet1, Vlan100
v6:no routing
VRFA2 205:205 ipv4 v4:routing; multicast, Ethernet1, Vlan100
v6:no routing
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"default": {
"name": "default",
"type": "DEFAULT_INSTANCE",
"state": { "route_distinguisher": "" },
"interfaces": {
"interface": {
"Ethernet1": {},
"Ethernet2": {},
"Loopback0": {},
"Management0": {}
}
}
},
"foo": {
"name": "foo",
"type": "L3VRF",
"state": { "route_distinguisher": "0:1" },
"interfaces": {
"interface": {}
}
},
"bar": {
"name": "bar",
"type": "L3VRF",
"state": { "route_distinguisher": "" },
"interfaces": {
"interface": {}
}
}
}
61 changes: 61 additions & 0 deletions test/eos/mocked_data/test_get_network_instances/vrf/show_vrf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"vrfs": {
"foo": {
"routeDistinguisher": "0:1",
"protocols": {
"ipv4": {
"supported": true,
"protocolState": "up",
"routingState": "up"
},
"ipv6": {
"supported": true,
"protocolState": "up",
"routingState": "down"
}
},
"vrfState": "up",
"interfacesV4": [],
"interfacesV6": [],
"interfaces": []
},
"bar": {
"routeDistinguisher": "",
"protocols": {
"ipv4": {
"supported": true,
"protocolState": "up",
"routingState": "down"
},
"ipv6": {
"supported": true,
"protocolState": "up",
"routingState": "down"
}
},
"vrfState": "up",
"interfacesV4": [],
"interfacesV6": [],
"interfaces": []
},
"default": {
"routeDistinguisher": "",
"protocols": {
"ipv4": {
"supported": true,
"protocolState": "up",
"routingState": "up"
},
"ipv6": {
"supported": true,
"protocolState": "up",
"routingState": "down"
}
},
"vrfState": "up",
"interfacesV4": ["Ethernet1", "Ethernet2", "Loopback0", "Management0"],
"interfacesV6": ["Management0"],
"interfaces": ["Ethernet1", "Ethernet2", "Loopback0", "Management0"]
}
}
}

0 comments on commit b64d005

Please sign in to comment.