Skip to content

Commit

Permalink
Merge branch 'develop' into issue_1371_1372
Browse files Browse the repository at this point in the history
  • Loading branch information
mirceaulinic authored Mar 21, 2024
2 parents aa37562 + 077be75 commit 8ece950
Show file tree
Hide file tree
Showing 171 changed files with 3,491 additions and 2,465 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [3.7, 3.8, 3.9, 3.10.0]
python-version: [3.8, 3.9, 3.10.9, 3.11, 3.12.0]

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

Expand Down Expand Up @@ -54,10 +54,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pythonpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ env

test/unit/test_devices.py

.report.json
report.json
tags
.pytest_cache/
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
6 changes: 3 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,13 +371,13 @@ def build_getters_support_matrix(app):
if not (m.startswith("_") or m in EXCLUDE_METHODS)
}

regex_name = re.compile(r"(?P<driver>\w+)\/.*::test_(?P<getter>\w+)")
regex_name = re.compile(r"test.*/(?P<driver>\w+)\/.*::test_(?P<getter>\w+)")

filename = "./support/tests/report.json"
with open(filename, "r") as f:
data = json.loads(f.read())
for test in data["report"]["tests"]:
match = regex_name.search(test["name"])
for test in data["tests"]:
match = regex_name.search(test["nodeid"])
if match:
driver = match.group("driver")
drivers.add(driver)
Expand Down
55 changes: 44 additions & 11 deletions docs/development/testing_framework.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Testing Framework
-----------------

As napalm consists of multiple drivers and all of them have to provide similar functionality, we have developed a testing framework to provide a consistent test suite for all the drivers.
As NAPALM consists of multiple drivers and all of them have to provide similar functionality, we have developed a testing framework to provide a consistent test suite for all the drivers.

Features
________
Expand Down Expand Up @@ -42,7 +42,7 @@ By default, the tests are going to be run against mocked data but you can change
* ``NAPALM_USERNAME``
* ``NAPALM_PASSWORD``
* ``NAPALM_OPTIONAL_ARGS``

