Skip to content

Commit 98a87e3

Browse files
authored
Merge branch 'develop' into ios_bgp_duplicate_neighbor
2 parents b20ece2 + 1014cfa commit 98a87e3

File tree

169 files changed

+2630
-2458
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

169 files changed

+2630
-2458
lines changed

.github/workflows/commit.yaml

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ jobs:
1010
strategy:
1111
max-parallel: 4
1212
matrix:
13-
python-version: [3.7, 3.8, 3.9, 3.10.0]
13+
python-version: [3.8, 3.9, 3.10.9, 3.11, 3.12.0]
1414

1515
steps:
1616
- name: Checkout repository
17-
uses: actions/checkout@v2
17+
uses: actions/checkout@v3
1818

1919
- name: Setup Python ${{ matrix.python-version }}
20-
uses: actions/setup-python@v2
20+
uses: actions/setup-python@v4
2121
with:
2222
python-version: ${{ matrix.python-version }}
2323

@@ -54,10 +54,10 @@ jobs:
5454

5555
steps:
5656
- name: Checkout repository
57-
uses: actions/checkout@v2
57+
uses: actions/checkout@v3
5858

5959
- name: Setup Python ${{ matrix.python-version }}
60-
uses: actions/setup-python@v2
60+
uses: actions/setup-python@v4
6161
with:
6262
python-version: ${{ matrix.python-version }}
6363

.github/workflows/pythonpublish.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ jobs:
1212
deploy:
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v1
15+
- uses: actions/checkout@v3
1616
- name: Set up Python
17-
uses: actions/setup-python@v1
17+
uses: actions/setup-python@v4
1818
with:
1919
python-version: '3.x'
2020
- name: Install dependencies

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ env
6565

6666
test/unit/test_devices.py
6767

