Skip to content

Commit f195a34

Browse files
authored
Merge pull request #1276 from napalm-automation/develop
Release 3.2.0
2 parents 4af2aba + 965966d commit f195a34

37 files changed

+7099
-148
lines changed

napalm/eos/eos.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -966,7 +966,7 @@ def get_arp_table(self, vrf=""):
966966
interface = str(neighbor.get("interface"))
967967
mac_raw = neighbor.get("hwAddress")
968968
ip = str(neighbor.get("address"))
969-
age = float(neighbor.get("age"))
969+
age = float(neighbor.get("age", -1.0))
970970
arp_table.append(
971971
{
972972
"interface": interface,
@@ -1990,3 +1990,16 @@ def ping(
19901990
)
19911991
ping_dict["success"].update({"results": results_array})
19921992
return ping_dict
1993+
1994+
def get_vlans(self):
1995+
command = ["show vlan"]
1996+
output = self.device.run_commands(command, encoding="json")[0]["vlans"]
1997+
1998+
vlans = {}
1999+
for vlan, vlan_config in output.items():
2000+
vlans[vlan] = {
2001+
"name": vlan_config["name"],
2002+
"interfaces": list(vlan_config["interfaces"].keys()),
2003+
}
2004+
2005+
return vlans

napalm/eos/pyeapi_syntax_wrapper.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def update_cli_version(self, version):
2525
"""
2626
self.cli_version = version
2727

28-
def run_commands(self, commands, **kwargs):
28+
def run_commands(self, commands, *args, **kwargs):
2929
"""
3030
Run commands wrapper
3131
:param commands: list of commands
@@ -39,4 +39,4 @@ def run_commands(self, commands, **kwargs):
3939
else:
4040
commands = [cli_convert(cmd, self.cli_version) for cmd in commands]
4141

42-
return super(Node, self).run_commands(commands, **kwargs)
42+
return super(Node, self).run_commands(commands, *args, **kwargs)

napalm/eos/utils/cli_syntax.py

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"show dot1x all brief": "show dot1x all summary",
9393
"show system environment all": "show environment all",
9494
"show system environment cooling": "show environment cooling",
95+
"show system environment temperature": "show environment temperature",
9596
"show interfaces hardware": "show interfaces capabilities",
9697
"show interfaces flow-control": "show interfaces flowcontrol",
9798
"show pvlan mapping interfaces": "show interfaces private-vlan mapping",
@@ -244,6 +245,7 @@
244245
"show dot1x all summary": "show dot1x all brief",
245246
"show environment all": "show system environment all",
246247
"show environment cooling": "show system environment cooling",
248+
"show environment temperature": "show system environment temperature",
247249
"show interfaces capabilities": "show interfaces hardware",
248250
"show interfaces flowcontrol": "show interfaces flow-control",
249251
"show interfaces private-vlan mapping": "show pvlan mapping interfaces",

napalm/ios/ios.py

+36-13
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
RE_BGP_REMOTE_AS = re.compile(r"remote AS (" + ASN_REGEX + r")")
8989
RE_BGP_AS_PATH = re.compile(r"^[ ]{2}([\d\(]([\d\) ]+)|Local)")
9090

91+
RE_RP_ROUTE = re.compile(r"Routing entry for (" + IP_ADDR_REGEX + r"\/\d+)")
9192
RE_RP_FROM = re.compile(r"Known via \"([a-z]+)[ \"]")
9293
RE_RP_VIA = re.compile(r"via (\S+)")
9394
RE_RP_METRIC = re.compile(r"[ ]+Route metric is (\d+)")
@@ -2322,7 +2323,7 @@ def get_arp_table(self, vrf=""):
23222323

23232324
try:
23242325
if age == "-":
2325-
age = 0
2326+
age = -1
23262327
age = float(age)
23272328
except ValueError:
23282329
raise ValueError("Unable to convert age value to float: {}".format(age))
@@ -2696,18 +2697,22 @@ def process_mac_fields(vlan, mac, mac_type, interface):
26962697

26972698
def get_probes_config(self):
26982699
probes = {}
2700+
26992701
probes_regex = (
27002702
r"ip\s+sla\s+(?P<id>\d+)\n"
2701-
r"\s+(?P<probe_type>\S+)\s+(?P<probe_args>.*\n).*"
2702-
r"\s+tag\s+(?P<name>\S+)\n.*"
2703-
r"\s+history\s+buckets-kept\s+(?P<probe_count>\d+)\n.*"
2704-
r"\s+frequency\s+(?P<interval>\d+)$"
2703+
r"\s+(?P<probe_type>\S+)\s+(?P<probe_args>.*)\n"
2704+
r"\s+tag\s+(?P<name>[\S ]+)\n"
2705+
r"(\s+.*\n)*"
2706+
r"((\s+frequency\s+(?P<interval0>\d+)\n(\s+.*\n)*\s+history"
2707+
r"\s+buckets-kept\s+(?P<probe_count0>\d+))|(\s+history\s+buckets-kept"
2708+
r"\s+(?P<probe_count1>\d+)\n.*\s+frequency\s+(?P<interval1>\d+)))"
27052709
)
2710+
27062711
probe_args = {
27072712
"icmp-echo": r"^(?P<target>\S+)\s+source-(?:ip|interface)\s+(?P<source>\S+)$"
27082713
}
27092714
probe_type_map = {"icmp-echo": "icmp-ping"}
2710-
command = "show run | include ip sla [0-9]"
2715+
command = "show run | section ip sla [0-9]"
27112716
output = self._send_command(command)
27122717
for match in re.finditer(probes_regex, output, re.M):
27132718
probe = match.groupdict()
@@ -2723,8 +2728,8 @@ def get_probes_config(self):
27232728
"probe_type": probe_type_map[probe["probe_type"]],
27242729
"target": probe_data["target"],
27252730
"source": probe_data["source"],
2726-
"probe_count": int(probe["probe_count"]),
2727-
"test_interval": int(probe["interval"]),
2731+
"probe_count": int(probe["probe_count0"] or probe["probe_count1"]),
2732+
"test_interval": int(probe["interval0"] or probe["interval1"]),
27282733
}
27292734
}
27302735