Mocking the ``open`` method
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -56,29 +56,29 @@ Multiple test cases::
(napalm) ➜ napalm-eos git:(test_framework) ✗ ls test/unit/mocked_data/test_get_bgp_neighbors
lots_of_peers no_peers normal
(napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors
...
...
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[lots_of_peers] <- ../napalm/napalm.base/test/getters.py PASSED
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[no_peers] <- ../napalm/napalm.base/test/getters.py PASSED
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[normal] <- ../napalm/napalm.base/test/getters.py PASSED

Missing test cases::

(napalm) ➜ napalm-eos git:(test_framework) ✗ ls test/unit/mocked_data/test_get_bgp_neighbors
ls: test/unit/mocked_data/test_get_bgp_neighbors: No such file or directory
(napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors
...
...
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[no_test_case_found] <- ../napalm/napalm.base/test/getters.py FAILED

========================================================= FAILURES ==========================================================
___________________________________ TestGetter.test_get_bgp_neighbors[no_test_case_found] ___________________________________

cls = <test_getters.TestGetter instance at 0x10ed5eb90>, test_case = 'no_test_case_found'

@functools.wraps(func)
def wrapper(cls, test_case):
cls.device.device.current_test = func.__name__
cls.device.device.current_test_case = test_case

try:
# This is an ugly, ugly, ugly hack because some python objects don't load
# as expected. For example, dicts where integers are strings
Expand All @@ -87,7 +87,7 @@ Missing test cases::
if test_case == "no_test_case_found":
> pytest.fail("No test case for '{}' found".format(func.__name__))
E Failed: No test case for 'test_get_bgp_neighbors' found

../napalm/napalm.base/test/getters.py:64: Failed
================================================= 1 failed in 0.12 seconds ==================================================

Expand All @@ -96,8 +96,41 @@ Method not implemented::
(napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_probes_config
...
test/unit/test_getters.py::TestGetter::test_get_probes_config[no_test_case_found] <- ../napalm/napalm.base/test/getters.py SKIPPED

================================================= 1 skipped in 0.09 seconds =================================================

Testing Matrix
--------------

NAPALM leverages [Github Actions](https://docs.github.com/en/actions) to test and lint code on commits and pull requests.
If you want to test prior to opening a pull request, you can use [nektos/act](https://github.com/nektos/act) and Docker to locally run the tests

.. code-block:: console
$ act -j std_tests
[build/std_tests-4] 🚀 Start image=catthehacker/ubuntu:act-latest
[build/std_tests-3] 🚀 Start image=catthehacker/ubuntu:act-latest
[build/std_tests-1] 🚀 Start image=catthehacker/ubuntu:act-latest
[build/std_tests-2] 🚀 Start image=catthehacker/ubuntu:act-latest
[build/std_tests-5] 🚀 Start image=catthehacker/ubuntu:act-latest
[build/std_tests-4] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[build/std_tests-1] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[build/std_tests-3] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[build/std_tests-5] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[build/std_tests-2] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
...
| ---------------------------------------------------------------------
| TOTAL 9258 1836 80%
|
| ================= 619 passed, 80 skipped, 3 warnings in 19.97s =================
[build/std_tests-5] ✅ Success - Main Run Tests
[build/std_tests-5] ⭐ Run Post Setup Python 3.11
[build/std_tests-5] 🐳 docker exec cmd=[node /var/run/act/actions/actions-setup-python@v2/dist/cache-save/index.js] user= workdir=
[build/std_tests-5] ✅ Success - Post Setup Python 3.11
[build/std_tests-5] 🏁 Job succeeded
.. _`test_getters.py`: https://github.com/napalm-automation/napalm-eos/blob/a2fc2cf6a98b0851efe4cba907086191b8f1df02/test/unit/test_getters.py
.. _`conftest.py`: https://github.com/napalm-automation/napalm-eos/blob/a2fc2cf6a98b0851efe4cba907086191b8f1df02/test/unit/conftest.py
7 changes: 4 additions & 3 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
urllib3==1.26.15 # https://github.com/readthedocs/readthedocs.org/issues/10290
sphinx==1.8.6
sphinx-rtd-theme==1.0.0
sphinx-rtd-theme==1.2.0
sphinxcontrib-napoleon==0.7
invoke==1.7.1
invoke==2.0.0
jinja2==2.11.3
MarkupSafe==2.0.1
pytest==7.1.2
pytest==7.2.2
ansible==4.10.0
1 change: 1 addition & 0 deletions docs/support/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ ____________________________________
* :code:`eos_fn0039_config` (eos) - Transform old style configuration to the new style, available beginning with EOS release 4.23.0, as per FN 0039. Beware
that enabling this option will change the configuration you're loading through NAPALM. Default: ``False`` (won't change your configuration commands).
.. versionadded:: 3.0.1
* :code:`force_cfg_session_invalid` (eos) - Force the config_session to be cleared in case of issues, like `discard_config` failure. (default: ``False``)

The transport argument
______________________
Expand Down
6 changes: 3 additions & 3 deletions docs/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ CWD=`pwd`
TEST_RESULTS_PATH="$CWD/support/tests"
REPOBASE=$CWD/..

if [ ! -f "report.json" ]; then
if [ ! -f ".report.json" ]; then
set -e
py.test --rootdir $REPOBASE -c /dev/null --cov=./ -vs --json=report.json $REPOBASE/test*/*/test_getters.py
pytest --rootdir $REPOBASE -c /dev/null --json-report --cov=./ -vs $REPOBASE/test*/*/test_getters.py

set -e
cp report.json $TEST_RESULTS_PATH/report.json
cp .report.json $TEST_RESULTS_PATH/report.json
fi
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.
18 changes: 4 additions & 14 deletions napalm/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from typing_extensions import Literal

from netmiko import ConnectHandler, NetMikoTimeoutException
from netutils.interface import canonical_interface_name

# local modules
import napalm.base.exceptions
Expand Down Expand Up @@ -647,7 +648,8 @@ def get_bgp_config(
:param neighbor: Returns the configuration of a specific BGP neighbor.
Main dictionary keys represent the group name and the values represent a dictionary having
the keys below. Neighbors which aren't members of a group will be stored in a key named "_":
the keys below. A default group named "_" will contain information regarding global
settings and any neighbors that are not members of a group.
* type (string)
* description (string)
Expand Down Expand Up @@ -740,7 +742,6 @@ def get_bgp_config(
def cli(
self, commands: List[str], encoding: str = "text"
) -> Dict[str, Union[str, Dict[str, Any]]]:

"""
Will execute a list of commands and return the output in a dictionary format.
Expand Down Expand Up @@ -770,7 +771,6 @@ def cli(
def get_bgp_neighbors_detail(
self, neighbor_address: str = ""
) -> Dict[str, models.PeerDetailsDict]:

"""
Returns a detailed view of the BGP neighbors as a dictionary of lists.
Expand Down Expand Up @@ -865,7 +865,6 @@ def get_bgp_neighbors_detail(
raise NotImplementedError

def get_arp_table(self, vrf: str = "") -> List[models.ARPTableDict]:

"""
Returns a list of dictionaries having the following set of keys:
* interface (string)
Expand Down Expand Up @@ -900,7 +899,6 @@ def get_arp_table(self, vrf: str = "") -> List[models.ARPTableDict]:
raise NotImplementedError

def get_ntp_peers(self) -> Dict[str, models.NTPPeerDict]:

"""
Returns the NTP peers configuration as dictionary.
The keys of the dictionary represent the IP Addresses of the peers.
Expand All @@ -920,7 +918,6 @@ def get_ntp_peers(self) -> Dict[str, models.NTPPeerDict]:
raise NotImplementedError

def get_ntp_servers(self) -> Dict[str, models.NTPServerDict]:

"""
Returns the NTP servers configuration as dictionary.
The keys of the dictionary represent the IP Addresses of the servers.
Expand All @@ -940,7 +937,6 @@ def get_ntp_servers(self) -> Dict[str, models.NTPServerDict]:
raise NotImplementedError

def get_ntp_stats(self) -> List[models.NTPStats]:

"""
Returns a list of NTP synchronization statistics.
Expand Down Expand Up @@ -977,7 +973,6 @@ def get_ntp_stats(self) -> List[models.NTPStats]:
raise NotImplementedError

def get_interfaces_ip(self) -> Dict[str, models.InterfacesIPDict]:

"""
Returns all configured IP addresses on all interfaces as a dictionary of dictionaries.
Keys of the main dictionary represent the name of the interface.
Expand Down Expand Up @@ -1031,7 +1026,6 @@ def get_interfaces_ip(self) -> Dict[str, models.InterfacesIPDict]:
raise NotImplementedError

def get_mac_address_table(self) -> List[models.MACAdressTable]:

"""
Returns a lists of dictionaries. Each dictionary represents an entry in the MAC Address
Table, having the following keys:
Expand Down Expand Up @@ -1084,7 +1078,6 @@ def get_mac_address_table(self) -> List[models.MACAdressTable]:
def get_route_to(
self, destination: str = "", protocol: str = "", longer: bool = False
) -> Dict[str, models.RouteDict]:

"""
Returns a dictionary of dictionaries containing details of all available routes to a
destination.
Expand Down Expand Up @@ -1159,7 +1152,6 @@ def get_route_to(
raise NotImplementedError

def get_snmp_information(self) -> models.SNMPDict:

"""
Returns a dict of dicts containing SNMP configuration.
Each inner dictionary contains these fields
Expand Down Expand Up @@ -1804,8 +1796,6 @@ def compliance_report(
def _canonical_int(self, interface: str) -> str:
"""Expose the helper function within this class."""
if self.use_canonical_interface is True:
return napalm.base.helpers.canonical_interface_name(
interface, addl_name_map=None
)
return canonical_interface_name(interface, addl_name_map=None)
else:
return interface
Loading

0 comments on commit 8ece950

Please sign in to comment.