Skip to content

Commit

Permalink
Merge branch 'develop' into nxos-bgp-shut
Browse files Browse the repository at this point in the history
  • Loading branch information
mirceaulinic authored Mar 21, 2024
2 parents 31d547d + a45cf4d commit a0fe5c9
Show file tree
Hide file tree
Showing 105 changed files with 1,097 additions and 1,795 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [3.7, 3.8, 3.9, 3.10.9, 3.11]
python-version: [3.8, 3.9, 3.10.9, 3.11, 3.12.0]

steps:
- name: Checkout repository
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Install
pip install napalm
```

*Note*: Beginning with release 5.0.0 and later, NAPALM offers support for
Python 3.8+ only.

*Note*: Beginning with release 4.0.0 and later, NAPALM offers support for
Python 3.7+ only.

Expand Down
44 changes: 26 additions & 18 deletions docs/tutorials/extend_driver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,26 @@ Bulding on the previous example, we can create a a simple parse to return what o

.. code-block:: python
def get_my_banner(self):
command = 'show banner motd'
output = self._send_command(command)
return_vars = {}
for line in output.splitlines():
split_line = line.split()
if "Site:" == split_line[0]:
return_vars["site"] = split_line[1]
elif "Device:" == split_line[0]:
return_vars["device"] = split_line[1]
elif "Floor:" == split_line[0]:
return_vars["floor"] = split_line[1]
elif "Room:" == split_line[0]:
return_vars["room"] = split_line[1]
return return_vars
from napalm.ios.ios import IOSDriver
class CustomIOSDriver(IOSDriver):
"""Custom NAPALM Cisco IOS Handler."""
def get_my_banner(self):
command = 'show banner motd'
output = self._send_command(command)
return_vars = {}
for line in output.splitlines():
split_line = line.split()
if "Site:" == split_line[0]:
return_vars["site"] = split_line[1]
elif "Device:" == split_line[0]:
return_vars["device"] = split_line[1]
elif "Floor:" == split_line[0]:
return_vars["floor"] = split_line[1]
elif "Room:" == split_line[0]:
return_vars["room"] = split_line[1]
return return_vars
Which can build.

Expand All @@ -85,8 +89,12 @@ be able to support their own environment.

.. code-block:: python
def get_my_banner(self):
raise NotImplementedError
from napalm.ios.ios import IOSDriver
class CustomIOSDriver(IOSDriver):
"""Custom NAPALM Cisco IOS Handler."""
def get_my_banner(self):
raise NotImplementedError
This feature is meant to allow for maximum amount of flexibility, but it is up to the user to ensure they do
not run into namespace issues, and follow best practices.
4 changes: 4 additions & 0 deletions napalm/base/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class ConnectionClosedException(ConnectionException):
pass


class UnsupportedVersion(ConnectionException):
pass


class ReplaceConfigException(NapalmException):
pass

Expand Down
2 changes: 1 addition & 1 deletion napalm/base/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def compare(
else:
return src == dst

elif type(src) == type(dst) == list:
elif isinstance(src, list) and isinstance(dst, list):
pairs = zip(src, dst)
diff_lists = [
[(k, x[k], y[k]) for k in x if not re.search(x[k], y[k])]
Expand Down
93 changes: 38 additions & 55 deletions napalm/eos/eos.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@
ReplaceConfigException,
SessionLockedException,
CommandErrorException,
UnsupportedVersion,
)
from napalm.eos.constants import LLDP_CAPAB_TRANFORM_TABLE
from napalm.eos.pyeapi_syntax_wrapper import Node
from napalm.eos.utils.versions import EOSVersion
from napalm.eos.utils.cli_syntax import cli_convert
import napalm.base.constants as c

# local modules
Expand Down Expand Up @@ -123,15 +122,13 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None)
self.timeout = timeout
self.config_session = None
self.locked = False
self.cli_version = 1

self.platform = "eos"
self.profile = [self.platform]
self.optional_args = optional_args or {}

self.enablepwd = self.optional_args.pop("enable_password", "")
self.eos_autoComplete = self.optional_args.pop("eos_autoComplete", None)
self.fn0039_config = self.optional_args.pop("eos_fn0039_config", False)

# Define locking method
self.lock_disable = self.optional_args.pop("lock_disable", False)
Expand Down Expand Up @@ -201,10 +198,6 @@ def open(self):
device_type="arista_eos",
netmiko_optional_args=self.netmiko_optional_args,
)
# let's try to determine if we need to use new EOS cli syntax
sh_ver = self._run_commands(["show version"])
if EOSVersion(sh_ver[0]["version"]) >= EOSVersion("4.23.0"):
self.cli_version = 2
else:
try:
connection = self.transport_class(
Expand All @@ -216,22 +209,24 @@ def open(self):
)

if self.device is None:
self.device = Node(connection, enablepwd=self.enablepwd)
self.device = pyeapi.client.Node(
connection, enablepwd=self.enablepwd
)
# does not raise an Exception if unusable

# let's try to determine if we need to use new EOS cli syntax
sh_ver = self.device.run_commands(["show version"])
self.cli_version = (
2 if EOSVersion(sh_ver[0]["version"]) >= EOSVersion("4.23.0") else 1
)

self.device.update_cli_version(self.cli_version)
except ConnectionError as ce:
# and this is raised either if device not avaiable
# either if HTTP(S) agent is not enabled
# show management api http-commands
raise ConnectionException(str(ce))

# endif self.transport

sh_ver = self._run_commands(["show version"])
self._eos_version = EOSVersion(sh_ver[0]["version"])
if self._eos_version < EOSVersion("4.23.0"):
raise UnsupportedVersion(self._eos_version)

def close(self):
"""Implementation of NAPALM method close."""
self.discard_config()
Expand Down Expand Up @@ -263,11 +258,6 @@ def is_alive(self):

def _run_commands(self, commands, **kwargs):
if self.transport == "ssh":
if self.fn0039_config:
if isinstance(commands, str):
commands = [cli_convert(commands, self.cli_version)]
else:
commands = [cli_convert(cmd, self.cli_version) for cmd in commands]
ret = []
for command in commands:
if kwargs.get("encoding") == "text":
Expand Down Expand Up @@ -463,10 +453,9 @@ def _load_config(self, filename=None, config=None, replace=True):
self._run_commands(
commands,
autoComplete=self.eos_autoComplete,
fn0039_transform=self.fn0039_config,
)
else:
self._run_commands(commands, fn0039_transform=self.fn0039_config)
self._run_commands(commands)
except pyeapi.eapilib.CommandError as e:
self.discard_config()
msg = str(e)
Expand Down Expand Up @@ -691,13 +680,16 @@ def get_re_group(res, key, default=None):
except KeyError:
return default

NEIGHBOR_FILTER = "bgp neighbors vrf all | include IPv[46] (Unicast|6PE):.*[0-9]+ | grep -v ' IPv[46] Unicast:/.' | remote AS |^Local AS|Desc|BGP state |remote router ID" # noqa
NEIGHBOR_FILTER = "vrf all | include IPv[46] (Unicast|6PE):.*[0-9]+ | grep -v ' IPv[46] Unicast:/.' | remote AS |^Local AS|Desc|BGP state |remote router ID" # noqa
output_summary_cmds = self._run_commands(
["show ipv6 bgp summary vrf all", "show ip bgp summary vrf all"],
encoding="json",
)
output_neighbor_cmds = self._run_commands(
["show ip " + NEIGHBOR_FILTER, "show ipv6 " + NEIGHBOR_FILTER],
[
"show ip bgp neighbors " + NEIGHBOR_FILTER,
"show ipv6 bgp peers " + NEIGHBOR_FILTER,
],
encoding="text",
)

Expand Down Expand Up @@ -831,10 +823,13 @@ def extract_temperature_data(data):
yield name, values

sh_version_out = self._run_commands(["show version"])
is_veos = sh_version_out[0]["modelName"].lower() == "veos"
commands = ["show environment cooling", "show environment temperature"]
is_veos = sh_version_out[0]["modelName"].lower() in ["veos", "ceoslab"]
commands = [
"show system environment cooling",
"show system environment temperature",
]
if not is_veos:
commands.append("show environment power")
commands.append("show system environment power")
fans_output, temp_output, power_output = self._run_commands(commands)
else:
fans_output, temp_output = self._run_commands(commands)
Expand Down Expand Up @@ -1527,23 +1522,12 @@ def get_route_to(self, destination="", protocol="", longer=False):
nexthop_interface_map[nexthop_ip] = next_hop.get("interface")
metric = route_details.get("metric")
if _vrf not in vrf_cache.keys():
if self.cli_version == 1:
command = "show ip{ipv} bgp {dest} {longer} detail vrf {_vrf}".format(
ipv=ipv,
dest=destination,
longer="longer-prefixes" if longer else "",
_vrf=_vrf,
)
else:
# Newer EOS can't mix longer-prefix and detail
command = (
"show ip{ipv} bgp {dest} {longer} vrf {_vrf}".format(
ipv=ipv,
dest=destination,
longer="longer-prefixes" if longer else "",
_vrf=_vrf,
)
)
command = "show ip{ipv} bgp {dest} {longer} vrf {_vrf}".format(
ipv=ipv,
dest=destination,
longer="longer-prefixes" if longer else "",
_vrf=_vrf,
)
vrf_cache.update(
{
_vrf: self._run_commands([command])[0]
Expand Down Expand Up @@ -1656,7 +1640,11 @@ def get_snmp_information(self):
# Default values
snmp_dict = {"chassis_id": "", "location": "", "contact": "", "community": {}}

commands = ["show snmp chassis", "show snmp location", "show snmp contact"]
commands = [
"show snmp v2-mib chassis",
"show snmp v2-mib location",
"show snmp v2-mib contact",
]
snmp_config = self._run_commands(commands, encoding="json")
for line in snmp_config:
for k, v in line.items():
Expand Down Expand Up @@ -1691,7 +1679,7 @@ def _sshkey_type(sshkey):

users = {}

commands = ["show user-account"]
commands = ["show users accounts"]
user_items = self._run_commands(commands)[0].get("users", {})

for user, user_details in user_items.items():
Expand Down Expand Up @@ -1950,7 +1938,7 @@ def _append(bgp_dict, peer_info):
summary_commands = []
if not neighbor_address:
commands.append("show ip bgp neighbors vrf all")
commands.append("show ipv6 bgp neighbors vrf all")
commands.append("show ipv6 bgp peers vrf all")
summary_commands.append("show ip bgp summary vrf all")
summary_commands.append("show ipv6 bgp summary vrf all")
else:
Expand All @@ -1963,7 +1951,7 @@ def _append(bgp_dict, peer_info):
commands.append("show ip bgp neighbors %s vrf all" % neighbor_address)
summary_commands.append("show ip bgp summary vrf all")
elif peer_ver == 6:
commands.append("show ipv6 bgp neighbors %s vrf all" % neighbor_address)
commands.append("show ipv6 bgp peers %s vrf all" % neighbor_address)
summary_commands.append("show ipv6 bgp summary vrf all")

raw_output = self._run_commands(commands, encoding="text")
Expand Down Expand Up @@ -2169,18 +2157,13 @@ def _show_vrf_text(self):
return vrfs

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

def _get_vrfs(self):
output = self._show_vrf()

vrfs = [str(vrf["name"]) for vrf in output]

vrfs.append("default")

return vrfs

def get_network_instances(self, name=""):
Expand Down
42 changes: 0 additions & 42 deletions napalm/eos/pyeapi_syntax_wrapper.py

This file was deleted.

Loading

0 comments on commit a0fe5c9

Please sign in to comment.