@@ -2773,7 +2778,7 @@ def _get_bgp_route_attr(self, destination, vrf, next_hop, ip_version=4):
27732778

27742779
search_re_dict = {
27752780
"aspath": {
2776-
"re": r"[^|\\n][ ]{2}([\d\(\)]([\d\(\) ])*)",
2781+
"re": r"[^|\\n][ ]{2}([\d\(\)]([\d\(\) ])*|Local)",
27772782
"group": 1,
27782783
"default": "",
27792784
},
@@ -2947,8 +2952,11 @@ def get_route_to(self, destination="", protocol="", longer=False):
29472952
vrfs.append("default") # global VRF
29482953
ipnet_dest = IPNetwork(destination)
29492954
prefix = str(ipnet_dest.network)
2950-
netmask = str(ipnet_dest.netmask)
2951-
routes = {destination: []}
2955+
netmask = ""
2956+
routes = {}
2957+
if "/" in destination:
2958+
netmask = str(ipnet_dest.netmask)
2959+
routes = {destination: []}
29522960
commands = []
29532961
for _vrf in vrfs:
29542962
if _vrf == "default":
@@ -2969,6 +2977,14 @@ def get_route_to(self, destination="", protocol="", longer=False):
29692977
for (outitem, _vrf) in zip(output, vrfs): # for all VRFs
29702978
route_proto_regex = RE_RP_FROM.search(outitem)
29712979
if route_proto_regex:
2980+
route_match = destination
2981+
if netmask == "":
2982+
# Get the matching route for a non-exact lookup
2983+
route_match_regex = RE_RP_ROUTE.search(outitem)
2984+
if route_match_regex:
2985+
route_match = route_match_regex.group(1)
2986+
if route_match not in routes:
2987+
routes[route_match] = []
29722988
# routing protocol name (bgp, ospf, ...)
29732989
route_proto = route_proto_regex.group(1)
29742990
rdb = outitem.split("Routing Descriptor Blocks:")
@@ -3037,7 +3053,7 @@ def get_route_to(self, destination="", protocol="", longer=False):
30373053
nh_line_found = (
30383054
False # for next RT entry processing ...
30393055
)
3040-
routes[destination].append(route_entry)
3056+
routes[route_match].append(route_entry)
30413057
return routes
30423058

30433059
def get_snmp_information(self):
@@ -3405,7 +3421,14 @@ def get_network_instances(self, name=""):
34053421
if "No interfaces" in first_part:
34063422
interfaces = {}
34073423
else:
3408-
interfaces = {itf: {} for itf in if_regex.group(1).split()}
3424+
interfaces = {
3425+
canonical_interface_name(itf, {"Vl": "Vlan"}): {}
3426+
for itf in if_regex.group(1).split()
3427+
}
3428+
3429+
# remove interfaces in the VRF from the default VRF
3430+
for item in interfaces:
3431+
del instances["default"]["interfaces"]["interface"][item]
34093432

34103433
instances[vrf_name] = {
34113434
"name": vrf_name,

napalm/iosxr/iosxr.py

+25-10
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from napalm.base.exceptions import CommandTimeoutException
4242

4343
logger = logging.getLogger(__name__)
44+
IP_RIBRoute = "IP_RIBRoute"
4445

4546

4647
class IOSXRDriver(NetworkDriver):
@@ -1402,7 +1403,7 @@ def get_arp_table(self, vrf=""):
14021403
str, napalm.base.helpers.find_txt(arp_entry, ".//Address")
14031404
)
14041405
age = napalm.base.helpers.convert(
1405-
float, napalm.base.helpers.find_txt(arp_entry, ".//Age"), 0.0
1406+
float, napalm.base.helpers.find_txt(arp_entry, ".//Age"), -1.0
14061407
)
14071408
mac_raw = napalm.base.helpers.find_txt(arp_entry, ".//HardwareAddress")
14081409

@@ -1641,6 +1642,7 @@ def get_mac_address_table(self):
16411642
def get_route_to(self, destination="", protocol="", longer=False):
16421643

16431644
routes = {}
1645+
global IP_RIBRoute
16441646

16451647
if not isinstance(destination, str):
16461648
raise TypeError("Please specify a valid destination!")
@@ -1672,32 +1674,45 @@ def get_route_to(self, destination="", protocol="", longer=False):
16721674
"<Get><Operational><IPV6_RIB><VRFTable><VRF><Naming><VRFName>"
16731675
"default</VRFName></Naming><AFTable><AF><Naming><AFName>IPv6</AFName></Naming>"
16741676
"<SAFTable>"
1675-
"<SAF><Naming><SAFName>Unicast</SAFName></Naming><IP_RIBRouteTable><IP_RIBRoute>"
1677+
"<SAF><Naming><SAFName>Unicast</SAFName></Naming><IP_RIBRouteTable><{ipribroute}>"
16761678
"<Naming>"
16771679
"<RouteTableName>default</RouteTableName></Naming><RouteTable><Route><Naming>"
16781680
"<Address>"
1679-
"{network}</Address>{prefix}</Naming></Route></RouteTable></IP_RIBRoute>"
1681+
"{network}</Address>{prefix}</Naming></Route></RouteTable></{ipribroute}>"
16801682
"</IP_RIBRouteTable></SAF></SAFTable></AF></AFTable></VRF></VRFTable></IPV6_RIB>"
16811683
"</Operational></Get>"
1682-
).format(network=network, prefix=prefix_tag)
1684+
).format(network=network, prefix=prefix_tag, ipribroute=IP_RIBRoute)
16831685
else:
16841686
route_info_rpc_command = (
16851687
"<Get><Operational><RIB><VRFTable><VRF><Naming><VRFName>"
16861688
"default"
16871689
"</VRFName></Naming><AFTable><AF><Naming><AFName>IPv4</AFName></Naming>"
16881690
"<SAFTable><SAF>"
1689-
"<Naming><SAFName>Unicast</SAFName></Naming><IP_RIBRouteTable><IP_RIBRoute>"
1691+
"<Naming><SAFName>Unicast</SAFName></Naming><IP_RIBRouteTable><{ipribroute}>"
16901692
"<Naming>"
16911693
"<RouteTableName>default</RouteTableName></Naming><RouteTable><Route><Naming>"
16921694
"<Address>"
1693-
"{network}</Address>{prefix}</Naming></Route></RouteTable></IP_RIBRoute>"
1695+
"{network}</Address>{prefix}</Naming></Route></RouteTable></{ipribroute}>"
16941696
"</IP_RIBRouteTable>"
16951697
"</SAF></SAFTable></AF></AFTable></VRF></VRFTable></RIB></Operational></Get>"
1696-
).format(network=network, prefix=prefix_tag)
1698+
).format(network=network, prefix=prefix_tag, ipribroute=IP_RIBRoute)
16971699