68+
.report.json
6869
report.json
6970
tags
7071
.pytest_cache/

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Install
3131
pip install napalm
3232
```
3333

34+
*Note*: Beginning with release 5.0.0 and later, NAPALM offers support for
35+
Python 3.8+ only.
36+
3437
*Note*: Beginning with release 4.0.0 and later, NAPALM offers support for
3538
Python 3.7+ only.
3639

docs/conf.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,13 @@ def build_getters_support_matrix(app):
371371
if not (m.startswith("_") or m in EXCLUDE_METHODS)
372372
}
373373

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

376376
filename = "./support/tests/report.json"
377377
with open(filename, "r") as f:
378378
data = json.loads(f.read())
379-
for test in data["report"]["tests"]:
380-
match = regex_name.search(test["name"])
379+
for test in data["tests"]:
380+
match = regex_name.search(test["nodeid"])
381381
if match:
382382
driver = match.group("driver")
383383
drivers.add(driver)

docs/development/testing_framework.rst

+44-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Testing Framework
22
-----------------
33

4-
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.
4+
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.
55

66
Features
77
________
@@ -42,7 +42,7 @@ By default, the tests are going to be run against mocked data but you can change
4242
* ``NAPALM_USERNAME``
4343
* ``NAPALM_PASSWORD``
4444
* ``NAPALM_OPTIONAL_ARGS``
45-
45+
4646
Mocking the ``open`` method
4747
^^^^^^^^^^^^^^^^^^^^^^^^^^^
4848

@@ -56,29 +56,29 @@ Multiple test cases::
5656
(napalm) ➜ napalm-eos git:(test_framework) ✗ ls test/unit/mocked_data/test_get_bgp_neighbors
5757
lots_of_peers no_peers normal
5858
(napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors
59-
...
59+
...
6060
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[lots_of_peers] <- ../napalm/napalm.base/test/getters.py PASSED
6161
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[no_peers] <- ../napalm/napalm.base/test/getters.py PASSED
6262
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[normal] <- ../napalm/napalm.base/test/getters.py PASSED
63-
63+
6464
Missing test cases::
6565

6666
(napalm) ➜ napalm-eos git:(test_framework) ✗ ls test/unit/mocked_data/test_get_bgp_neighbors
6767
ls: test/unit/mocked_data/test_get_bgp_neighbors: No such file or directory
6868
(napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors
69-
...
69+
...
7070
test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[no_test_case_found] <- ../napalm/napalm.base/test/getters.py FAILED
71-
71+
7272
========================================================= FAILURES ==========================================================
7373
___________________________________ TestGetter.test_get_bgp_neighbors[no_test_case_found] ___________________________________
74-
74+
7575
cls = <test_getters.TestGetter instance at 0x10ed5eb90>, test_case = 'no_test_case_found'
76-
76+
7777
@functools.wraps(func)
7878
def wrapper(cls, test_case):
7979
cls.device.device.current_test = func.__name__
8080
cls.device.device.current_test_case = test_case
81-
81+
8282
try:
8383
# This is an ugly, ugly, ugly hack because some python objects don't load
8484
# as expected. For example, dicts where integers are strings
@@ -87,7 +87,7 @@ Missing test cases::
8787
if test_case == "no_test_case_found":
8888
> pytest.fail("No test case for '{}' found".format(func.__name__))
8989
E Failed: No test case for 'test_get_bgp_neighbors' found
90-
90+
9191
../napalm/napalm.base/test/getters.py:64: Failed
9292
================================================= 1 failed in 0.12 seconds ==================================================
9393

@@ -96,8 +96,41 @@ Method not implemented::
9696
(napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_probes_config
9797
...
9898
test/unit/test_getters.py::TestGetter::test_get_probes_config[no_test_case_found] <- ../napalm/napalm.base/test/getters.py SKIPPED
99-
99+
100100
================================================= 1 skipped in 0.09 seconds =================================================
101101

102+
Testing Matrix
103+
--------------
104+
105+
NAPALM leverages [Github Actions](https://docs.github.com/en/actions) to test and lint code on commits and pull requests.
106+
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
107+
108+
.. code-block:: console
109+
110+
$ act -j std_tests
111+
[build/std_tests-4] 🚀 Start image=catthehacker/ubuntu:act-latest
112+
[build/std_tests-3] 🚀 Start image=catthehacker/ubuntu:act-latest
113+
[build/std_tests-1] 🚀 Start image=catthehacker/ubuntu:act-latest
114+
[build/std_tests-2] 🚀 Start image=catthehacker/ubuntu:act-latest
115+
[build/std_tests-5] 🚀 Start image=catthehacker/ubuntu:act-latest
116+
[build/std_tests-4] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
117+
[build/std_tests-1] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
118+
[build/std_tests-3] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
119+
[build/std_tests-5] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
120+
[build/std_tests-2] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
121+
122+
...
123+
124+
| ---------------------------------------------------------------------
125+
| TOTAL 9258 1836 80%
126+
|
127+
| ================= 619 passed, 80 skipped, 3 warnings in 19.97s =================
128+
[build/std_tests-5] ✅ Success - Main Run Tests
129+
[build/std_tests-5] ⭐ Run Post Setup Python 3.11
130+
[build/std_tests-5] 🐳 docker exec cmd=[node /var/run/act/actions/actions-setup-python@v2/dist/cache-save/index.js] user= workdir=
131+
[build/std_tests-5] ✅ Success - Post Setup Python 3.11
132+
[build/std_tests-5] 🏁 Job succeeded
133+
134+
102135
.. _`test_getters.py`: https://github.com/napalm-automation/napalm-eos/blob/a2fc2cf6a98b0851efe4cba907086191b8f1df02/test/unit/test_getters.py
103136
.. _`conftest.py`: https://github.com/napalm-automation/napalm-eos/blob/a2fc2cf6a98b0851efe4cba907086191b8f1df02/test/unit/conftest.py

docs/requirements.txt

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
urllib3==1.26.15 # https://github.com/readthedocs/readthedocs.org/issues/10290
12
sphinx==1.8.6
2-
sphinx-rtd-theme==1.0.0
3+
sphinx-rtd-theme==1.2.0
34
sphinxcontrib-napoleon==0.7
4-
invoke==1.7.1
5+
invoke==2.0.0
56
jinja2==2.11.3
67
MarkupSafe==2.0.1
7-
pytest==7.1.2
8+
pytest==7.2.2
89
ansible==4.10.0

docs/support/index.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ General support matrix
1313
===================== ========== ============= ==================== ================== ============ ============ ============
1414
**Driver Name** eos junos iosxr_netconf iosxr nxos nxos_ssh ios
1515
**Structured data** Yes Yes Yes No Yes No No
16-
**Minimum version** 4.15.0F 12.1 7.0 5.1.0 6.1 [#g1]_ 12.4(20)T 6.3.2
16+
**Minimum version** 4.15.0F 12.1 7.0 5.1.0 6.1 [#g1]_ 6.3.2 12.4(20)T
1717
**Backend library** `pyeapi`_ `junos-eznc`_ `ncclient`_ `pyIOSXR`_ `pynxos`_ `netmiko`_ `netmiko`_
1818
**Caveats** :doc:`eos` :doc:`iosxr_netconf` :doc:`nxos` :doc:`nxos` :doc:`ios`
1919
===================== ========== ============= ==================== ================== ============ ============ ============
@@ -141,6 +141,7 @@ ____________________________________
141141
* :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
142142
that enabling this option will change the configuration you're loading through NAPALM. Default: ``False`` (won't change your configuration commands).
143143
.. versionadded:: 3.0.1
144+
* :code:`force_cfg_session_invalid` (eos) - Force the config_session to be cleared in case of issues, like `discard_config` failure. (default: ``False``)
144145

145146
The transport argument
146147
______________________

docs/test.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ CWD=`pwd`
33
TEST_RESULTS_PATH="$CWD/support/tests"
44
REPOBASE=$CWD/..
55

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

1010
set -e
11-
cp report.json $TEST_RESULTS_PATH/report.json
11+
cp .report.json $TEST_RESULTS_PATH/report.json
1212
fi

docs/tutorials/extend_driver.rst

+26-18
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,26 @@ Bulding on the previous example, we can create a a simple parse to return what o
4444

4545
.. code-block:: python
4646
47-
def get_my_banner(self):
48-
command = 'show banner motd'
49-
output = self._send_command(command)
50-
51-
return_vars = {}
52-
for line in output.splitlines():
53-
split_line = line.split()
54-
if "Site:" == split_line[0]:
55-
return_vars["site"] = split_line[1]
56-
elif "Device:" == split_line[0]:
57-
return_vars["device"] = split_line[1]
58-
elif "Floor:" == split_line[0]:
59-
return_vars["floor"] = split_line[1]
60-
elif "Room:" == split_line[0]:
61-
return_vars["room"] = split_line[1]
62-
return return_vars
47+
from napalm.ios.ios import IOSDriver
48+
class CustomIOSDriver(IOSDriver):
49+
"""Custom NAPALM Cisco IOS Handler."""
50+
51+
def get_my_banner(self):
52+
command = 'show banner motd'
53+
output = self._send_command(command)
54+
55+
return_vars = {}
56+
for line in output.splitlines():
57+
split_line = line.split()
58+
if "Site:" == split_line[0]:
59+
return_vars["site"] = split_line[1]
60+
elif "Device:" == split_line[0]:
61+
return_vars["device"] = split_line[1]
62+
elif "Floor:" == split_line[0]:
63+
return_vars["floor"] = split_line[1]
64+
elif "Room:" == split_line[0]:
65+
return_vars["room"] = split_line[1]
66+
return return_vars
6367
6468
Which can build.
6569

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

8690
.. code-block:: python
8791
88-
def get_my_banner(self):
89-
raise NotImplementedError
92+
from napalm.ios.ios import IOSDriver
93+
class CustomIOSDriver(IOSDriver):
94+
"""Custom NAPALM Cisco IOS Handler."""
95+
96+
def get_my_banner(self):
97+
raise NotImplementedError
9098
9199
This feature is meant to allow for maximum amount of flexibility, but it is up to the user to ensure they do
92100
not run into namespace issues, and follow best practices.

napalm/base/base.py

+4-14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing_extensions import Literal
2020

2121
from netmiko import ConnectHandler, NetMikoTimeoutException
22+
from netutils.interface import canonical_interface_name
2223

2324
# local modules
2425
import napalm.base.exceptions
@@ -647,7 +648,8 @@ def get_bgp_config(
647648
:param neighbor: Returns the configuration of a specific BGP neighbor.
648649
649650
Main dictionary keys represent the group name and the values represent a dictionary having
650-
the keys below. Neighbors which aren't members of a group will be stored in a key named "_":
651+
the keys below. A default group named "_" will contain information regarding global
652+
settings and any neighbors that are not members of a group.
651653
652654
* type (string)
653655
* description (string)
@@ -740,7 +742,6 @@ def get_bgp_config(
740742
def cli(
741743
self, commands: List[str], encoding: str = "text"
742744
) -> Dict[str, Union[str, Dict[str, Any]]]:
743-
744745
"""
745746
Will execute a list of commands and return the output in a dictionary format.
746747
@@ -770,7 +771,6 @@ def cli(
770771
def get_bgp_neighbors_detail(
771772
self, neighbor_address: str = ""
772773
) -> Dict[str, models.PeerDetailsDict]:
773-
774774
"""
775775
Returns a detailed view of the BGP neighbors as a dictionary of lists.
776776
@@ -865,7 +865,6 @@ def get_bgp_neighbors_detail(
865865
raise NotImplementedError
866866

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

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

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

942939
def get_ntp_stats(self) -> List[models.NTPStats]:
943-
944940
"""
945941
Returns a list of NTP synchronization statistics.
946942
@@ -977,7 +973,6 @@ def get_ntp_stats(self) -> List[models.NTPStats]:
977973
raise NotImplementedError
978974

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

10331028
def get_mac_address_table(self) -> List[models.MACAdressTable]:
1034-
10351029
"""
10361030
Returns a lists of dictionaries. Each dictionary represents an entry in the MAC Address
10371031
Table, having the following keys:
@@ -1084,7 +1078,6 @@ def get_mac_address_table(self) -> List[models.MACAdressTable]:
10841078
def get_route_to(
10851079
self, destination: str = "", protocol: str = "", longer: bool = False
10861080
) -> Dict[str, models.RouteDict]:
1087-
10881081
"""
10891082
Returns a dictionary of dictionaries containing details of all available routes to a
10901083
destination.
@@ -1159,7 +1152,6 @@ def get_route_to(
11591152
raise NotImplementedError
11601153

11611154
def get_snmp_information(self) -> models.SNMPDict:
1162-
11631155
"""
11641156
Returns a dict of dicts containing SNMP configuration.
11651157
Each inner dictionary contains these fields
@@ -1804,8 +1796,6 @@ def compliance_report(
18041796
def _canonical_int(self, interface: str) -> str:
18051797
"""Expose the helper function within this class."""
18061798
if self.use_canonical_interface is True:
1807-
return napalm.base.helpers.canonical_interface_name(
1808-
interface, addl_name_map=None
1809-
)
1799+
return canonical_interface_name(interface, addl_name_map=None)
18101800
else:
18111801
return interface

0 commit comments

Comments
 (0)