Skip to content

Commit

Permalink
Merge pull request #1565 from jvanderaa/replace_confparse_w_netutils
Browse files Browse the repository at this point in the history
Replace confparse w netutils
  • Loading branch information
mirceaulinic committed Feb 13, 2022
2 parents 6a936b2 + 8f8da9a commit ff83c56
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 36 deletions.
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ ignore_missing_imports = True
[mypy-lxml.*]
ignore_missing_imports = True

[mypy-ciscoconfparse]
[mypy-netutils.*]
ignore_missing_imports = True

[mypy-textfsm]
Expand Down
78 changes: 60 additions & 18 deletions napalm/base/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
# third party libs
import jinja2
import textfsm
from ciscoconfparse import CiscoConfParse
from lxml import etree
from netaddr import EUI
from netaddr import IPAddress
from netaddr import mac_unix
from netutils.config.parser import IOSConfigParser

# local modules
import napalm.base.exceptions
Expand Down Expand Up @@ -127,42 +127,84 @@ def load_template(
return cls.load_merge_candidate(config=configuration)


def cisco_conf_parse_parents(
def netutils_parse_parents(
parent: str, child: str, config: Union[str, List[str]]
) -> List[str]:
"""
Use CiscoConfParse to find parent lines that contain a specific child line.
Use Netutils to find parent lines that contain a specific child line.
:param parent: The parent line to search for
:param child: The child line required under the given parent
:param config: The device running/startup config
"""
if type(config) == str:
config = config.splitlines() # type: ignore
parse = CiscoConfParse(config)
cfg_obj = parse.find_parents_w_child(parent, child)
return cfg_obj
# Check if the config is a list, if it is a list, then join it to make a string.
if isinstance(config, list):
config = "\n".join(config)
config = config + "\n"

# Config tree is the entire configuration in a tree format,
# followed by getting the individual lines that has the formats:
# ConfigLine(config_line=' ip address 192.0.2.10 255.255.255.0',
# parents=('interface GigabitEthernet1',))
# ConfigLine(config_line='Current configuration : 1624 bytes', parents=())
config_tree = IOSConfigParser(str(config))
configuration_lines = config_tree.build_config_relationship()

# Return config is the list that will be returned
return_config = []

# Loop over each of the configuration lines
for line in configuration_lines:
# Loop over any line that has a parent line. If there are no parents for a line item then
# the parents is an empty tuple.
for parent_line in line.parents:
if (
child in line.config_line
and re.match(parent, parent_line) is not None
and parent_line not in return_config
):
return_config.append(parent_line)

return return_config


def cisco_conf_parse_objects(
def netutils_parse_objects(
cfg_section: str, config: Union[str, List[str]]
) -> List[str]:
"""
Use CiscoConfParse to find and return a section of Cisco IOS config.
Use Netutils to find and return a section of Cisco IOS config.
Similar to "show run | section <cfg_section>"
:param cfg_section: The section of the config to return eg. "router bgp"
:param config: The running/startup config of the device to parse
"""
# Check if the config is a list, if it is a list, then join it to make a string.
if isinstance(config, list):
config = "\n".join(config)
config = config + "\n"

# Config tree is the entire configuration in a tree format,
# followed by getting the individual lines that has the formats:
# ConfigLine(config_line=' ip address 192.0.2.10 255.255.255.0',
# parents=('interface GigabitEthernet1',))
# ConfigLine(config_line='Current configuration : 1624 bytes', parents=())
config_tree = IOSConfigParser(str(config))
lines = config_tree.build_config_relationship()

# Return config is the list that will be returned
return_config = []
if type(config) is str:
config = config.splitlines() # type: ignore
parse = CiscoConfParse(config)
cfg_obj = parse.find_objects(cfg_section)
for parent in cfg_obj:
return_config.append(parent.text)
for child in parent.all_children:
return_config.append(child.text)
for line in lines:
# The parent configuration is expected on the function that this is replacing,
# add the parent line to the base of the return_config
if cfg_section in line.config_line:
return_config.append(line.config_line)
# Check if the tuple is greater than 0
if len(line.parents) > 0:
# Check the eldest parent, if that is part of the config section, then append
# the current line being checked to it.
if cfg_section in line.parents[0]:
return_config.append(line.config_line)

return return_config


Expand Down
31 changes: 15 additions & 16 deletions napalm/ios/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -1333,21 +1333,20 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout):
}
return prefix_limit

# Get BGP config using ciscoconfparse because some old devices dont support "| sec bgp"
# Get BGP config using netutils because some old devices dont support "| sec bgp"
cfg = self.get_config(retrieve="running")
cfg = cfg["running"].splitlines()
bgp_config_text = napalm.base.helpers.cisco_conf_parse_objects(
"router bgp", cfg
bgp_config_list = napalm.base.helpers.netutils_parse_objects(
"router bgp", cfg["running"]
)
bgp_asn = napalm.base.helpers.regex_find_txt(
r"router bgp (\d+)", bgp_config_text, default=0
r"router bgp (\d+)", bgp_config_list, default=0
)
# Get a list of all neighbors and groups in the config
all_neighbors = set()
all_groups = set()
bgp_group_neighbors = {}
all_groups.add("_")
for line in bgp_config_text:
for line in bgp_config_list:
if " neighbor " in line:
if re.search(IP_ADDR_REGEX, line) is not None:
all_neighbors.add(re.search(IP_ADDR_REGEX, line).group())
Expand All @@ -1364,8 +1363,8 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout):
if neighbor:
if bgp_neighbor != neighbor:
continue
afi_list = napalm.base.helpers.cisco_conf_parse_parents(
r"\s+address-family.*", bgp_neighbor, bgp_config_text
afi_list = napalm.base.helpers.netutils_parse_parents(
r"\s+address-family.*", bgp_neighbor, bgp_config_list
)
try:
afi = afi_list[0]
Expand All @@ -1376,8 +1375,8 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout):
if "vrf" in str(afi_list):
continue
else:
neighbor_config = napalm.base.helpers.cisco_conf_parse_objects(
bgp_neighbor, bgp_config_text
neighbor_config = napalm.base.helpers.netutils_parse_objects(
bgp_neighbor, bgp_config_list
)
# For group_name- use peer-group name, else VRF name, else "_" for no group
group_name = napalm.base.helpers.regex_find_txt(
Expand Down Expand Up @@ -1467,16 +1466,16 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout):
"neighbors": bgp_group_neighbors.get("_", {}),
}
continue
neighbor_config = napalm.base.helpers.cisco_conf_parse_objects(
group_name, bgp_config_text
neighbor_config = napalm.base.helpers.netutils_parse_objects(
group_name, bgp_config_list
)
multipath = False
afi_list = napalm.base.helpers.cisco_conf_parse_parents(
r"\s+address-family.*", group_name, neighbor_config
afi_list = napalm.base.helpers.netutils_parse_parents(
r"\s+address-family.*", group_name, bgp_config_list
)
for afi in afi_list:
afi_config = napalm.base.helpers.cisco_conf_parse_objects(
afi, bgp_config_text
afi_config = napalm.base.helpers.netutils_parse_objects(
afi, bgp_config_list
)
multipath = bool(
napalm.base.helpers.regex_find_txt(r" multipath", str(afi_config))
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pyYAML
pyeapi>=0.8.2
netmiko>=3.3.0,<4.0.0
junos-eznc>=2.2.1
ciscoconfparse
scp
lxml>=4.3.0
ncclient
netutils>=1.0.0

0 comments on commit ff83c56

Please sign in to comment.