1698-
routes_tree = ETREE.fromstring(
1699-
self.device.make_rpc_call(route_info_rpc_command)
1700-
)
1700+
try:
1701+
routes_tree = ETREE.fromstring(
1702+
self.device.make_rpc_call(route_info_rpc_command)
1703+
)
1704+
except Exception:
1705+
pass
1706+
# Some versions of IOS-XR use IP_RIBRouteTableName instead of IP_RIBRoute.
1707+
# If IP_RIBRoute throws an exception, try again with IP_RIBRouteTableName
1708+
# and have subsequent get_route_to calls use that.
1709+
IP_RIBRoute = "IP_RIBRouteTableName"
1710+
route_info_rpc_command = route_info_rpc_command.replace(
1711+
"IP_RIBRoute>", "{ipribroute}>".format(ipribroute=IP_RIBRoute)
1712+
)
1713+
routes_tree = ETREE.fromstring(
1714+
self.device.make_rpc_call(route_info_rpc_command)
1715+
)
17011716

17021717
for route in routes_tree.xpath(".//Route"):
17031718
route_protocol = napalm.base.helpers.convert(

napalm/nxos_ssh/nxos_ssh.py

+97
Original file line numberDiff line numberDiff line change
@@ -1641,3 +1641,100 @@ def get_optics(self):
16411641
optics_detail[port] = port_detail
16421642

16431643
return optics_detail
1644+
1645+
def get_interfaces_counters(self):
1646+
"""
1647+
Return interface counters and errors.
1648+
1649+
'tx_errors': int,
1650+
'rx_errors': int,
1651+
'tx_discards': int,
1652+
'rx_discards': int,
1653+
'tx_octets': int,
1654+
'rx_octets': int,
1655+
'tx_unicast_packets': int,
1656+
'rx_unicast_packets': int,
1657+
'tx_multicast_packets': int,
1658+
'rx_multicast_packets': int,
1659+
'tx_broadcast_packets': int,
1660+
'rx_broadcast_packets': int,
1661+
"""
1662+
if_mapping = {
1663+
"eth": {
1664+
"regexp": re.compile("^(Ether|port-channel).*"),
1665+
"mapping": {
1666+
"tx_errors": "eth_outerr",
1667+
"rx_errors": "eth_inerr",
1668+
"tx_discards": "eth_outdiscard",
1669+
"rx_discards": "eth_indiscard",
1670+
"tx_octets": "eth_outbytes",
1671+
"rx_octets": "eth_inbytes",
1672+
"tx_unicast_packets": "eth_outucast",
1673+
"rx_unicast_packets": "eth_inucast",
1674+
"tx_multicast_packets": "eth_outmcast",
1675+
"rx_multicast_packets": "eth_inmcast",
1676+
"tx_broadcast_packets": "eth_outbcast",
1677+
"rx_broadcast_packets": "eth_inbcast",
1678+
},
1679+
},
1680+
"mgmt": {
1681+
"regexp": re.compile("mgm.*"),
1682+
"mapping": {
1683+
"tx_errors": None,
1684+
"rx_errors": None,
1685+
"tx_discards": None,
1686+
"rx_discards": None,
1687+
"tx_octets": "mgmt_out_bytes",
1688+
"rx_octets": "mgmt_in_bytes",
1689+
"tx_unicast_packets": None,
1690+
"rx_unicast_packets": None,
1691+
"tx_multicast_packets": "mgmt_out_mcast",
1692+
"rx_multicast_packets": "mgmt_in_mcast",
1693+
"tx_broadcast_packets": None,
1694+
"rx_broadcast_packets": None,
1695+
},
1696+
},
1697+
}
1698+
command = "show interface counters detailed | json"
1699+
# To retrieve discards
1700+
command_interface = "show interface | json"
1701+
counters_table_raw = self._get_command_table(
1702+
command, "TABLE_interface", "ROW_interface"
1703+
)
1704+
counters_interface_table_raw = self._get_command_table(
1705+
command_interface, "TABLE_interface", "ROW_interface"
1706+
)
1707+
all_stats_d = {}
1708+
# Start with show interface as all interfaces
1709+
# Are surely listed
1710+
for row in counters_interface_table_raw:
1711+
if_counter = {}
1712+
# loop through regexp to find mapping
1713+
for if_v in if_mapping:
1714+
my_re = if_mapping[if_v]["regexp"]
1715+
re_match = my_re.match(row["interface"])
1716+
if re_match:
1717+
interface = re_match.group()
1718+
map_d = if_mapping[if_v]["mapping"]
1719+
for k, v in map_d.items():
1720+
if_counter[k] = int(row[v]) if v in row else 0
1721+
all_stats_d[interface] = if_counter
1722+
break
1723+
print(all_stats_d)
1724+
1725+
for row in counters_table_raw:
1726+
if_counter = {}
1727+
# loop through regexp to find mapping
1728+
for if_v in if_mapping:
1729+
my_re = if_mapping[if_v]["regexp"]
1730+
re_match = my_re.match(row["interface"])
1731+
if re_match:
1732+
interface = re_match.group()
1733+
map_d = if_mapping[if_v]["mapping"]
1734+
for k, v in map_d.items():
1735+
if v in row:
1736+
if_counter[k] = int(row[v])
1737+
all_stats_d[interface].update(if_counter)
1738+
break
1739+
1740+
return all_stats_d

requirements-dev.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
black==19.10b0
2-
coveralls==2.1.1
1+
black==20.8b1
2+
coveralls==2.1.2
33
ddt==1.4.1
44
flake8-import-order==0.18.1
55
pytest==5.4.3
6-
pytest-cov==2.10.0
6+
pytest-cov==2.10.1
77
pytest-json==0.4.0
88
pytest-pythonpath==0.7.3
99
pylama==7.7.1
1010
mock==4.0.2
11-
tox==3.18.0
11+
tox==3.20.0

setup.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@
1212

1313
setup(
1414
name="napalm",
15-
version="3.1.0",
15+
version="3.2.0",
1616
packages=find_packages(exclude=("test*",)),
1717
test_suite="test_base",
1818
author="David Barroso, Kirk Byers, Mircea Ulinic",
1919
2020
description="Network Automation and Programmability Abstraction Layer with Multivendor support",
21+
license="Apache 2.0",
2122
long_description=long_description,
2223
long_description_content_type="text/markdown",
2324
classifiers=[
2425
"Topic :: Utilities",
26+
"License :: OSI Approved :: Apache Software License",
2527
"Programming Language :: Python",
2628
"Programming Language :: Python :: 3",
2729
"Programming Language :: Python :: 3.6",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"interface": "Ethernet45", "ip": "172.17.17.1", "mac": "DC:38:E1:11:97:CF", "age": -1.0}, {"interface": "Ethernet36", "ip": "172.17.17.1", "mac": "90:E2:BA:5C:25:FD", "age": 0.0}]

0 commit comments

Comments
 (0)