diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml index f7d265f1f..0329a5554 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/commit.yaml @@ -10,14 +10,14 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.7, 3.8, 3.9, 3.10.0] + python-version: [3.7, 3.8, 3.9, 3.10.9, 3.11] 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 }} @@ -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 }} @@ -65,9 +65,9 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -e . - pip install -r requirements.txt - pip install -r requirements-dev.txt python -m pip install -r docs/requirements.txt + pip install -r requirements-dev.txt + pip install -r requirements.txt - name: Doctests run: | diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index 1fba0b41b..ed1ba114e 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -12,19 +12,20 @@ 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 run: | python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + pip install setuptools wheel + - name: Build run: | python setup.py sdist bdist_wheel - twine upload dist/* + - name: Publish + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index a00f7f807..87330b57c 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ env test/unit/test_devices.py +.report.json report.json tags .pytest_cache/ diff --git a/.readthedocs.yml b/.readthedocs.yml index 65fd950da..320d0121d 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,9 +14,10 @@ formats: all # Optionally set the version of Python and requirements required to build your docs python: - version: 3.7 + version: 3.8 install: - method: pip path: . - requirements: docs/requirements.txt + - requirements: requirements-dev.txt - requirements: requirements.txt diff --git a/docs/conf.py b/docs/conf.py index cf56e0537..d9b5774cd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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\w+)\/.*::test_(?P\w+)") + regex_name = re.compile(r"test.*/(?P\w+)\/.*::test_(?P\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) diff --git a/docs/development/testing_framework.rst b/docs/development/testing_framework.rst index 2b61cc9da..ef5a7ed7f 100644 --- a/docs/development/testing_framework.rst +++ b/docs/development/testing_framework.rst @@ -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 ________ @@ -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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -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_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 @@ -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 ================================================== @@ -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 diff --git a/docs/requirements.txt b/docs/requirements.txt index f822bffdb..24e3fa96b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,9 @@ -sphinx -sphinx-rtd-theme -sphinxcontrib-napoleon -invoke +urllib3==1.26.15 # https://github.com/readthedocs/readthedocs.org/issues/10290 +sphinx==1.8.6 +sphinx-rtd-theme==1.2.0 +sphinxcontrib-napoleon==0.7 +invoke==2.0.0 +jinja2==2.11.3 +MarkupSafe==2.0.1 +pytest==7.2.2 +ansible==4.10.0 diff --git a/docs/support/index.rst b/docs/support/index.rst index f869f592c..78ad1191a 100644 --- a/docs/support/index.rst +++ b/docs/support/index.rst @@ -13,7 +13,7 @@ General support matrix ===================== ========== ============= ==================== ================== ============ ============ ============ **Driver Name** eos junos iosxr_netconf iosxr nxos nxos_ssh ios **Structured data** Yes Yes Yes No Yes No No - **Minimum version** 4.15.0F 12.1 7.0 5.1.0 6.1 [#g1]_ 12.4(20)T 6.3.2 + **Minimum version** 4.15.0F 12.1 7.0 5.1.0 6.1 [#g1]_ 6.3.2 12.4(20)T **Backend library** `pyeapi`_ `junos-eznc`_ `ncclient`_ `pyIOSXR`_ `pynxos`_ `netmiko`_ `netmiko`_ **Caveats** :doc:`eos` :doc:`iosxr_netconf` :doc:`nxos` :doc:`nxos` :doc:`ios` ===================== ========== ============= ==================== ================== ============ ============ ============ @@ -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 ______________________ diff --git a/docs/support/ios.rst b/docs/support/ios.rst index b46a92a54..1bbc5116e 100644 --- a/docs/support/ios.rst +++ b/docs/support/ios.rst @@ -116,7 +116,7 @@ File Operation Prompts ______________________ By default IOS will prompt for confirmation on file operations. These prompts need to be disabled before the NAPALM-ios driver performs any such operation on the device. -This can be controlled using the `auto_file_prompt` optional arguement: +This can be controlled using the `auto_file_prompt` optional argument: * `auto_file_prompt=True` (default): NAPALM will automatically add `file prompt quiet` to the device configuration before performing file operations, and un-configure it again afterwards. If the device already had the command in its configuration then it will be silently removed as a result, and diff --git a/docs/support/nxos.rst b/docs/support/nxos.rst index 0dfff92be..9f9e5ee8b 100644 --- a/docs/support/nxos.rst +++ b/docs/support/nxos.rst @@ -71,7 +71,7 @@ One caveat of using netutils diff of configurations is that the diff is performe Example assuming that the device config contains: -.. code-block:: +.. code-block:: bash interface loopback0 ip address 10.1.4.4/32 diff --git a/docs/test.sh b/docs/test.sh index 78a572fac..3d102c74a 100755 --- a/docs/test.sh +++ b/docs/test.sh @@ -1,14 +1,12 @@ #!/bin/bash CWD=`pwd` TEST_RESULTS_PATH="$CWD/support/tests" +REPOBASE=$CWD/.. -if [ ! -f "report.json" ]; then +if [ ! -f ".report.json" ]; then set -e - pip install -r ../requirements.txt -r ../requirements-dev.txt + pytest --rootdir $REPOBASE -c /dev/null --json-report --cov=./ -vs $REPOBASE/test*/*/test_getters.py - set +e - py.test -c /dev/null --cov=./ -vs --json=report.json ../test*/*/test_getters.py set -e - - cp report.json $TEST_RESULTS_PATH/report.json + cp .report.json $TEST_RESULTS_PATH/report.json fi diff --git a/napalm/base/base.py b/napalm/base/base.py index 1259f6966..7410bf6a8 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -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 @@ -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) @@ -1804,8 +1806,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 diff --git a/napalm/base/canonical_map.py b/napalm/base/canonical_map.py index 29b50e714..fb9658213 100644 --- a/napalm/base/canonical_map.py +++ b/napalm/base/canonical_map.py @@ -1,153 +1,4 @@ -base_interfaces = { - "ATM": "ATM", - "AT": "ATM", - "B": "Bdi", - "Bd": "Bdi", - "Bdi": "Bdi", - "EOBC": "EOBC", - "EO": "EOBC", - "Ethernet": "Ethernet", - "Eth": "Ethernet", - "eth": "Ethernet", - "Et": "Ethernet", - "et": "Ethernet", - "FastEthernet": "FastEthernet", - "FastEth": "FastEthernet", - "FastE": "FastEthernet", - "Fast": "FastEthernet", - "Fas": "FastEthernet", - "FE": "FastEthernet", - "Fa": "FastEthernet", - "fa": "FastEthernet", - "Fddi": "Fddi", - "FD": "Fddi", - "FortyGigabitEthernet": "FortyGigabitEthernet", - "FortyGigEthernet": "FortyGigabitEthernet", - "FortyGigEth": "FortyGigabitEthernet", - "FortyGigE": "FortyGigabitEthernet", - "FortyGig": "FortyGigabitEthernet", - "FGE": "FortyGigabitEthernet", - "FO": "FortyGigabitEthernet", - "Fo": "FortyGigabitEthernet", - "FiftyGigabitEthernet": "FiftyGigabitEthernet", - "FiftyGigEthernet": "FiftyGigabitEthernet", - "FiftyGigEth": "FiftyGigabitEthernet", - "FiftyGigE": "FiftyGigabitEthernet", - "FI": "FiftyGigabitEthernet", - "Fi": "FiftyGigabitEthernet", - "fi": "FiftyGigabitEthernet", - "GigabitEthernet": "GigabitEthernet", - "GigEthernet": "GigabitEthernet", - "GigEth": "GigabitEthernet", - "GigE": "GigabitEthernet", - "Gig": "GigabitEthernet", - "GE": "GigabitEthernet", - "Ge": "GigabitEthernet", - "ge": "GigabitEthernet", - "Gi": "GigabitEthernet", - "gi": "GigabitEthernet", - "HundredGigabitEthernet": "HundredGigabitEthernet", - "HundredGigEthernet": "HundredGigabitEthernet", - "HundredGigEth": "HundredGigabitEthernet", - "HundredGigE": "HundredGigabitEthernet", - "HundredGig": "HundredGigabitEthernet", - "Hu": "HundredGigabitEthernet", - "TwentyFiveGigabitEthernet": "TwentyFiveGigabitEthernet", - "TwentyFiveGigEthernet": "TwentyFiveGigabitEthernet", - "TwentyFiveGigEth": "TwentyFiveGigabitEthernet", - "TwentyFiveGigE": "TwentyFiveGigabitEthernet", - "TwentyFiveGig": "TwentyFiveGigabitEthernet", - "TF": "TwentyFiveGigabitEthernet", - "Tf": "TwentyFiveGigabitEthernet", - "tf": "TwentyFiveGigabitEthernet", - "TwoHundredGigabitEthernet": "TwoHundredGigabitEthernet", - "TwoHundredGigEthernet": "TwoHundredGigabitEthernet", - "TwoHundredGigEth": "TwoHundredGigabitEthernet", - "TwoHundredGigE": "TwoHundredGigabitEthernet", - "TwoHundredGig": "TwoHundredGigabitEthernet", - "TH": "TwoHundredGigabitEthernet", - "Th": "TwoHundredGigabitEthernet", - "th": "TwoHundredGigabitEthernet", - "FourHundredGigabitEthernet": "FourHundredGigabitEthernet", - "FourHundredGigEthernet": "FourHundredGigabitEthernet", - "FourHundredGigEth": "FourHundredGigabitEthernet", - "FourHundredGigE": "FourHundredGigabitEthernet", - "FourHundredGig": "FourHundredGigabitEthernet", - "F": "FourHundredGigabitEthernet", - "f": "FourHundredGigabitEthernet", - "Loopback": "Loopback", - "loopback": "Loopback", - "Lo": "Loopback", - "lo": "Loopback", - "Management": "Management", - "Mgmt": "Management", - "mgmt": "Management", - "Ma": "Management", - "Management_short": "Ma", - "MFR": "MFR", - "Multilink": "Multilink", - "Mu": "Multilink", - "n": "nve", - "nv": "nve", - "nve": "nve", - "PortChannel": "Port-channel", - "Port-channel": "Port-channel", - "Port-Channel": "Port-channel", - "port-channel": "Port-channel", - "po": "Port-channel", - "Po": "Port-channel", - "POS": "POS", - "PO": "POS", - "Serial": "Serial", - "Se": "Serial", - "S": "Serial", - "TenGigabitEthernet": "TenGigabitEthernet", - "TenGigEthernet": "TenGigabitEthernet", - "TenGigEth": "TenGigabitEthernet", - "TenGig": "TenGigabitEthernet", - "TeGig": "TenGigabitEthernet", - "Ten": "TenGigabitEthernet", - "T": "TenGigabitEthernet", - "Te": "TenGigabitEthernet", - "te": "TenGigabitEthernet", - "Tunnel": "Tunnel", - "Tun": "Tunnel", - "Tu": "Tunnel", - "Twe": "TwentyFiveGigE", - "Tw": "TwoGigabitEthernet", - "Two": "TwoGigabitEthernet", - "Virtual-Access": "Virtual-Access", - "Vi": "Virtual-Access", - "Virtual-Template": "Virtual-Template", - "Vt": "Virtual-Template", - "VLAN": "VLAN", - "V": "VLAN", - "Vl": "VLAN", - "Wlan-GigabitEthernet": "Wlan-GigabitEthernet", -} - -reverse_mapping = { - "ATM": "At", - "EOBC": "EO", - "Ethernet": "Et", - "FastEthernet": "Fa", - "Fddi": "FD", - "FortyGigabitEthernet": "Fo", - "GigabitEthernet": "Gi", - "HundredGigabitEthernet": "Hu", - "Loopback": "Lo", - "Management": "Ma", - "MFR": "MFR", - "Multilink": "Mu", - "Port-channel": "Po", - "POS": "PO", - "Serial": "Se", - "TenGigabitEthernet": "Te", - "Tunnel": "Tu", - "TwoGigabitEthernet": "Two", - "TwentyFiveGigE": "Twe", - "Virtual-Access": "Vi", - "Virtual-Template": "Vt", - "VLAN": "Vl", - "Wlan-GigabitEthernet": "Wl-Gi", -} +# Do not remove the below imports, functions were moved to netutils, but to not +# break backwards compatibility, these should remain +from netutils.constants import BASE_INTERFACES as base_interfaces # noqa +from netutils.constants import REVERSE_MAPPING as reverse_mapping # noqa diff --git a/napalm/base/helpers.py b/napalm/base/helpers.py index 935dfd58d..dd58ddc7a 100644 --- a/napalm/base/helpers.py +++ b/napalm/base/helpers.py @@ -1,4 +1,5 @@ """Helper functions for the NAPALM base.""" +import ipaddress import itertools import logging @@ -14,10 +15,17 @@ import textfsm from lxml import etree from netaddr import EUI -from netaddr import IPAddress from netaddr import mac_unix from netutils.config.parser import IOSConfigParser +# Do not remove the below imports, functions were moved to netutils, but to not +# break backwards compatibility, these should remain +from netutils.interface import abbreviated_interface_name # noqa +from netutils.interface import canonical_interface_name # noqa +from netutils.constants import BASE_INTERFACES as base_interfaces # noqa +from netutils.constants import REVERSE_MAPPING as reverse_mapping # noqa +from netutils.interface import split_interface as _split_interface + try: from ttp import quick_parse as ttp_quick_parse @@ -30,7 +38,6 @@ from napalm.base import constants from napalm.base.models import ConfigDict from napalm.base.utils.jinja_filters import CustomJinjaFilters -from napalm.base.canonical_map import base_interfaces, reverse_mapping T = TypeVar("T") R = TypeVar("R") @@ -537,10 +544,19 @@ def ip(addr: str, version: Optional[int] = None) -> str: >>> ip('2001:0dB8:85a3:0000:0000:8A2e:0370:7334') u'2001:db8:85a3::8a2e:370:7334' """ - addr_obj = IPAddress(addr) + scope = "" + if "%" in addr: + addr, scope = addr.split("%", 1) + addr_obj = ipaddress.ip_address(addr) if version and addr_obj.version != version: raise ValueError("{} is not an ipv{} address".format(addr, version)) - return str(addr_obj) + if addr_obj.version == 6 and addr_obj.ipv4_mapped is not None: + return_addr = "%s:%s" % ("::ffff", addr_obj.ipv4_mapped) + else: + return_addr = str(addr_obj) + if scope: + return_addr = "%s%%%s" % (return_addr, scope) + return return_addr def as_number(as_number_val: str) -> int: @@ -555,92 +571,7 @@ def as_number(as_number_val: str) -> int: def split_interface(intf_name: str) -> Tuple[str, str]: """Split an interface name based on first digit, slash, or space match.""" - head = intf_name.rstrip(r"/\0123456789. ") - tail = intf_name[len(head) :].lstrip() - return (head, tail) - - -def canonical_interface_name( - interface: str, addl_name_map: Optional[Dict[str, str]] = None -) -> str: - """Function to return an interface's canonical name (fully expanded name). - - Use of explicit matches used to indicate a clear understanding on any potential - match. Regex and other looser matching methods were not implmented to avoid false - positive matches. As an example, it would make sense to do "[P|p][O|o]" which would - incorrectly match PO = POS and Po = Port-channel, leading to a false positive, not - easily troubleshot, found, or known. - - :param interface: The interface you are attempting to expand. - :param addl_name_map: A dict containing key/value pairs that updates - the base mapping. Used if an OS has specific differences. e.g. {"Po": "PortChannel"} vs - {"Po": "Port-Channel"} - :type addl_name_map: optional - """ - - name_map = {} - name_map.update(base_interfaces) - interface_type, interface_number = split_interface(interface) - - if isinstance(addl_name_map, dict): - name_map.update(addl_name_map) - # check in dict for mapping - if name_map.get(interface_type): - long_int = name_map.get(interface_type) - assert isinstance(long_int, str) - return long_int + str(interface_number) - # if nothing matched, return the original name - else: - return interface - - -def abbreviated_interface_name( - interface: str, - addl_name_map: Optional[Dict[str, str]] = None, - addl_reverse_map: Optional[Dict[str, str]] = None, -) -> str: - """Function to return an abbreviated representation of the interface name. - - :param interface: The interface you are attempting to abbreviate. - :param addl_name_map: A dict containing key/value pairs that updates - the base mapping. Used if an OS has specific differences. e.g. {"Po": "PortChannel"} vs - {"Po": "Port-Channel"} - :type addl_name_map: optional - :param addl_reverse_map: A dict containing key/value pairs that updates - the reverse mapping. Used if an OS has specific differences. e.g. {"PortChannel": "Po"} vs - {"PortChannel": "po"} - :type addl_reverse_map: optional - """ - - name_map = {} - name_map.update(base_interfaces) - interface_type, interface_number = split_interface(interface) - - if isinstance(addl_name_map, dict): - name_map.update(addl_name_map) - - rev_name_map = {} - rev_name_map.update(reverse_mapping) - - if isinstance(addl_reverse_map, dict): - rev_name_map.update(addl_reverse_map) - - # Try to ensure canonical type. - if name_map.get(interface_type): - canonical_type = name_map.get(interface_type) - else: - canonical_type = interface_type - - assert isinstance(canonical_type, str) - - try: - abbreviated_name = rev_name_map[canonical_type] + str(interface_number) - return abbreviated_name - except KeyError: - pass - - # If abbreviated name lookup fails, return original name - return interface + return _split_interface(interface=intf_name) def transform_lldp_capab(capabilities: Union[str, Any]) -> List[str]: diff --git a/napalm/base/test/getters.py b/napalm/base/test/getters.py index cc64f551e..0a2e47a66 100644 --- a/napalm/base/test/getters.py +++ b/napalm/base/test/getters.py @@ -244,7 +244,7 @@ def test_get_lldp_neighbors_detail(self, test_case): def test_get_bgp_config(self, test_case): """Test get_bgp_config.""" get_bgp_config = self.device.get_bgp_config() - assert len(get_bgp_config) > 0 + assert get_bgp_config == {} or len(get_bgp_config) > 0 for bgp_group in get_bgp_config.values(): assert helpers.test_model(models.BPGConfigGroupDict, bgp_group) diff --git a/napalm/base/utils/string_parsers.py b/napalm/base/utils/string_parsers.py index c7dd8f376..f14f10e8b 100644 --- a/napalm/base/utils/string_parsers.py +++ b/napalm/base/utils/string_parsers.py @@ -1,6 +1,7 @@ """ Common methods to normalize a string """ import re -from typing import Union, List, Iterable, Dict, Optional +import struct +from typing import Union, List, Iterable, Dict, Optional, Tuple def convert(text: str) -> Union[str, int]: @@ -50,7 +51,7 @@ def colon_separated_string_to_dict( dictionary[line_data[0].strip()] = None else: raise Exception( - "Something went wrong parsing the colo separated string {}".format(line) + f"Something went wrong parsing the colon separated string:\n\n{line}" ) return dictionary @@ -133,3 +134,14 @@ def convert_uptime_string_seconds(uptime: str) -> int: raise Exception("Unrecognized uptime string:{}".format(uptime)) return uptime_seconds + + +def parse_fixed_width(text: str, *fields: int) -> List[Tuple[str, ...]]: + len = sum(fields) + fmtstring = " ".join(f"{fw}s" for fw in fields) + unpack = struct.Struct(fmtstring).unpack_from + + def parse(line: str) -> Tuple[str, ...]: + return tuple([str(s.decode()) for s in unpack(line.ljust(len).encode())]) + + return [parse(s) for s in text.splitlines()] diff --git a/napalm/eos/eos.py b/napalm/eos/eos.py index b38ae4e7d..d5554f434 100644 --- a/napalm/eos/eos.py +++ b/napalm/eos/eos.py @@ -23,15 +23,12 @@ import time import importlib import inspect +import ipaddress import json import socket from datetime import datetime from collections import defaultdict -from netaddr import IPAddress -from netaddr import IPNetwork - -from netaddr.core import AddrFormatError # third party libs import pyeapi @@ -40,6 +37,7 @@ # NAPALM base import napalm.base.helpers +from napalm.base.netmiko_helpers import netmiko_args from napalm.base.base import NetworkDriver from napalm.base.utils import string_parsers from napalm.base.exceptions import ( @@ -98,9 +96,12 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None) Optional args: * lock_disable (True/False): force configuration lock to be disabled (for external lock management). + * force_cfg_session_invalid (True/False): force invalidation of the config session + in case of failure. * enable_password (True/False): Enable password for privilege elevation * eos_autoComplete (True/False): Allow for shortening of cli commands - * transport (string): pyeapi transport, defaults to eos_transport if set + * transport (string): transport, eos_transport is a fallback for compatibility. + - ssh (uses Netmiko) - socket - http_local - http @@ -126,45 +127,47 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None) self.platform = "eos" self.profile = [self.platform] + self.optional_args = optional_args or {} - self._process_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) - def _process_optional_args(self, optional_args): # Define locking method - self.lock_disable = optional_args.get("lock_disable", False) + self.lock_disable = self.optional_args.pop("lock_disable", False) + + self.force_cfg_session_invalid = self.optional_args.pop( + "force_cfg_session_invalid", False + ) - self.enablepwd = optional_args.pop("enable_password", "") - self.eos_autoComplete = optional_args.pop("eos_autoComplete", None) # eos_transport is there for backwards compatibility, transport is the preferred method - transport = optional_args.get( - "transport", optional_args.get("eos_transport", "https") + transport = self.optional_args.get( + "transport", self.optional_args.get("eos_transport", "https") ) - self.fn0039_config = optional_args.pop("eos_fn0039_config", False) self.transport = transport - if transport == "ssh": - self.transport_class = "ssh" - init_args = ["port"] - else: - # Parse pyeapi transport class - self.transport_class = self._parse_transport(transport) - # ([1:]) to omit self - init_args = inspect.getfullargspec(self.transport_class.__init__)[0][1:] - - filter_args = ["host", "username", "password", "timeout", "lock_disable"] if transport == "ssh": - self.netmiko_optional_args = { - k: v - for k, v in optional_args.items() - if k in init_args and k not in filter_args - } + self._process_optional_args_ssh(self.optional_args) else: - init_args.append("enforce_verification") # Not an arg for unknown reason - self.eapi_kwargs = { - k: v - for k, v in optional_args.items() - if k in init_args and k not in filter_args - } + self._process_optional_args_eapi(self.optional_args) + + def _process_optional_args_ssh(self, optional_args): + self.transport_class = None + self.netmiko_optional_args = netmiko_args(optional_args) + + def _process_optional_args_eapi(self, optional_args): + # Parse pyeapi transport class + self.transport_class = self._parse_transport(self.transport) + + # ([1:]) to omit self + init_args = inspect.getfullargspec(self.transport_class.__init__)[0][1:] + filter_args = ["host", "username", "password", "timeout"] + init_args.append("enforce_verification") # Not an arg for unknown reason + self.eapi_kwargs = { + k: v + for k, v in optional_args.items() + if k in init_args and k not in filter_args + } def _parse_transport(self, transport): if inspect.isclass(transport) and issubclass(transport, EapiConnection): @@ -195,7 +198,8 @@ def open(self): """Implementation of NAPALM method open.""" if self.transport == "ssh": self.device = self._netmiko_open( - "arista_eos", netmiko_optional_args=self.netmiko_optional_args + 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"]) @@ -208,7 +212,7 @@ def open(self): username=self.username, password=self.password, timeout=self.timeout, - **self.eapi_kwargs + **self.eapi_kwargs, ) if self.device is None: @@ -231,7 +235,9 @@ def open(self): def close(self): """Implementation of NAPALM method close.""" self.discard_config() - if hasattr(self.device.connection, "close") and callable( + if self.transport == "ssh": + self._netmiko_close() + elif hasattr(self.device.connection, "close") and callable( self.device.connection.close ): self.device.connection.close() @@ -282,6 +288,27 @@ def _run_commands(self, commands, **kwargs): else: return self.device.run_commands(commands, **kwargs) + def _obtain_lock(self, wait_time=None): + """ + EOS internally creates config sessions when using commit-confirm. + + This can cause issues obtaining the configuration lock: + + cfg-2034--574620864-0 completed + cfg-2034--574620864-1 pending + """ + if wait_time: + start_time = time.time() + while time.time() - start_time < wait_time: + try: + self._lock() + return + except SessionLockedException: + time.sleep(1) + + # One last try + return self._lock() + def _lock(self): sess = self._run_commands(["show configuration sessions"])[0]["sessions"] if [ @@ -385,7 +412,7 @@ def _load_config(self, filename=None, config=None, replace=True): self.config_session = "napalm_{}".format(datetime.now().microsecond) if not self.lock_disable: - self._lock() + self._obtain_lock(wait_time=10) commands = [] commands.append("configure session {}".format(self.config_session)) @@ -430,7 +457,7 @@ def _load_config(self, filename=None, config=None, replace=True): return None try: - if not any(l == "end" for l in commands): + if not any(cmd == "end" for cmd in commands): commands.append("end") # exit config mode if self.eos_autoComplete is not None: self._run_commands( @@ -521,13 +548,15 @@ def confirm_commit(self): def discard_config(self): """Implementation of NAPALM method discard_config.""" if self.config_session is not None: - commands = ["configure session {}".format(self.config_session), "abort"] - if self.transport == "ssh": - # For some reason when testing with vEOS 4.26.1F this - # doesn't work with the normal wrapper. - self._run_commands(["", commands[0]]) - else: - self.device.run_commands(commands) + try: + commands = [f"configure session {self.config_session} abort"] + self._run_commands(commands, encoding="text") + except Exception: + # If discard fails, you might want to invalidate the config_session (esp. Salt) + # The config_session in EOS is used as the config lock. + if self.force_cfg_session_invalid: + self.config_session = None + raise self.config_session = None def rollback(self): @@ -656,13 +685,13 @@ def get_interfaces_counters(self): def get_bgp_neighbors(self): def get_re_group(res, key, default=None): - """Small helper to retrive data from re match groups""" + """Small helper to retrieve data from re match groups""" try: return res.group(key) except KeyError: return default - NEIGHBOR_FILTER = "bgp neighbors vrf all | include remote AS | remote router ID |IPv[46] (Unicast|6PE):.*[0-9]+|^Local AS|Desc|BGP state" # noqa + 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 output_summary_cmds = self._run_commands( ["show ipv6 bgp summary vrf all", "show ip bgp summary vrf all"], encoding="json", @@ -977,6 +1006,7 @@ def get_bgp_config(self, group="", neighbor=""): "local-v4-addr": "local_address", "local-v6-addr": "local_address", "local-as": "local_as", + "next-hop-self": "nhs", "description": "description", "import-policy": "import_policy", "export-policy": "export_policy", @@ -1034,7 +1064,7 @@ def default_group_dict(local_as): ) # few more default values return group_dict - def default_neighbor_dict(local_as): + def default_neighbor_dict(local_as, group_dict): neighbor_dict = {} neighbor_dict.update( { @@ -1045,6 +1075,13 @@ def default_neighbor_dict(local_as): neighbor_dict.update( {"prefix_limit": {}, "local_as": local_as, "authentication_key": ""} ) # few more default values + neighbor_dict.update( + { + key: group_dict.get(key) + for key in _GROUP_FIELD_MAP_.values() + if key in group_dict and key in _PEER_FIELD_MAP_.values() + } + ) # copy in values from group dict if present return neighbor_dict def parse_options(options, default_value=False): @@ -1132,17 +1169,23 @@ def parse_options(options, default_value=False): # will try to parse the neighbor name # which sometimes is the IP Address of the neigbor # or the name of the BGP group - IPAddress(group_or_neighbor) + ipaddress.ip_address(group_or_neighbor) # if passes the test => it is an IP Address, thus a Neighbor! peer_address = group_or_neighbor - if peer_address not in bgp_neighbors: - bgp_neighbors[peer_address] = default_neighbor_dict(local_as) + group_name = None if options[0] == "peer-group": - bgp_neighbors[peer_address]["__group"] = options[1] + group_name = options[1] # EOS > 4.23.0 only supports the new syntax # https://www.arista.com/en/support/advisories-notices/fieldnotices/7097-field-notice-39 elif options[0] == "peer" and options[1] == "group": - bgp_neighbors[peer_address]["__group"] = options[2] + group_name = options[2] + if peer_address not in bgp_neighbors: + bgp_neighbors[peer_address] = default_neighbor_dict( + local_as, bgp_config.get(group_name, {}) + ) + + if group_name: + bgp_neighbors[peer_address]["__group"] = group_name # in the config, neighbor details are lister after # the group is specified for the neighbor: @@ -1160,7 +1203,7 @@ def parse_options(options, default_value=False): bgp_neighbors[peer_address].update( parse_options(options, default_value) ) - except AddrFormatError: + except ValueError: # exception trying to parse group name # group_or_neighbor represents the name of the group group_name = group_or_neighbor @@ -1170,6 +1213,8 @@ def parse_options(options, default_value=False): bgp_config[group_name] = default_group_dict(local_as) bgp_config[group_name].update(parse_options(options, default_value)) + bgp_config["_"] = default_group_dict(local_as) + for peer, peer_details in bgp_neighbors.items(): peer_group = peer_details.pop("__group", None) if not peer_group: @@ -1178,6 +1223,14 @@ def parse_options(options, default_value=False): bgp_config[peer_group] = default_group_dict(local_as) bgp_config[peer_group]["neighbors"][peer] = peer_details + [ + v.pop("nhs", None) for v in bgp_config.values() + ] # remove NHS from group-level dictionary + + if local_as == 0: + # BGP not running + return {} + return bgp_config def get_arp_table(self, vrf=""): @@ -1423,7 +1476,7 @@ def get_route_to(self, destination="", protocol="", longer=False): protocol = "connected" ipv = "" - if IPNetwork(destination).version == 6: + if ipaddress.ip_network(destination).version == 6: ipv = "v6" commands = [] @@ -1530,7 +1583,7 @@ def get_route_to(self, destination="", protocol="", longer=False): .get("peerEntry", {}) .get("peerAddr", "") ) - except AddrFormatError: + except ValueError: remote_address = napalm.base.helpers.ip( bgp_route_details.get("peerEntry", {}).get( "peerAddr", "" @@ -1909,7 +1962,7 @@ def _append(bgp_dict, peer_info): summary_commands.append("show ipv6 bgp summary vrf all") else: try: - peer_ver = IPAddress(neighbor_address).version + peer_ver = ipaddress.ip_address(neighbor_address).version except Exception as e: raise e @@ -2077,15 +2130,59 @@ def get_config(self, retrieve="all", full=False, sanitized=False): else: raise Exception("Wrong retrieve filter: {}".format(retrieve)) - def _show_vrf(self): + def _show_vrf_json(self): + commands = ["show vrf"] + + vrfs = self._run_commands(commands)[0]["vrfs"] + return [ + { + "name": k, + "interfaces": [i for i in v["interfaces"]], + "route_distinguisher": v["routeDistinguisher"], + } + for k, v in vrfs.items() + ] + + def _show_vrf_text(self): commands = ["show vrf"] - # This command has no JSON yet + # This command has no JSON in EOS < 4.23 raw_output = self._run_commands(commands, encoding="text")[0].get("output", "") - output = napalm.base.helpers.textfsm_extractor(self, "vrf", raw_output) + width_line = raw_output.splitlines()[2] # Line with dashes + fields = width_line.split(" ") + widths = [len(f) + 1 for f in fields] + widths[-1] -= 1 + + parsed_lines = string_parsers.parse_fixed_width(raw_output, *widths) + + vrfs = [] + vrf = {} + current_vrf = None + for line in parsed_lines[3:]: + line = [t.strip() for t in line] + if line[0]: + if current_vrf: + vrfs.append(vrf) + current_vrf = line[0] + vrf = { + "name": current_vrf, + "interfaces": list(), + } + if line[1]: + vrf["route_distinguisher"] = line[1] + if line[4]: + vrf["interfaces"].extend([t.strip() for t in line[4].split(",") if t]) + if current_vrf: + vrfs.append(vrf) + + return vrfs - return output + def _show_vrf(self): + if self.cli_version == 2: + return self._show_vrf_json() + else: + return self._show_vrf_text() def _get_vrfs(self): output = self._show_vrf() @@ -2118,23 +2215,26 @@ def get_network_instances(self, name=""): interfaces[str(line.strip())] = {} all_vrf_interfaces[str(line.strip())] = {} - vrfs[str(vrf["name"])] = { - "name": str(vrf["name"]), - "type": "L3VRF", + vrfs[vrf["name"]] = { + "name": vrf["name"], + "type": "DEFAULT_INSTANCE" if vrf["name"] == "default" else "L3VRF", "state": {"route_distinguisher": vrf["route_distinguisher"]}, "interfaces": {"interface": interfaces}, } - all_interfaces = self.get_interfaces_ip().keys() - vrfs["default"] = { - "name": "default", - "type": "DEFAULT_INSTANCE", - "state": {"route_distinguisher": ""}, - "interfaces": { - "interface": { - k: {} for k in all_interfaces if k not in all_vrf_interfaces.keys() - } - }, - } + if "default" not in vrfs: + all_interfaces = self.get_interfaces_ip().keys() + vrfs["default"] = { + "name": "default", + "type": "DEFAULT_INSTANCE", + "state": {"route_distinguisher": ""}, + "interfaces": { + "interface": { + k: {} + for k in all_interfaces + if k not in all_vrf_interfaces.keys() + } + }, + } if name: if name in vrfs: diff --git a/napalm/ios/ios.py b/napalm/ios/ios.py index 59284a362..5e31c49f8 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -14,6 +14,7 @@ # the License. import copy import functools +import ipaddress import os import re import socket @@ -22,8 +23,6 @@ import uuid from collections import defaultdict -from netaddr import IPNetwork -from netaddr.core import AddrFormatError from netmiko import FileTransfer, InLineTransfer import napalm.base.constants as C @@ -37,14 +36,17 @@ CommitConfirmException, ) from napalm.base.helpers import ( - canonical_interface_name, transform_lldp_capab, textfsm_extractor, - split_interface, - abbreviated_interface_name, generate_regex_or, sanitize_configs, ) +from netaddr.core import AddrFormatError +from netutils.interface import ( + abbreviated_interface_name, + canonical_interface_name, + split_interface, +) from napalm.base.netmiko_helpers import netmiko_args # Easier to store these as constants @@ -481,10 +483,10 @@ def _commit_handler(self, cmd): # Handle special username removal pattern pattern2 = r".*all username.*confirm" patterns = rf"(?:{pattern1}|{pattern2})" - output = self.device.send_command(cmd, expect_string=patterns) + output = self.device.send_command(cmd, expect_string=patterns, read_timeout=90) loop_count = 50 new_output = output - for i in range(loop_count): + for _ in range(loop_count): if re.search(pattern2, new_output): # Send confirmation if username removal new_output = self.device.send_command_timing( @@ -985,10 +987,26 @@ def get_lldp_neighbors(self): hostname = lldp_entry["remote_system_name"] port = lldp_entry["remote_port"] # Match IOS behaviour of taking remote chassis ID - # When lacking a system name (in show lldp neighbors) + # when lacking a system name (in show lldp neighbors) + + # We can't assume remote_chassis_id or remote_port are MAC Addresses + # See IEEE 802.1AB-2005 and rfc2922, specifically PtopoChassisId if not hostname: - hostname = napalm.base.helpers.mac(lldp_entry["remote_chassis_id"]) - port = napalm.base.helpers.mac(port) + try: + hostname = napalm.base.helpers.mac( + lldp_entry["remote_chassis_id"] + ) + except AddrFormatError: + hostname = lldp_entry["remote_chassis_id"] + + # If port is a mac-address, normalize it. + # The MAC helper library will normalize "15" to "00:00:00:00:00:0F" + if port.count(":") == 5 or port.count("-") == 5 or port.count(".") == 2: + try: + port = napalm.base.helpers.mac(port) + except AddrFormatError: + pass + lldp_dict = {"port": port, "hostname": hostname} lldp[intf_name].append(lldp_dict) @@ -1445,6 +1463,11 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): bgp_config_list = napalm.base.helpers.netutils_parse_objects( "router bgp", cfg["running"] ) + + # No BGP configuration + if not bgp_config_list: + return {} + bgp_asn = napalm.base.helpers.regex_find_txt( r"router bgp (\d+)", bgp_config_list, default=0 ) @@ -1515,7 +1538,9 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): r" update-source (\w+)", neighbor_config ) local_as = napalm.base.helpers.regex_find_txt( - r"local-as (\d+)", neighbor_config, default=0 + r"local-as (\d+)", + neighbor_config, + default=bgp_asn, ) password = napalm.base.helpers.regex_find_txt( r"password (?:[0-9] )?([^\']+\')", neighbor_config @@ -1549,29 +1574,32 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): "route_reflector_client": route_reflector_client, } + # Do not include the no-group ("_") if a group argument is passed in + # unless group argument is "_" + if not group or group == "_": + bgp_config["_"] = { + "apply_groups": [], + "description": "", + "local_as": bgp_asn, + "type": "", + "import_policy": "", + "export_policy": "", + "local_address": "", + "multipath": False, + "multihop_ttl": 0, + "remote_as": 0, + "remove_private_as": False, + "prefix_limit": {}, + "neighbors": bgp_group_neighbors.get("_", {}), + } + # Get the peer-group level config for each group for group_name in bgp_group_neighbors.keys(): # If a group is passed in params, only continue on that group if group: if group_name != group: continue - # Default no group if group_name == "_": - bgp_config["_"] = { - "apply_groups": [], - "description": "", - "local_as": 0, - "type": "", - "import_policy": "", - "export_policy": "", - "local_address": "", - "multipath": False, - "multihop_ttl": 0, - "remote_as": 0, - "remove_private_as": False, - "prefix_limit": {}, - "neighbors": bgp_group_neighbors.get("_", {}), - } continue neighbor_config = napalm.base.helpers.netutils_parse_objects( group_name, bgp_config_list @@ -1593,7 +1621,7 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): r" description ([^\']+)\'", neighbor_config ) local_as = napalm.base.helpers.regex_find_txt( - r"local-as (\d+)", neighbor_config, default=0 + r"local-as (\d+)", neighbor_config, default=bgp_asn ) import_policy = napalm.base.helpers.regex_find_txt( r"route-map ([^\s]+) in", neighbor_config @@ -3005,7 +3033,7 @@ def _get_bgp_route_attr(self, destination, vrf, next_hop, ip_version=4): # next-hop is not known in this vrf, route leaked from # other vrf or from vpnv4 table? # get remote AS nr. from as-path if it is ebgp neighbor - # localy sourced prefix is not in routing table as a bgp route (i hope...) + # locally sourced prefix is not in routing table as a bgp route (i hope...) if search_re_dict["bgpie"]["result"] == "external": bgpras = ( search_re_dict["aspath"]["result"] @@ -3074,8 +3102,8 @@ def get_route_to(self, destination="", protocol="", longer=False): vrf = "" ip_version = None try: - ip_version = IPNetwork(destination).version - except AddrFormatError: + ip_version = ipaddress.ip_network(destination).version + except ValueError: return "Please specify a valid destination!" if ip_version == 4: # process IPv4 routing table if vrf == "": @@ -3083,8 +3111,8 @@ def get_route_to(self, destination="", protocol="", longer=False): else: vrfs = [vrf] # VRFs where IPv4 is enabled vrfs.append("default") # global VRF - ipnet_dest = IPNetwork(destination) - prefix = str(ipnet_dest.network) + ipnet_dest = ipaddress.ip_network(destination) + prefix = str(ipnet_dest.network_address) netmask = "" routes = {} if "/" in destination: diff --git a/napalm/iosxr/iosxr.py b/napalm/iosxr/iosxr.py index e8e4e5921..383e8ae00 100644 --- a/napalm/iosxr/iosxr.py +++ b/napalm/iosxr/iosxr.py @@ -16,15 +16,13 @@ # import stdlib import re import copy +import ipaddress from collections import defaultdict import logging # import third party lib from lxml import etree as ETREE -from netaddr import IPAddress # needed for traceroute, to check IP version -from netaddr.core import AddrFormatError - from napalm.pyIOSXR import IOSXR from napalm.pyIOSXR.exceptions import ConnectError from napalm.pyIOSXR.exceptions import TimeoutError @@ -989,6 +987,22 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): default" result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command)) + # Check if BGP is not configured. + get_tag = result_tree.find("./Get") + if get_tag is not None: + bgp_not_found = get_tag.attrib.get("ItemNotFound") + if bgp_not_found: + return {} + + bgp_asn = napalm.base.helpers.convert( + int, + napalm.base.helpers.find_txt( + result_tree, + "Get/Configuration/BGP/Instance[1]/InstanceAS/FourByteAS/Naming/AS", + ), + 0, + ) + if not group: neighbor = "" @@ -1012,7 +1026,9 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): int, napalm.base.helpers.find_txt(bgp_neighbor, "RemoteAS/AS_YY"), 0 ) local_as = napalm.base.helpers.convert( - int, napalm.base.helpers.find_txt(bgp_neighbor, "LocalAS/AS_YY"), 0 + int, + napalm.base.helpers.find_txt(bgp_neighbor, "LocalAS/AS_YY"), + bgp_asn, ) af_table = napalm.base.helpers.find_txt( bgp_neighbor, "NeighborAFTable/NeighborAF/Naming/AFName" @@ -1104,7 +1120,7 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): int, napalm.base.helpers.find_txt(bgp_group, "RemoteAS/AS_YY"), 0 ) local_as = napalm.base.helpers.convert( - int, napalm.base.helpers.find_txt(bgp_group, "LocalAS/AS_YY"), 0 + int, napalm.base.helpers.find_txt(bgp_group, "LocalAS/AS_YY"), bgp_asn ) multihop_ttl = napalm.base.helpers.convert( int, @@ -1166,22 +1182,22 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): } if group and group == group_name: break - if "" in bgp_group_neighbors.keys(): - bgp_config["_"] = { - "apply_groups": [], - "description": "", - "local_as": 0, - "type": "", - "import_policy": "", - "export_policy": "", - "local_address": "", - "multipath": False, - "multihop_ttl": 0, - "remote_as": 0, - "remove_private_as": False, - "prefix_limit": {}, - "neighbors": bgp_group_neighbors.get("", {}), - } + + bgp_config["_"] = { + "apply_groups": [], + "description": "", + "local_as": bgp_asn, + "type": "", + "import_policy": "", + "export_policy": "", + "local_address": "", + "multipath": False, + "multihop_ttl": 0, + "remote_as": 0, + "remove_private_as": False, + "prefix_limit": {}, + "neighbors": bgp_group_neighbors.get("", {}), + } return bgp_config @@ -1733,8 +1749,8 @@ def get_route_to(self, destination="", protocol="", longer=False): ipv = 4 try: - ipv = IPAddress(network).version - except AddrFormatError: + ipv = ipaddress.ip_address(network).version + except ValueError: logger.error("Wrong destination IP Address format supplied to get_route_to") raise TypeError("Wrong destination IP Address!") @@ -2187,8 +2203,8 @@ def traceroute( ipv = 4 try: - ipv = IPAddress(destination).version - except AddrFormatError: + ipv = ipaddress.ip_address(destination).version + except ValueError: logger.error( "Incorrect format of IP Address in traceroute \ with value provided:%s" diff --git a/napalm/iosxr_netconf/iosxr_netconf.py b/napalm/iosxr_netconf/iosxr_netconf.py index 656078880..ff7a06ef8 100644 --- a/napalm/iosxr_netconf/iosxr_netconf.py +++ b/napalm/iosxr_netconf/iosxr_netconf.py @@ -22,6 +22,7 @@ import re import copy import difflib +import ipaddress import logging # import third party lib @@ -31,8 +32,6 @@ from ncclient.operations.errors import TimeoutExpiredError from lxml import etree as ETREE from lxml.etree import XMLSyntaxError -from netaddr import IPAddress # needed for traceroute, to check IP version -from netaddr.core import AddrFormatError # import NAPALM base from napalm.iosxr_netconf import constants as C @@ -1367,9 +1366,25 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): # Converts string to etree result_tree = ETREE.fromstring(rpc_reply) + data_ele = result_tree.find("./{*}data") + # If there are no children in "", then there is no BGP configured + bgp_configured = bool(len(data_ele.getchildren())) + if not bgp_configured: + return {} + if not group: neighbor = "" + bgp_asn = napalm.base.helpers.convert( + int, + self._find_txt( + result_tree, + ".//bgpc:bgp/bgpc:instance/bgpc:instance-as/bgpc:four-byte-as/bgpc:as", + default=0, + namespaces=C.NS, + ), + ) + bgp_group_neighbors = {} bgp_neighbor_xpath = ".//bgpc:bgp/bgpc:instance/bgpc:instance-as/\ bgpc:four-byte-as/bgpc:default-vrf/bgpc:bgp-entity/bgpc:neighbors/bgpc:neighbor" @@ -1431,7 +1446,7 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): ), 0, ) - local_as = local_as_x * 65536 + local_as_y + local_as = (local_as_x * 65536 + local_as_y) or bgp_asn af_table = self._find_txt( bgp_neighbor, "./bgpc:neighbor-afs/bgpc:neighbor-af/bgpc:af-name", @@ -1598,7 +1613,7 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): ), 0, ) - local_as = local_as_x * 65536 + local_as_y + local_as = (local_as_x * 65536 + local_as_y) or bgp_asn multihop_ttl = napalm.base.helpers.convert( int, self._find_txt( @@ -1680,22 +1695,22 @@ def build_prefix_limit(af_table, limit, prefix_percent, prefix_timeout): } if group and group == group_name: break - if "" in bgp_group_neighbors.keys(): - bgp_config["_"] = { - "apply_groups": [], - "description": "", - "local_as": 0, - "type": "", - "import_policy": "", - "export_policy": "", - "local_address": "", - "multipath": False, - "multihop_ttl": 0, - "remote_as": 0, - "remove_private_as": False, - "prefix_limit": {}, - "neighbors": bgp_group_neighbors.get("", {}), - } + + bgp_config["_"] = { + "apply_groups": [], + "description": "", + "local_as": bgp_asn, + "type": "", + "import_policy": "", + "export_policy": "", + "local_address": "", + "multipath": False, + "multihop_ttl": 0, + "remote_as": 0, + "remove_private_as": False, + "prefix_limit": {}, + "neighbors": bgp_group_neighbors.get("", {}), + } return bgp_config @@ -2481,8 +2496,8 @@ def get_route_to(self, destination="", protocol="", longer=False): ipv = 4 try: - ipv = IPAddress(network).version - except AddrFormatError: + ipv = ipaddress.ip_address(network).version + except ValueError: logger.error("Wrong destination IP Address format supplied to get_route_to") raise TypeError("Wrong destination IP Address!") @@ -2952,8 +2967,8 @@ def traceroute( ipv = 4 try: - ipv = IPAddress(destination).version - except AddrFormatError: + ipv = ipaddress.ip_address(destination).version + except ValueError: logger.error( "Incorrect format of IP Address in traceroute \ with value provided:%s" @@ -3139,7 +3154,7 @@ def get_config(self, retrieve="all", full=False, sanitized=False): if config[datastore] != "": if encoding == "cli": cli_tree = ETREE.XML(config[datastore], parser=parser)[0] - if cli_tree: + if len(cli_tree): config[datastore] = cli_tree[0].text.strip() else: config[datastore] = "" diff --git a/napalm/junos/junos.py b/napalm/junos/junos.py index d274dac91..74b6e5efe 100644 --- a/napalm/junos/junos.py +++ b/napalm/junos/junos.py @@ -176,10 +176,10 @@ def _unlock(self): def _rpc(self, get, child=None, **kwargs): """ - This allows you to construct an arbitrary RPC call to retreive common stuff. For example: + This allows you to construct an arbitrary RPC call to retrieve common stuff. For example: Configuration: get: "" Interface information: get: "" - A particular interfacece information: + A particular interface information: get: "" child: "ge-0/0/0" """ @@ -1169,7 +1169,7 @@ def update_dict(d, u): # for deep dictionary update def build_prefix_limit(**args): """ - Transform the lements of a dictionary into nested dictionaries. + Transform the elements of a dictionary into nested dictionaries. Example: { @@ -1250,13 +1250,40 @@ def build_prefix_limit(**args): bgp_config = {} - if group: + routing_options = junos_views.junos_routing_config_table(self.device) + routing_options.get(options=self.junos_config_options) + + bgp_asn_obj = routing_options.xml.find( + "./routing-options/autonomous-system/as-number" + ) + system_bgp_asn = int(bgp_asn_obj.text) if bgp_asn_obj is not None else 0 + + # No BGP peer-group i.e. "_" key is a special case. + if group and group != "_": bgp = junos_views.junos_bgp_config_group_table(self.device) bgp.get(group=group, options=self.junos_config_options) else: bgp = junos_views.junos_bgp_config_table(self.device) bgp.get(options=self.junos_config_options) neighbor = "" # if no group is set, no neighbor should be set either + + # Only set no peer-group if BGP is actually configured. + if bgp.items() or system_bgp_asn: + bgp_config["_"] = { + "apply_groups": [], + "description": "", + "local_as": system_bgp_asn, + "type": "", + "import_policy": "", + "export_policy": "", + "local_address": "", + "multipath": False, + "multihop_ttl": 0, + "remote_as": 0, + "remove_private_as": False, + "prefix_limit": {}, + "neighbors": {}, + } bgp_items = bgp.items() if neighbor: @@ -1283,13 +1310,17 @@ def build_prefix_limit(**args): for field, datatype in _GROUP_FIELDS_DATATYPE_MAP_.items() if "_prefix_limit" not in field } - for elem in bgp_group_details: - if not ("_prefix_limit" not in elem[0] and elem[1] is not None): + + # Always overwrite with the system local_as (this will either be + # valid or will be zero i.e. the same as the default value). + bgp_config[bgp_group_name]["local_as"] = system_bgp_asn + + for key, value in bgp_group_details: + if "_prefix_limit" in key or value is None: continue - datatype = _GROUP_FIELDS_DATATYPE_MAP_.get(elem[0]) + datatype = _GROUP_FIELDS_DATATYPE_MAP_.get(key) default = _DATATYPE_DEFAULT_.get(datatype) - key = elem[0] - value = elem[1] + if key in ["export_policy", "import_policy"]: if isinstance(value, list): value = " ".join(value) @@ -1297,6 +1328,10 @@ def build_prefix_limit(**args): value = napalm.base.helpers.convert( napalm.base.helpers.ip, value, value ) + if key == "apply_groups": + # Ensure apply_groups value is wrapped in a list + if isinstance(value, str): + value = [value] if key == "neighbors": bgp_group_peers = value continue @@ -1304,15 +1339,15 @@ def build_prefix_limit(**args): {key: napalm.base.helpers.convert(datatype, value, default)} ) prefix_limit_fields = {} - for elem in bgp_group_details: - if "_prefix_limit" in elem[0] and elem[1] is not None: - datatype = _GROUP_FIELDS_DATATYPE_MAP_.get(elem[0]) + for key, value in bgp_group_details: + if "_prefix_limit" in key and value is not None: + datatype = _GROUP_FIELDS_DATATYPE_MAP_.get(key) default = _DATATYPE_DEFAULT_.get(datatype) prefix_limit_fields.update( { - elem[0].replace( + key.replace( "_prefix_limit", "" - ): napalm.base.helpers.convert(datatype, elem[1], default) + ): napalm.base.helpers.convert(datatype, value, default) } ) bgp_config[bgp_group_name]["prefix_limit"] = build_prefix_limit( @@ -1326,23 +1361,31 @@ def build_prefix_limit(**args): bgp_config[bgp_group_name]["multihop_ttl"] = 64 bgp_config[bgp_group_name]["neighbors"] = {} + bgp_group_remote_as = bgp_config[bgp_group_name]["remote_as"] for bgp_group_neighbor in bgp_group_peers.items(): bgp_peer_address = napalm.base.helpers.ip(bgp_group_neighbor[0]) if neighbor and bgp_peer_address != neighbor: continue # if filters applied, jump over all other neighbors bgp_group_details = bgp_group_neighbor[1] + + # Set defaults for this BGP peer bgp_peer_details = { field: _DATATYPE_DEFAULT_.get(datatype) for field, datatype in _PEER_FIELDS_DATATYPE_MAP_.items() if "_prefix_limit" not in field } - for elem in bgp_group_details: - if not ("_prefix_limit" not in elem[0] and elem[1] is not None): + + # Always overwrite with the system local_as (this will either be + # valid or will be zero i.e. the same as the default value). + bgp_peer_details["local_as"] = system_bgp_asn + # Always set the default remote-as as the Peer-Group remote-as + bgp_peer_details["remote_as"] = bgp_group_remote_as + + for key, value in bgp_group_details: + if "_prefix_limit" in key or value is None: continue - datatype = _PEER_FIELDS_DATATYPE_MAP_.get(elem[0]) + datatype = _PEER_FIELDS_DATATYPE_MAP_.get(key) default = _DATATYPE_DEFAULT_.get(datatype) - key = elem[0] - value = elem[1] if key in ["export_policy"]: # next-hop self is applied on export IBGP sessions bgp_peer_details["nhs"] = _check_nhs(value, nhs_policies) @@ -1370,17 +1413,15 @@ def build_prefix_limit(**args): if "cluster" in bgp_config[bgp_group_name].keys(): bgp_peer_details["route_reflector_client"] = True prefix_limit_fields = {} - for elem in bgp_group_details: - if "_prefix_limit" in elem[0] and elem[1] is not None: - datatype = _PEER_FIELDS_DATATYPE_MAP_.get(elem[0]) + for key, value in bgp_group_details: + if "_prefix_limit" in key and value is not None: + datatype = _PEER_FIELDS_DATATYPE_MAP_.get(key) default = _DATATYPE_DEFAULT_.get(datatype) prefix_limit_fields.update( { - elem[0].replace( + key.replace( "_prefix_limit", "" - ): napalm.base.helpers.convert( - datatype, elem[1], default - ) + ): napalm.base.helpers.convert(datatype, value, default) } ) bgp_peer_details["prefix_limit"] = build_prefix_limit( @@ -1824,7 +1865,7 @@ def get_route_to(self, destination="", protocol="", longer=False): try: routes_table.get(**rt_kargs) except RpcTimeoutError: - # on devices with milions of routes + # on devices with millions of routes # in case the destination is too generic (e.g.: 10/8) # will take very very long to determine all routes and # moreover will return a huge list diff --git a/napalm/junos/utils/junos_views.yml b/napalm/junos/utils/junos_views.yml index 5d81ae2fc..3fd60b1f4 100644 --- a/napalm/junos/utils/junos_views.yml +++ b/napalm/junos/utils/junos_views.yml @@ -402,6 +402,15 @@ junos_bgp_config_peers_view: inet6_flow_teardown_timeout_prefix_limit: {family/inet6/flow/prefix-limit/teardown/idle-timeout/timeout: int} inet6_flow_novalidate_prefix_limit: {family/inet6/flow/prefix-limit/no-validate: unicode} +junos_routing_config_table: + get: "routing-options/autonomous-system" + view: junos_routing_config_view + +junos_routing_config_view: + fields: + local_system_as: autonomous-system + + #### #### BGP Neighbors and Routing Tables Stats #### diff --git a/napalm/nxos/nxos.py b/napalm/nxos/nxos.py index d857832e4..9668569ff 100644 --- a/napalm/nxos/nxos.py +++ b/napalm/nxos/nxos.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations under # the License. +import ipaddress import json import os import re @@ -42,11 +43,10 @@ DefaultDict, ) -from netaddr import IPAddress -from netaddr.core import AddrFormatError from netmiko import file_transfer from requests.exceptions import ConnectionError from netutils.config.compliance import diff_network_config +from netutils.interface import canonical_interface_name import napalm.base.constants as c @@ -233,7 +233,7 @@ def _get_merge_diff(self) -> str: interface loopback0 ip address 10.1.4.5/32 """ - running_config = self.get_config(retrieve="running")["running"] + running_config = self.get_config(retrieve="running", full=True)["running"] return diff_network_config(self.merge_candidate, running_config, "cisco_nxos") def _get_diff(self) -> str: @@ -356,8 +356,8 @@ def ping( version = "" try: - version = "6" if IPAddress(destination).version == 6 else "" - except AddrFormatError: + version = "6" if ipaddress.ip_address(destination).version == 6 else "" + except ValueError: # Allow use of DNS names pass @@ -470,8 +470,8 @@ def traceroute( version = "" try: - version = "6" if IPAddress(destination).version == 6 else "" - except AddrFormatError: + version = "6" if ipaddress.ip_address(destination).version == 6 else "" + except ValueError: # Allow use of DNS names pass @@ -683,7 +683,7 @@ def get_lldp_neighbors_detail( lldp_entry["remote_system_enable_capab"] ) # Turn the interfaces into their long version - local_intf = napalm.base.helpers.canonical_interface_name(local_intf) + local_intf = canonical_interface_name(local_intf) lldp.setdefault(local_intf, []) lldp[local_intf].append(lldp_entry) # type: ignore @@ -739,13 +739,9 @@ def _parse_vlan_ports(self, vlan_s: Union[str, List]) -> List: find = re.findall(find_regexp, vls.strip()) if find: for i in range(int(find[0][1]), int(find[0][2]) + 1): - vlans.append( - napalm.base.helpers.canonical_interface_name( - find[0][0] + str(i) - ) - ) + vlans.append(canonical_interface_name(find[0][0] + str(i))) else: - vlans.append(napalm.base.helpers.canonical_interface_name(vls.strip())) + vlans.append(canonical_interface_name(vls.strip())) return vlans @abstractmethod diff --git a/napalm/nxos_ssh/nxos_ssh.py b/napalm/nxos_ssh/nxos_ssh.py index 01147d0e6..bdb01f5a9 100644 --- a/napalm/nxos_ssh/nxos_ssh.py +++ b/napalm/nxos_ssh/nxos_ssh.py @@ -15,12 +15,13 @@ # import stdlib from builtins import super +import ipaddress import re import socket +from collections import defaultdict -# import third party lib -from netaddr import IPAddress, IPNetwork -from netaddr.core import AddrFormatError +# import external lib +from netutils.interface import canonical_interface_name # import NAPALM Base from napalm.base import helpers @@ -118,7 +119,7 @@ def parse_intf_section(interface): else: # More standard is up, next line admin state is lines match = re.search(re_intf_name_state, interface) - intf_name = helpers.canonical_interface_name(match.group("intf_name")) + intf_name = canonical_interface_name(match.group("intf_name")) intf_state = match.group("intf_state").strip() is_up = True if intf_state == "up" else False @@ -456,21 +457,15 @@ def _send_command(self, command, raw_text=False, cmd_verify=True): """ return self.device.send_command(command, cmd_verify=cmd_verify) - def _send_command_list(self, commands, expect_string=None): - """Wrapper for Netmiko's send_command method (for list of commands.""" - output = "" - for command in commands: - output += self.device.send_command( - command, - strip_prompt=False, - strip_command=False, - expect_string=expect_string, - ) - return output + def _send_command_list(self, commands, expect_string=None, **kwargs): + """Send a list of commands using Netmiko""" + return self.device.send_multiline( + commands, expect_string=expect_string, **kwargs + ) def _send_config(self, commands): if isinstance(commands, str): - commands = (command for command in commands.splitlines() if command) + commands = [command for command in commands.splitlines() if command] return self.device.send_config_set(commands) @staticmethod @@ -524,7 +519,6 @@ def is_alive(self): return {"is_alive": self.device.remote_conn.transport.is_active()} def _copy_run_start(self): - output = self.device.save_config() if "complete" in output.lower(): return True @@ -533,7 +527,6 @@ def _copy_run_start(self): raise CommandErrorException(msg) def _load_cfg_from_checkpoint(self): - commands = [ "terminal dont-ask", "rollback running-config file {}".format(self.candidate_cfg), @@ -541,7 +534,9 @@ def _load_cfg_from_checkpoint(self): ] try: - rollback_result = self._send_command_list(commands, expect_string=r"[#>]") + rollback_result = self._send_command_list( + commands, expect_string=r"[#>]", read_timeout=90 + ) finally: self.changed = True msg = rollback_result @@ -555,7 +550,9 @@ def rollback(self): "rollback running-config file {}".format(self.rollback_cfg), "no terminal dont-ask", ] - result = self._send_command_list(commands, expect_string=r"[#>]") + result = self._send_command_list( + commands, expect_string=r"[#>]", read_timeout=90 + ) if "completed" not in result.lower(): raise ReplaceConfigException(result) # If hostname changes ensure Netmiko state is updated properly @@ -659,7 +656,7 @@ def get_facts(self): continue interface = line.split()[0] # Return canonical interface name - interface_list.append(helpers.canonical_interface_name(interface)) + interface_list.append(canonical_interface_name(interface)) return { "uptime": float(uptime), @@ -799,6 +796,61 @@ def cli(self, commands, encoding="text"): cli_output[str(command)] = output return cli_output + def get_network_instances(self, name=""): + """ + get_network_instances implementation for NX-OS + """ + + # command 'show vrf detail | json' returns all VRFs with detailed information in JSON format + # format: list of dictionaries with keys such as 'vrf_name' and 'rd' + vrf_table_raw = self._get_command_table( + "show vrf detail | json", "TABLE_vrf", "ROW_vrf" + ) + + # command 'show vrf interface' returns all interfaces including their assigned VRF + # format: list of dictionaries with keys 'if_name', 'vrf_name', 'vrf_id' and 'soo' + intf_table_raw = self._get_command_table( + "show vrf interface | json", "TABLE_if", "ROW_if" + ) + + # create a dictionary with key = 'vrf_name' and value = list of interfaces + vrf_intfs = defaultdict(list) + for intf in intf_table_raw: + vrf_intfs[intf["vrf_name"]].append(str(intf["if_name"])) + + vrfs = {} + for vrf in vrf_table_raw: + vrf_name = str(vrf.get("vrf_name")) + vrfs[vrf_name] = {} + vrfs[vrf_name]["name"] = vrf_name + + # differentiate between VRF type 'DEFAULT_INSTANCE' and 'L3VRF' + if vrf_name == "default": + vrfs[vrf_name]["type"] = "DEFAULT_INSTANCE" + else: + vrfs[vrf_name]["type"] = "L3VRF" + + vrfs[vrf_name]["state"] = {"route_distinguisher": str(vrf.get("rd"))} + + # convert list of interfaces (vrf_intfs[vrf_name]) to expected format + # format = dict with key = interface name and empty values + vrfs[vrf_name]["interfaces"] = {} + vrfs[vrf_name]["interfaces"]["interface"] = dict.fromkeys( + vrf_intfs[vrf_name], {} + ) + + # if name of a specific VRF was passed as an argument + # only return results for this particular VRF + + if name: + if name in vrfs.keys(): + return {str(name): vrfs[name]} + else: + return {} + # else return results for all VRFs + else: + return vrfs + def get_environment(self): """ Get environment facts. @@ -953,7 +1005,7 @@ def _get_ntp_entity(self, peer_type): # Skip first two lines and last line of command output if line == "" or "-----" in line or "Peer IP Address" in line: continue - elif IPAddress(len(line.split()[0])).is_unicast: + elif not ipaddress.ip_address(len(line.split()[0])).is_multicast: peer_addr = line.split()[0] ntp_entities[peer_addr] = {} else: @@ -1139,7 +1191,7 @@ def process_mac_fields(vlan, mac, mac_type, interface): active = False return { "mac": helpers.mac(mac), - "interface": helpers.canonical_interface_name(interface), + "interface": canonical_interface_name(interface), "vlan": int(vlan), "static": static, "active": active, @@ -1158,7 +1210,6 @@ def process_mac_fields(vlan, mac, mac_type, interface): output = re.sub(r"vPC Peer-Link", "vPC-Peer-Link", output, flags=re.M) for line in output.splitlines(): - # Every 500 Mac's Legend is reprinted, regardless of terminal length if re.search(r"^Legend", line): continue @@ -1340,8 +1391,8 @@ def get_route_to(self, destination="", protocol="", longer=False): ip_version = None try: - ip_version = IPNetwork(destination).version - except AddrFormatError: + ip_version = ipaddress.ip_network(destination).version + except ValueError: return "Please specify a valid destination!" if ip_version == 4: # process IPv4 routing table routes = {} @@ -1409,7 +1460,7 @@ def get_route_to(self, destination="", protocol="", longer=False): # routing protocol process number, for future use # nh_source_proc_nr = viastr.group('procnr) if nh_int: - nh_int_canon = helpers.canonical_interface_name(nh_int) + nh_int_canon = canonical_interface_name(nh_int) else: nh_int_canon = "" route_entry = { diff --git a/napalm/pyIOSXR/iosxr.py b/napalm/pyIOSXR/iosxr.py index 694a73227..a1ddc2215 100644 --- a/napalm/pyIOSXR/iosxr.py +++ b/napalm/pyIOSXR/iosxr.py @@ -690,7 +690,7 @@ def commit_replace_config(self, label=None, comment=None, confirmed=None): def discard_config(self): """ - Clear uncommited changes in the current session. + Clear uncommitted changes in the current session. Clear previously loaded configuration on the device without committing it. """ diff --git a/requirements-dev.txt b/requirements-dev.txt index b1197ced2..62923790b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,17 +1,17 @@ black==22.6.0 coveralls==3.3.1 -ddt==1.5.0 -flake8-import-order==0.18.1 -pytest==7.1.2 -pytest-cov==3.0.0 -pytest-json==0.4.0 -pylama==8.2.1 -mock==4.0.3 -tox==3.25.1 -mypy==0.961 -types-requests==2.28.0 -types-six==1.16.17 -types-setuptools==57.4.18 -types-PyYAML==6.0.9 -ttp==0.9.0 -ttp_templates==0.3.0 \ No newline at end of file +ddt==1.6.0 +flake8-import-order==0.18.2 +pytest==7.3.1 +pytest-cov==4.0.0 +pytest-json-report==1.5.0 +pyflakes==3.0.1 +pylama==8.4.1 +mock==5.0.2 +mypy==0.982 +types-PyYAML==6.0.12.10 +types-requests==2.28.11.17 +types-six==1.16.21.8 +types-setuptools==67.8.0.0 +ttp==0.9.4 +ttp_templates==0.3.5 diff --git a/requirements.txt b/requirements.txt index 18530cdd5..57ecd72eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,12 +3,12 @@ cffi>=1.11.3 paramiko>=2.6.0 requests>=2.7.0 future -textfsm<=1.1.2 +textfsm jinja2 netaddr pyYAML pyeapi>=0.8.2 -netmiko>=4.0.0 +netmiko>=4.1.0 junos-eznc>=2.6.3 scp lxml>=4.3.0 diff --git a/setup.cfg b/setup.cfg index 272f56d7f..871c4c939 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,11 +5,11 @@ universal = 1 license_file = LICENSE [pylama] -linters = mccabe,pep8,pyflakes +linters = mccabe,pycodestyle,pyflakes ignore = D203,C901,E203 skip = .tox/*,.env/*,.venv/* -[pylama:pep8] +[pylama:pycodestyle] max_line_length = 100 [tool:pytest] diff --git a/setup.py b/setup.py index 9eb0865d0..0a3bbe374 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="napalm", - version="4.0.0", + version="4.1.0", packages=find_packages(exclude=("test*",)), test_suite="test_base", author="David Barroso, Kirk Byers, Mircea Ulinic", diff --git a/test/base/test_helpers.py b/test/base/test_helpers.py index f6ca9717b..cd2226877 100644 --- a/test/base/test_helpers.py +++ b/test/base/test_helpers.py @@ -57,7 +57,10 @@ from napalm.base.netmiko_helpers import netmiko_args import napalm.base.exceptions from napalm.base.base import NetworkDriver -from napalm.base.utils.string_parsers import convert_uptime_string_seconds +from napalm.base.utils.string_parsers import ( + convert_uptime_string_seconds, + parse_fixed_width, +) class TestBaseHelpers(unittest.TestCase): @@ -81,7 +84,7 @@ def test_load_template(self): custom path * check if can load correct template from custom path * check if template passed as string can be loaded - * check that the search path setup by MRO is correct when loading an incorrecet template + * check that the search path setup by MRO is correct when loading an incorrect template """ self.assertTrue(HAS_JINJA) # firstly check if jinja2 is installed @@ -375,10 +378,8 @@ def test_ip(self): * check if IPv6 address returned as expected """ - self.assertTrue(HAS_NETADDR) - - # test that raises AddrFormatError when wrong format - self.assertRaises(AddrFormatError, napalm.base.helpers.ip, "fake") + # test that raises ValueError when wrong format + self.assertRaises(ValueError, napalm.base.helpers.ip, "fake") self.assertRaises( ValueError, napalm.base.helpers.ip, @@ -495,87 +496,6 @@ def test_convert_uptime_string_seconds(self): self.assertEqual(convert_uptime_string_seconds("95w2d10h58m"), 57668280) self.assertEqual(convert_uptime_string_seconds("1h5m"), 3900) - def test_canonical_interface_name(self): - """Test the canonical_interface_name helper function.""" - self.assertEqual( - napalm.base.helpers.canonical_interface_name("Fa0/1"), "FastEthernet0/1" - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name("FastEthernet0/1"), - "FastEthernet0/1", - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name("TenGig1/1/1.5"), - "TenGigabitEthernet1/1/1.5", - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name("Gi1/2"), "GigabitEthernet1/2" - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name("HundredGigE105/1/1"), - "HundredGigabitEthernet105/1/1", - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name("Lo0"), "Loopback0" - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name("lo0"), "Loopback0" - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name("no_match0/1"), "no_match0/1" - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name( - "lo10", addl_name_map={"lo": "something_custom"} - ), - "something_custom10", - ) - self.assertEqual( - napalm.base.helpers.canonical_interface_name( - "uniq0/1/1", addl_name_map={"uniq": "something_custom"} - ), - "something_custom0/1/1", - ) - - def test_abbreviated_interface_name(self): - """Test the abbreviated_interface_name helper function.""" - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name("Fa0/1"), "Fa0/1" - ) - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name("FastEthernet0/1"), "Fa0/1" - ) - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name("TenGig1/1/1.5"), "Te1/1/1.5" - ) - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name("Gi1/2"), "Gi1/2" - ) - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name("HundredGigE105/1/1"), - "Hu105/1/1", - ) - self.assertEqual(napalm.base.helpers.abbreviated_interface_name("Lo0"), "Lo0") - self.assertEqual(napalm.base.helpers.abbreviated_interface_name("lo0"), "Lo0") - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name("something_custom0/1"), - "something_custom0/1", - ) - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name( - "loop10", addl_name_map={"loop": "Loopback"} - ), - "Lo10", - ) - self.assertEqual( - napalm.base.helpers.abbreviated_interface_name( - "loop10", - addl_name_map={"loop": "Loopback"}, - addl_reverse_map={"Loopback": "lo"}, - ), - "lo10", - ) - def test_netmiko_arguments(self): """Test the netmiko argument processing.""" self.assertEqual(netmiko_args(optional_args={}), {}) @@ -755,6 +675,37 @@ def test_ttp_parse(self): ) self.assertEqual(result, _EXPECTED_RESULT) + def test_parse_fixed_width(self): + _TEST_STRING = """ VRF RD Protocols State Interfaces +---------------- --------------- --------------- ------------------- --------------------------- +123456789012345671234567890123456123456789012345612345678901234567890123456789012345678901234567 +""" # noqa: E501 + _EXPECTED_RESULT = [ + ( + " VRF ", + " RD ", + " Protocols ", + " State ", + "Interfaces ", + ), + ( + "---------------- ", + "--------------- ", + "--------------- ", + "------------------- ", + "---------------------------", + ), + ( + "12345678901234567", + "1234567890123456", + "1234567890123456", + "12345678901234567890", + "123456789012345678901234567", + ), + ] + result = parse_fixed_width(_TEST_STRING, 17, 16, 16, 20, 27) + self.assertEqual(result, _EXPECTED_RESULT) + class FakeNetworkDriver(NetworkDriver): def __init__(self): diff --git a/test/eos/conftest.py b/test/eos/conftest.py index 7cf515f31..aff8e78a1 100644 --- a/test/eos/conftest.py +++ b/test/eos/conftest.py @@ -37,6 +37,19 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None) self.patched_attrs = ["device"] self.device = FakeEOSDevice() + self._cli_version = 1 + + @property + def cli_version(self): + try: + full_path = self.device.find_file("cli_version.txt") + except IOError: + return self._cli_version + return int(self.device.read_txt_file(full_path)) + + @cli_version.setter + def cli_version(self, value): + self._cli_version = value class FakeEOSDevice(BaseTestDouble): diff --git a/test/eos/mocked_data/test_get_bgp_config/issue1504_alt_peer_group_syntax/expected_result.json b/test/eos/mocked_data/test_get_bgp_config/issue1504_alt_peer_group_syntax/expected_result.json index 8354f24ba..1dbf0d43b 100644 --- a/test/eos/mocked_data/test_get_bgp_config/issue1504_alt_peer_group_syntax/expected_result.json +++ b/test/eos/mocked_data/test_get_bgp_config/issue1504_alt_peer_group_syntax/expected_result.json @@ -1,4 +1,19 @@ { + "_": { + "type": "", + "multipath": false, + "apply_groups": [], + "remove_private_as": false, + "multihop_ttl": 0, + "remote_as": 0, + "local_address": "", + "local_as": 64496, + "description": "", + "import_policy": "", + "export_policy": "", + "prefix_limit": {}, + "neighbors": {} + }, "IPv6-PEERS-GROUP-NAME": { "type": "", "multipath": false, @@ -20,8 +35,8 @@ "local_as": 64496, "nhs": false, "route_reflector_client": false, - "import_policy": "", - "export_policy": "", + "import_policy": "reject-all", + "export_policy": "reject-all", "authentication_key": "", "prefix_limit": {} }, @@ -32,8 +47,8 @@ "local_as": 64496, "nhs": false, "route_reflector_client": false, - "import_policy": "", - "export_policy": "", + "import_policy": "reject-all", + "export_policy": "reject-all", "authentication_key": "", "prefix_limit": {} } diff --git a/test/eos/mocked_data/test_get_bgp_config/issue_1113_dot_asn/expected_result.json b/test/eos/mocked_data/test_get_bgp_config/issue_1113_dot_asn/expected_result.json index 84f8c3bd8..1f885538c 100644 --- a/test/eos/mocked_data/test_get_bgp_config/issue_1113_dot_asn/expected_result.json +++ b/test/eos/mocked_data/test_get_bgp_config/issue_1113_dot_asn/expected_result.json @@ -1,4 +1,19 @@ { + "_": { + "type": "", + "multipath": false, + "apply_groups": [], + "remove_private_as": false, + "multihop_ttl": 0, + "remote_as": 0, + "local_address": "", + "local_as": 4266524237, + "description": "", + "import_policy": "", + "export_policy": "", + "prefix_limit": {}, + "neighbors": {} + }, "IPv4-PEERS-GROUP-NAME": { "type": "", "multipath": false, @@ -20,8 +35,8 @@ "local_as": 4266524237, "nhs": false, "route_reflector_client": false, - "import_policy": "", - "export_policy": "", + "import_policy": "reject-all", + "export_policy": "4-public-peer-anycast-out", "authentication_key": "", "prefix_limit": {} }, @@ -32,8 +47,8 @@ "local_as": 4266524237, "nhs": false, "route_reflector_client": false, - "import_policy": "", - "export_policy": "", + "import_policy": "reject-all", + "export_policy": "4-public-peer-anycast-out", "authentication_key": "", "prefix_limit": {} } @@ -60,8 +75,8 @@ "local_as": 4266524237, "nhs": false, "route_reflector_client": false, - "import_policy": "", - "export_policy": "", + "import_policy": "reject-all", + "export_policy": "reject-all", "authentication_key": "", "prefix_limit": {} }, @@ -72,8 +87,8 @@ "local_as": 4266524237, "nhs": false, "route_reflector_client": false, - "import_policy": "", - "export_policy": "", + "import_policy": "reject-all", + "export_policy": "reject-all", "authentication_key": "", "prefix_limit": {} } diff --git a/test/eos/mocked_data/test_get_bgp_config/issue_905_group_copydown/expected_result.json b/test/eos/mocked_data/test_get_bgp_config/issue_905_group_copydown/expected_result.json new file mode 100644 index 000000000..4f2f36e4e --- /dev/null +++ b/test/eos/mocked_data/test_get_bgp_config/issue_905_group_copydown/expected_result.json @@ -0,0 +1,57 @@ +{ + "_": { + "type": "", + "multipath": false, + "apply_groups": [], + "remove_private_as": false, + "multihop_ttl": 0, + "remote_as": 0, + "local_address": "", + "local_as": 65534, + "description": "", + "import_policy": "", + "export_policy": "", + "prefix_limit": {}, + "neighbors": {} + }, + "FOO-GROUP": { + "local_address": "", + "description": "FOO", + "type": "", + "local_as": 65534, + "apply_groups": [], + "multihop_ttl": 0, + "remove_private_as": false, + "remote_as": 65534, + "import_policy": "", + "export_policy": "", + "neighbors": { + "192.0.2.2": { + "local_address": "", + "authentication_key": "", + "description": "FOO", + "nhs": false, + "local_as": 65534, + "route_reflector_client": false, + "remote_as": 65534, + "import_policy": "", + "export_policy": "", + "prefix_limit": {} + }, + "192.0.2.3": { + "local_address": "", + "authentication_key": "", + "description": "SECOND-PEER", + "nhs": true, + "local_as": 65534, + "route_reflector_client": false, + "remote_as": 65534, + "import_policy": "", + "export_policy": "", + "prefix_limit": {} + } + }, + "prefix_limit": {}, + "multipath": false + } +} diff --git a/test/eos/mocked_data/test_get_bgp_config/issue_905_group_copydown/show_running_config___section_router_bgp.text b/test/eos/mocked_data/test_get_bgp_config/issue_905_group_copydown/show_running_config___section_router_bgp.text new file mode 100644 index 000000000..0db6d0c27 --- /dev/null +++ b/test/eos/mocked_data/test_get_bgp_config/issue_905_group_copydown/show_running_config___section_router_bgp.text @@ -0,0 +1,11 @@ +router bgp 65534 + router-id 192.0.2.1 + neighbor FOO-GROUP peer group + neighbor FOO-GROUP next-hop-self + neighbor FOO-GROUP description FOO + neighbor FOO-GROUP remote-as 65534 + neighbor 192.0.2.2 peer group FOO-GROUP + no neighbor 192.0.2.2 next-hop-self + neighbor 192.0.2.3 peer group FOO-GROUP + neighbor 192.0.2.3 description SECOND-PEER +! diff --git a/test/eos/mocked_data/test_get_bgp_config/no_bgp_config/expected_result.json b/test/eos/mocked_data/test_get_bgp_config/no_bgp_config/expected_result.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/eos/mocked_data/test_get_bgp_config/no_bgp_config/expected_result.json @@ -0,0 +1 @@ +{} diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_config/no_bgp_config/show_running_config___section_router_bgp.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_config/no_bgp_config/show_running_config___section_router_bgp.text diff --git a/test/eos/mocked_data/test_get_bgp_config/normal/expected_result.json b/test/eos/mocked_data/test_get_bgp_config/normal/expected_result.json index de52578fe..bdf8f2657 100644 --- a/test/eos/mocked_data/test_get_bgp_config/normal/expected_result.json +++ b/test/eos/mocked_data/test_get_bgp_config/normal/expected_result.json @@ -1 +1,97 @@ -{"IPv4-PEERS-GROUP-NAME": {"local_address": "", "description": "", "type": "", "local_as": 13335, "apply_groups": [], "multihop_ttl": 0, "remove_private_as": true, "remote_as": 0, "import_policy": "reject-all", "export_policy": "4-public-peer-anycast-out", "neighbors": {"172.17.17.1": {"local_address": "", "authentication_key": "", "description": "", "nhs": false, "local_as": 13335, "route_reflector_client": false, "remote_as": 13414, "import_policy": "", "export_policy": "", "prefix_limit": {}}, "192.168.0.1": {"local_address": "", "authentication_key": "", "description": "", "nhs": false, "local_as": 13335, "route_reflector_client": false, "remote_as": 32934, "import_policy": "", "export_policy": "", "prefix_limit": {}}}, "prefix_limit": {}, "multipath": false}, "IPv6-PEERS-GROUP-NAME": {"local_address": "", "description": "", "type": "", "local_as": 13335, "apply_groups": [], "multihop_ttl": 0, "remove_private_as": true, "remote_as": 0, "import_policy": "reject-all", "export_policy": "reject-all", "neighbors": {"2001:db8::0:2": {"local_address": "", "authentication_key": "", "description": "", "nhs": false, "local_as": 13335, "route_reflector_client": false, "remote_as": 54113, "import_policy": "", "export_policy": "", "prefix_limit": {}}, "2001:db8::0:1": {"local_address": "", "authentication_key": "", "description": "", "nhs": false, "local_as": 13335, "route_reflector_client": false, "remote_as": 8403, "import_policy": "", "export_policy": "", "prefix_limit": {}}}, "prefix_limit": {}, "multipath": false}} +{ + "_": { + "type": "", + "multipath": false, + "apply_groups": [], + "remove_private_as": false, + "multihop_ttl": 0, + "remote_as": 0, + "local_address": "", + "local_as": 13335, + "description": "", + "import_policy": "", + "export_policy": "", + "prefix_limit": {}, + "neighbors": {} + }, + "IPv4-PEERS-GROUP-NAME": { + "local_address": "", + "description": "", + "type": "", + "local_as": 13335, + "apply_groups": [], + "multihop_ttl": 0, + "remove_private_as": true, + "remote_as": 0, + "import_policy": "reject-all", + "export_policy": "4-public-peer-anycast-out", + "neighbors": { + "172.17.17.1": { + "local_address": "", + "authentication_key": "", + "description": "", + "nhs": false, + "local_as": 13335, + "route_reflector_client": false, + "remote_as": 13414, + "import_policy": "reject-all", + "export_policy": "4-public-peer-anycast-out", + "prefix_limit": {} + }, + "192.168.0.1": { + "local_address": "", + "authentication_key": "", + "description": "", + "nhs": false, + "local_as": 13335, + "route_reflector_client": false, + "remote_as": 32934, + "import_policy": "reject-all", + "export_policy": "4-public-peer-anycast-out", + "prefix_limit": {} + } + }, + "prefix_limit": {}, + "multipath": false + }, + "IPv6-PEERS-GROUP-NAME": { + "local_address": "", + "description": "", + "type": "", + "local_as": 13335, + "apply_groups": [], + "multihop_ttl": 0, + "remove_private_as": true, + "remote_as": 0, + "import_policy": "reject-all", + "export_policy": "reject-all", + "neighbors": { + "2001:db8::0:2": { + "local_address": "", + "authentication_key": "", + "description": "", + "nhs": false, + "local_as": 13335, + "route_reflector_client": false, + "remote_as": 54113, + "import_policy": "reject-all", + "export_policy": "reject-all", + "prefix_limit": {} + }, + "2001:db8::0:1": { + "local_address": "", + "authentication_key": "", + "description": "", + "nhs": false, + "local_as": 13335, + "route_reflector_client": false, + "remote_as": 8403, + "import_policy": "reject-all", + "export_policy": "reject-all", + "prefix_limit": {} + } + }, + "prefix_limit": {}, + "multipath": false + } +} diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/issue1168/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue1356/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text new file mode 100644 index 000000000..e69de29bb diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/expected_result.json b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/expected_result.json new file mode 100644 index 000000000..ee7922393 --- /dev/null +++ b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/expected_result.json @@ -0,0 +1,112 @@ +{ + "global": { + "peers": { + "fe80::a8c1:abff:fe0b:7b5f%Et5": { + "is_up": true, + "is_enabled": true, + "uptime": "...", + "description": "", + "remote_as": 4259840008, + "remote_id": "172.18.0.8", + "local_as": 4259906562, + "address_family": { + "ipv4": { + "sent_prefixes": 9, + "received_prefixes": 2, + "accepted_prefixes": -1 + }, + "ipv6": { + "sent_prefixes": 9, + "received_prefixes": 2, + "accepted_prefixes": -1 + } + } + }, + "fe80::a8c1:abff:fe27:69e9%Et2": { + "is_up": true, + "is_enabled": true, + "uptime": "...", + "description": "", + "remote_as": 4259973121, + "remote_id": "172.18.8.1", + "local_as": 4259906562, + "address_family": { + "ipv4": { + "sent_prefixes": 9, + "received_prefixes": 5, + "accepted_prefixes": -1 + }, + "ipv6": { + "sent_prefixes": 9, + "received_prefixes": 5, + "accepted_prefixes": -1 + } + } + }, + "fe80::a8c1:abff:fe35:51d9%Et1": { + "is_up": true, + "is_enabled": true, + "uptime": "...", + "description": "", + "remote_as": 4259973120, + "remote_id": "172.18.8.0", + "local_as": 4259906562, + "address_family": { + "ipv4": { + "sent_prefixes": 6, + "received_prefixes": 5, + "accepted_prefixes": -1 + }, + "ipv6": { + "sent_prefixes": 6, + "received_prefixes": 5, + "accepted_prefixes": -1 + } + } + }, + "fe80::a8c1:abff:fe5d:9706%Et4": { + "is_up": true, + "is_enabled": true, + "uptime": "...", + "description": "", + "remote_as": 4259840007, + "remote_id": "172.18.0.7", + "local_as": 4259906562, + "address_family": { + "ipv4": { + "sent_prefixes": 9, + "received_prefixes": 2, + "accepted_prefixes": -1 + }, + "ipv6": { + "sent_prefixes": 9, + "received_prefixes": 2, + "accepted_prefixes": -1 + } + } + }, + "fe80::a8c1:abff:fe95:fa49%Et3": { + "is_up": true, + "is_enabled": true, + "uptime": "...", + "description": "", + "remote_as": 4259840005, + "remote_id": "172.18.0.5", + "local_as": 4259906562, + "address_family": { + "ipv4": { + "sent_prefixes": 9, + "received_prefixes": 2, + "accepted_prefixes": -1 + }, + "ipv6": { + "sent_prefixes": 9, + "received_prefixes": 2, + "accepted_prefixes": -1 + } + } + } + }, + "router_id": "172.18.4.2" + } +} diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text new file mode 100644 index 000000000..e69de29bb diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ip_bgp_summary_vrf_all.json b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ip_bgp_summary_vrf_all.json new file mode 100644 index 000000000..0155ea42a --- /dev/null +++ b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ip_bgp_summary_vrf_all.json @@ -0,0 +1,76 @@ +{ + "vrfs": { + "default": { + "routerId": "172.18.4.2", + "peers": { + "fe80::a8c1:abff:fe0b:7b5f%Et5": { + "msgSent": 239229, + "inMsgQueue": 0, + "prefixReceived": 2, + "upDownTime": 1664912777.128896, + "version": 4, + "prefixAccepted": 2, + "msgReceived": 203694, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65000.8" + }, + "fe80::a8c1:abff:fe27:69e9%Et2": { + "msgSent": 11997, + "inMsgQueue": 0, + "prefixReceived": 5, + "upDownTime": 1664912780.356704, + "version": 4, + "prefixAccepted": 5, + "msgReceived": 11972, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65002.2049" + }, + "fe80::a8c1:abff:fe35:51d9%Et1": { + "msgSent": 11984, + "inMsgQueue": 0, + "prefixReceived": 5, + "upDownTime": 1664912783.670673, + "version": 4, + "prefixAccepted": 5, + "msgReceived": 11979, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65002.2048" + }, + "fe80::a8c1:abff:fe5d:9706%Et4": { + "msgSent": 239170, + "inMsgQueue": 0, + "prefixReceived": 2, + "upDownTime": 1664912777.50903, + "version": 4, + "prefixAccepted": 2, + "msgReceived": 203718, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65000.7" + }, + "fe80::a8c1:abff:fe95:fa49%Et3": { + "msgSent": 239116, + "inMsgQueue": 0, + "prefixReceived": 2, + "upDownTime": 1664912777.604791, + "version": 4, + "prefixAccepted": 2, + "msgReceived": 203718, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65000.5" + } + }, + "vrf": "default", + "asn": "65001.1026" + } + } +} diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text new file mode 100644 index 000000000..f1d5ed371 --- /dev/null +++ b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text @@ -0,0 +1,30 @@ +BGP neighbor is fe80::a8c1:abff:fe0b:7b5f%Et5, remote AS 65000.8, external link + BGP version 4, remote router ID 172.18.0.8, VRF default + BGP state is Established, up for 7d01h + IPv4 Unicast: 9 2 1 0 + IPv6 Unicast: 9 2 1 0 +Local AS is 65001.1026, local router ID 172.18.4.2 +BGP neighbor is fe80::a8c1:abff:fe27:69e9%Et2, remote AS 65002.2049, external link + BGP version 4, remote router ID 172.18.8.1, VRF default + BGP state is Established, up for 7d01h + IPv4 Unicast: 9 5 1 0 + IPv6 Unicast: 9 5 1 0 +Local AS is 65001.1026, local router ID 172.18.4.2 +BGP neighbor is fe80::a8c1:abff:fe35:51d9%Et1, remote AS 65002.2048, external link + BGP version 4, remote router ID 172.18.8.0, VRF default + BGP state is Established, up for 7d01h + IPv4 Unicast: 6 5 4 0 + IPv6 Unicast: 6 5 4 0 +Local AS is 65001.1026, local router ID 172.18.4.2 +BGP neighbor is fe80::a8c1:abff:fe5d:9706%Et4, remote AS 65000.7, external link + BGP version 4, remote router ID 172.18.0.7, VRF default + BGP state is Established, up for 7d01h + IPv4 Unicast: 9 2 1 0 + IPv6 Unicast: 9 2 1 0 +Local AS is 65001.1026, local router ID 172.18.4.2 +BGP neighbor is fe80::a8c1:abff:fe95:fa49%Et3, remote AS 65000.5, external link + BGP version 4, remote router ID 172.18.0.5, VRF default + BGP state is Established, up for 7d01h + IPv4 Unicast: 9 2 1 0 + IPv6 Unicast: 9 2 1 0 +Local AS is 65001.1026, local router ID 172.18.4.2 diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ipv6_bgp_summary_vrf_all.json b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ipv6_bgp_summary_vrf_all.json new file mode 100644 index 000000000..aa96e6d48 --- /dev/null +++ b/test/eos/mocked_data/test_get_bgp_neighbors/issue1759/show_ipv6_bgp_summary_vrf_all.json @@ -0,0 +1,76 @@ +{ + "vrfs": { + "default": { + "routerId": "172.18.4.2", + "peers": { + "fe80::a8c1:abff:fe0b:7b5f%Et5": { + "msgSent": 239193, + "inMsgQueue": 0, + "prefixReceived": 2, + "upDownTime": 1664912777.128896, + "version": 4, + "prefixAccepted": 2, + "msgReceived": 203664, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65000.8" + }, + "fe80::a8c1:abff:fe27:69e9%Et2": { + "msgSent": 11995, + "inMsgQueue": 0, + "prefixReceived": 5, + "upDownTime": 1664912780.356703, + "version": 4, + "prefixAccepted": 5, + "msgReceived": 11970, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65002.2049" + }, + "fe80::a8c1:abff:fe35:51d9%Et1": { + "msgSent": 11982, + "inMsgQueue": 0, + "prefixReceived": 5, + "upDownTime": 1664912783.670674, + "version": 4, + "prefixAccepted": 5, + "msgReceived": 11977, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65002.2048" + }, + "fe80::a8c1:abff:fe5d:9706%Et4": { + "msgSent": 239135, + "inMsgQueue": 0, + "prefixReceived": 2, + "upDownTime": 1664912777.50903, + "version": 4, + "prefixAccepted": 2, + "msgReceived": 203688, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65000.7" + }, + "fe80::a8c1:abff:fe95:fa49%Et3": { + "msgSent": 239080, + "inMsgQueue": 0, + "prefixReceived": 2, + "upDownTime": 1664912777.604791, + "version": 4, + "prefixAccepted": 2, + "msgReceived": 203688, + "peerState": "Established", + "outMsgQueue": 0, + "underMaintenance": false, + "asn": "65000.5" + } + }, + "vrf": "default", + "asn": "65001.1026" + } + } +} diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/issue58_neighbor_down/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/issue944/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ip_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ip_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote_r.text diff --git a/test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text b/test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text similarity index 100% rename from test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ipv6_bgp_neighbors_vrf_all___include_remote_AS___remote_router_ID__IPv_46___Unicast_6PE_____0_9____Local_AS_Desc_BGP_state.text rename to test/eos/mocked_data/test_get_bgp_neighbors/normal/show_ipv6_bgp_neighbors_vrf_all___include_IPv_46___Unicast_6PE_____0_9_____grep__v___IPv_46__Unicast_______remote_AS___Local_AS_Desc_BGP_state__remote.text diff --git a/test/eos/mocked_data/test_get_network_instances/issue-1922/expected_result.json b/test/eos/mocked_data/test_get_network_instances/issue-1922/expected_result.json new file mode 100644 index 000000000..413d219c4 --- /dev/null +++ b/test/eos/mocked_data/test_get_network_instances/issue-1922/expected_result.json @@ -0,0 +1,30 @@ +{ + "management": { + "name": "management", + "type": "L3VRF", + "state": { "route_distinguisher": "" }, + "interfaces": { "interface": { "Management1": {} } } + }, + "default": { + "interfaces": { + "interface": { + "Ethernet1": {}, + "Ethernet2": {}, + "Ethernet3": {}, + "Ethernet4": {}, + "Ethernet49/1": {}, + "Ethernet5": {}, + "Ethernet50/1": {}, + "Ethernet52/1": {}, + "Ethernet53/1": {}, + "Ethernet54/1": {}, + "Ethernet55/1": {}, + "Ethernet56/1": {}, + "Loopback0": {} + } + }, + "state": { "route_distinguisher": "" }, + "type": "DEFAULT_INSTANCE", + "name": "default" + } +} diff --git a/test/eos/mocked_data/test_get_network_instances/issue-1922/show_vrf.text b/test/eos/mocked_data/test_get_network_instances/issue-1922/show_vrf.text new file mode 100644 index 000000000..cd1dbbba4 --- /dev/null +++ b/test/eos/mocked_data/test_get_network_instances/issue-1922/show_vrf.text @@ -0,0 +1,12 @@ +Maximum number of vrfs allowed: 1023 + VRF RD Protocols State Interfaces +---------------- --------------- --------------- ------------------- --------------------------- + default ipv4,ipv6 v4:routing, Ethernet1, Ethernet2, + v6:no routing Ethernet3, Ethernet4, + Ethernet49/1, Ethernet5, + Ethernet50/1, Ethernet52/1, + Ethernet53/1, Ethernet54/1, + Ethernet55/1, Ethernet56/1, + Loopback0 + management ipv4,ipv6 v4:routing, Management1 + v6:no routing diff --git a/test/eos/mocked_data/test_get_network_instances/issue-509/show_vrf.text b/test/eos/mocked_data/test_get_network_instances/issue-509/show_vrf.text index e81cd6e11..65fdb6529 100644 --- a/test/eos/mocked_data/test_get_network_instances/issue-509/show_vrf.text +++ b/test/eos/mocked_data/test_get_network_instances/issue-509/show_vrf.text @@ -1,11 +1,11 @@ Maximum number of vrfs allowed: 14 - Vrf RD Protocols State Interfaces -------- ------------ ------------ ------------------- ------------------- -VRFA0 201:201 ipv4 v4:routing; multicast, Vlan2, Vlan102 - v6:no routing + Vrf RD Protocols State Interfaces +------- --------- ------------ ------------------------ ------------------- + VRFA0 201:201 ipv4 v4:routing; multicast, Vlan2, Vlan102 + v6:no routing -VRFA1 203:203 ipv4 v4:routing; multicast, Vlan3, Vlan103 - v6:no routing + VRFA1 203:203 ipv4 v4:routing; multicast, Vlan3, Vlan103 + v6:no routing -VRFA2 205:205 ipv4 v4:routing; multicast, Ethernet1, Vlan100 - v6:no routing + VRFA2 205:205 ipv4 v4:routing; multicast, Ethernet1, Vlan100 + v6:no routing diff --git a/test/eos/mocked_data/test_get_network_instances/vrf/cli_version.txt b/test/eos/mocked_data/test_get_network_instances/vrf/cli_version.txt new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/test/eos/mocked_data/test_get_network_instances/vrf/cli_version.txt @@ -0,0 +1 @@ +2 diff --git a/test/eos/mocked_data/test_get_network_instances/vrf/expected_result.json b/test/eos/mocked_data/test_get_network_instances/vrf/expected_result.json new file mode 100644 index 000000000..dc3f3d294 --- /dev/null +++ b/test/eos/mocked_data/test_get_network_instances/vrf/expected_result.json @@ -0,0 +1,31 @@ +{ + "default": { + "name": "default", + "type": "DEFAULT_INSTANCE", + "state": { "route_distinguisher": "" }, + "interfaces": { + "interface": { + "Ethernet1": {}, + "Ethernet2": {}, + "Loopback0": {}, + "Management0": {} + } + } + }, + "foo": { + "name": "foo", + "type": "L3VRF", + "state": { "route_distinguisher": "0:1" }, + "interfaces": { + "interface": {} + } + }, + "bar": { + "name": "bar", + "type": "L3VRF", + "state": { "route_distinguisher": "" }, + "interfaces": { + "interface": {} + } + } +} diff --git a/test/eos/mocked_data/test_get_network_instances/vrf/show_vrf.json b/test/eos/mocked_data/test_get_network_instances/vrf/show_vrf.json new file mode 100644 index 000000000..bbc04245a --- /dev/null +++ b/test/eos/mocked_data/test_get_network_instances/vrf/show_vrf.json @@ -0,0 +1,61 @@ +{ + "vrfs": { + "foo": { + "routeDistinguisher": "0:1", + "protocols": { + "ipv4": { + "supported": true, + "protocolState": "up", + "routingState": "up" + }, + "ipv6": { + "supported": true, + "protocolState": "up", + "routingState": "down" + } + }, + "vrfState": "up", + "interfacesV4": [], + "interfacesV6": [], + "interfaces": [] + }, + "bar": { + "routeDistinguisher": "", + "protocols": { + "ipv4": { + "supported": true, + "protocolState": "up", + "routingState": "down" + }, + "ipv6": { + "supported": true, + "protocolState": "up", + "routingState": "down" + } + }, + "vrfState": "up", + "interfacesV4": [], + "interfacesV6": [], + "interfaces": [] + }, + "default": { + "routeDistinguisher": "", + "protocols": { + "ipv4": { + "supported": true, + "protocolState": "up", + "routingState": "up" + }, + "ipv6": { + "supported": true, + "protocolState": "up", + "routingState": "down" + } + }, + "vrfState": "up", + "interfacesV4": ["Ethernet1", "Ethernet2", "Loopback0", "Management0"], + "interfacesV6": ["Management0"], + "interfaces": ["Ethernet1", "Ethernet2", "Loopback0", "Management0"] + } + } +} diff --git a/test/ios/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json b/test/ios/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json index f74d5a593..d6a2343ac 100644 --- a/test/ios/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json +++ b/test/ios/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json @@ -5,7 +5,7 @@ "export_policy": "PASS-OUT", "import_policy": "PASS-IN", "local_address": "GigabitEthernet1", - "local_as": 0, + "local_as": 65001, "multihop_ttl": 0, "multipath": false, "neighbors": { @@ -15,7 +15,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65001, "nhs": false, "prefix_limit": { "inet": { @@ -37,7 +37,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65001, "nhs": false, "prefix_limit": { "inet": { @@ -75,7 +75,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65001, "multihop_ttl": 0, "multipath": false, "neighbors": { diff --git a/test/ios/mocked_data/test_get_bgp_config/no_afi/expected_result.json b/test/ios/mocked_data/test_get_bgp_config/no_afi/expected_result.json index 6c5a9cbaa..b75943177 100644 --- a/test/ios/mocked_data/test_get_bgp_config/no_afi/expected_result.json +++ b/test/ios/mocked_data/test_get_bgp_config/no_afi/expected_result.json @@ -2,7 +2,7 @@ "_": { "apply_groups": [], "description": "", - "local_as": 0, + "local_as": 42, "type": "", "import_policy": "", "export_policy": "", @@ -20,7 +20,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 42, "authentication_key": "", "nhs": false, "route_reflector_client": false diff --git a/test/ios/mocked_data/test_get_bgp_config/no_bgp/expected_result.json b/test/ios/mocked_data/test_get_bgp_config/no_bgp/expected_result.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/ios/mocked_data/test_get_bgp_config/no_bgp/expected_result.json @@ -0,0 +1 @@ +{} diff --git a/test/ios/mocked_data/test_get_bgp_config/no_bgp/show_running_config.txt b/test/ios/mocked_data/test_get_bgp_config/no_bgp/show_running_config.txt new file mode 100644 index 000000000..50e08a544 --- /dev/null +++ b/test/ios/mocked_data/test_get_bgp_config/no_bgp/show_running_config.txt @@ -0,0 +1,150 @@ +! +! Last configuration change at 18:41:02 UTC Thu Nov 24 2016 +! +version 15.5 +service timestamps debug datetime msec +service timestamps log datetime msec +no platform punt-keepalive disable-kernel-core +platform console auto +! +hostname CSR1 +! +boot-start-marker +boot-end-marker +! +! +enable password cisco +! +aaa new-model +! +! +aaa authentication login default local +aaa authorization exec default local +! +! +! +! +! +aaa session-id common +! +ip vrf MGMT +! +! +! +! +! +! +! +! +! + + +ip domain name example.local + +! +! +! +! +! +! +! +! +! +! +subscriber templating +! +multilink bundle-name authenticated +! +! +! +! +! +! +! +! +! +! +! +! +! +license udi pid CSR1000V sn 9OSEGKJXRHE +spanning-tree extend system-id +! +username cisco privilege 15 password 0 cisco +! +redundancy +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +interface Loopback0 + ip address 1.1.1.1 255.255.255.255 +! +interface GigabitEthernet1 + ip vrf forwarding MGMT + ip address 192.168.35.121 255.255.255.0 + negotiation auto +! +interface GigabitEthernet2 + ip address 10.1.1.1 255.255.255.0 + negotiation auto +! +interface GigabitEthernet3 + no ip address + shutdown + negotiation auto +! +router ospf 1 + redistribute connected subnets + network 10.1.1.0 0.0.0.255 area 0 +! +! +! +! +virtual-service csr_mgmt +! +ip forward-protocol nd +! +no ip http server +no ip http secure-server +! +! +! +! +! +! +control-plane +! + ! + ! + ! + ! +! +! +! +! +! +line con 0 +line vty 0 4 +! +! +end diff --git a/test/ios/mocked_data/test_get_bgp_config/normal/expected_result.json b/test/ios/mocked_data/test_get_bgp_config/normal/expected_result.json index 3f4742397..f58f5e0c5 100644 --- a/test/ios/mocked_data/test_get_bgp_config/normal/expected_result.json +++ b/test/ios/mocked_data/test_get_bgp_config/normal/expected_result.json @@ -1,11 +1,26 @@ { + "_": { + "apply_groups": [], + "description": "", + "local_as": 65001, + "type": "", + "import_policy": "", + "export_policy": "", + "local_address": "", + "multipath": false, + "multihop_ttl": 0, + "remote_as": 0, + "remove_private_as": false, + "prefix_limit": {}, + "neighbors": {} + }, "RR-CLIENTS": { "apply_groups": [], "description": "[ibgp - rr clients]", "export_policy": "PASS-OUT", "import_policy": "PASS-IN", "local_address": "GigabitEthernet1", - "local_as": 0, + "local_as": 65001, "multihop_ttl": 0, "multipath": false, "neighbors": { @@ -15,7 +30,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65001, "nhs": false, "prefix_limit": { "inet": { @@ -37,7 +52,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65001, "nhs": false, "prefix_limit": { "inet": { diff --git a/test/ios/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json b/test/ios/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json index d2dfbaf32..1f53b03ff 100644 --- a/test/ios/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json +++ b/test/ios/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json @@ -5,7 +5,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65001, "multihop_ttl": 0, "multipath": false, "neighbors": { @@ -15,7 +15,7 @@ "export_policy": "PASS-OUT", "import_policy": "PASS-IN", "local_address": "GigabitEthernet1", - "local_as": 0, + "local_as": 65001, "nhs": false, "prefix_limit": { "inet": { @@ -37,4 +37,4 @@ "remove_private_as": false, "type": "" } -} \ No newline at end of file +} diff --git a/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/expected_result.json b/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/expected_result.json new file mode 100644 index 000000000..49bc80227 --- /dev/null +++ b/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/expected_result.json @@ -0,0 +1,15 @@ +{ + "GigabitEthernet9/48": [ + { + "port": "Gi0", + "hostname": "COMPUTER.company.example.com" + } + ] + , + "GigabitEthernet9/8": [ + { + "port": "A1:8B:95:B5:E4:6F", + "hostname": "NICEHOSTNAME" + } + ] +} diff --git a/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/show_lldp_neighbors.txt b/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/show_lldp_neighbors.txt new file mode 100644 index 000000000..f7074e0d7 --- /dev/null +++ b/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/show_lldp_neighbors.txt @@ -0,0 +1,8 @@ + +Capability codes: + (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device + (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other + +Device ID Local Intf Hold-time Capability Port ID +ACOMPUTER.company.exGi9/48 120 B Gi0 +NICEHOSTNAME Gi9/8 3601 a18b.95b5.e46f diff --git a/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/show_lldp_neighbors_detail.txt b/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/show_lldp_neighbors_detail.txt new file mode 100644 index 000000000..5908277d6 --- /dev/null +++ b/test/ios/mocked_data/test_get_lldp_neighbors/no_mac_support/show_lldp_neighbors_detail.txt @@ -0,0 +1,68 @@ +------------------------------------------------ +Local Intf: Gi9/48 +Chassis id: 4a07.d0f3.fbb6 +Port id: Gi0 +Port Description: GigabitEthernet0 +System Name: COMPUTER.company.example.com + +System Description: +Cisco IOS Software, C3600 Software (AP3G2-K9W8-M), Version 15.3(3)JC15, RELEASE SOFTWARE (fc1) +Technical Support: http://www.cisco.com/techsupport +Copyright (c) 1986-2018 by Cisco Systems, Inc. +Compiled Thu 07-Jun-18 16:43 by prod_rel_team + +Time remaining: 95 seconds +System Capabilities: B +Enabled Capabilities: B +Management Addresses: + IP: 10.31.18.65 +Auto Negotiation - supported, enabled +Physical media capabilities: + 1000baseT(FD) + 1000baseT(HD) + 100base-TX(FD) + 100base-TX(HD) + 10base-T(FD) + 10base-T(HD) +Media Attachment Unit type: 30 +Vlan ID: - not advertised +PoE+ Power-via-MDI TLV: + Power Pair: Signal + Power Class: Class 4 + Power Device Type: Type 1 PD + Power Source: PSE + Power Priority: high + Power Requested: 13000 mW + Power Allocated: 13000 mW + +------------------------------------------------ +Local Intf: Gi9/8 +Chassis id: NICEHOSTNAME +Port id: a18b.95b5.e46f +Port Description - not advertised +System Name - not advertised +System Description - not advertised + +Time remaining: 2690 seconds +System Capabilities - not advertised +Enabled Capabilities - not advertised +Management Addresses - not advertised +Auto Negotiation - supported, enabled +Physical media capabilities: + 1000baseT(FD) +Media Attachment Unit type - not advertised +Vlan ID: - not advertised + +MED Information: + + MED Codes: + (NP) Network Policy, (LI) Location Identification + (PS) Power Source Entity, (PD) Power Device + (IN) Inventory + + Inventory information - not advertised + Capabilities: + Device type: Endpoint Class I + Network Policies - not advertised + Power requirements - not advertised + Location - not advertised diff --git a/test/iosxr/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json b/test/iosxr/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json index e39f06c91..60e669b26 100644 --- a/test/iosxr/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json +++ b/test/iosxr/mocked_data/test_get_bgp_config/mixed_with_without_groups/expected_result.json @@ -8,7 +8,7 @@ "description": "", "route_reflector_client": false, "nhs": false, - "local_as": 0, + "local_as": 65900, "import_policy": "pass-all", "local_address": "", "prefix_limit": { @@ -30,7 +30,7 @@ "description": "", "route_reflector_client": false, "nhs": false, - "local_as": 0, + "local_as": 65900, "import_policy": "pass-all", "local_address": "", "prefix_limit": { @@ -54,7 +54,7 @@ "remove_private_as": false, "description": "", "export_policy": "", - "local_as": 0, + "local_as": 65900, "apply_groups": [], "multipath": false, "prefix_limit": {} @@ -68,7 +68,7 @@ "description": "", "route_reflector_client": false, "nhs": false, - "local_as": 0, + "local_as": 65900, "import_policy": "RP-SPECIAL-SNOWFLAKE-IN", "local_address": "", "prefix_limit": { @@ -90,7 +90,7 @@ "description": "", "route_reflector_client": false, "nhs": false, - "local_as": 0, + "local_as": 65900, "import_policy": "", "local_address": "", "prefix_limit": {}, @@ -104,7 +104,7 @@ "remove_private_as": true, "description": "", "export_policy": "", - "local_as": 0, + "local_as": 65900, "apply_groups": [], "multipath": false, "prefix_limit": {} diff --git a/test/iosxr/mocked_data/test_get_bgp_config/no_bgp/_Get__Configuration__BGP__Instance__Naming__________InstanceName_default__InstanceName___Naming___Instance___BGP___Configuration___Get_.txt b/test/iosxr/mocked_data/test_get_bgp_config/no_bgp/_Get__Configuration__BGP__Instance__Naming__________InstanceName_default__InstanceName___Naming___Instance___BGP___Configuration___Get_.txt new file mode 100644 index 000000000..1bcd305b2 --- /dev/null +++ b/test/iosxr/mocked_data/test_get_bgp_config/no_bgp/_Get__Configuration__BGP__Instance__Naming__________InstanceName_default__InstanceName___Naming___Instance___BGP___Configuration___Get_.txt @@ -0,0 +1,2 @@ + +default diff --git a/test/iosxr/mocked_data/test_get_bgp_config/no_bgp/expected_result.json b/test/iosxr/mocked_data/test_get_bgp_config/no_bgp/expected_result.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/iosxr/mocked_data/test_get_bgp_config/no_bgp/expected_result.json @@ -0,0 +1 @@ +{} diff --git a/test/iosxr/mocked_data/test_get_bgp_config/normal/expected_result.json b/test/iosxr/mocked_data/test_get_bgp_config/normal/expected_result.json index 20971ddf8..c850fe601 100644 --- a/test/iosxr/mocked_data/test_get_bgp_config/normal/expected_result.json +++ b/test/iosxr/mocked_data/test_get_bgp_config/normal/expected_result.json @@ -1,13 +1,28 @@ { + "_": { + "local_as": 13335, + "remove_private_as": false, + "import_policy": "", + "multihop_ttl": 0, + "neighbors": {}, + "multipath": false, + "export_policy": "", + "type": "", + "apply_groups": [], + "remote_as": 0, + "prefix_limit": {}, + "description": "", + "local_address": "" + }, "4-public-anycast-peers": { - "local_as": 0, + "local_as": 13335, "remove_private_as": true, "import_policy": "4-public-anycast-peers-in", "multihop_ttl": 0, "neighbors": { "192.168.20.3": { "export_policy": "", - "local_as": 0, + "local_as": 13335, "prefix_limit": { "inet": { "unicast": { @@ -29,7 +44,7 @@ }, "172.17.17.50": { "export_policy": "", - "local_as": 0, + "local_as": 13335, "prefix_limit": { "inet": { "unicast": { @@ -51,7 +66,7 @@ }, "192.168.50.5": { "export_policy": "", - "local_as": 0, + "local_as": 13335, "prefix_limit": { "inet": { "unicast": { diff --git a/test/iosxr/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json b/test/iosxr/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json index bc0239416..30a30dd4c 100644 --- a/test/iosxr/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json +++ b/test/iosxr/mocked_data/test_get_bgp_config/peers_without_groups/expected_result.json @@ -10,7 +10,7 @@ "remove_private_as": false, "remote_as": 0, "multihop_ttl": 0, - "local_as": 0, + "local_as": 65900, "apply_groups": [], "neighbors": { "10.255.255.2": { @@ -26,7 +26,7 @@ } } }, - "local_as": 0, + "local_as": 65900, "description": "", "local_address": "", "import_policy": "pass-all", @@ -48,7 +48,7 @@ } } }, - "local_as": 0, + "local_as": 65900, "description": "", "local_address": "", "import_policy": "pass-all", diff --git a/test/iosxr_netconf/mocked_data/test_get_bgp_config/no_bgp/expected_result.json b/test/iosxr_netconf/mocked_data/test_get_bgp_config/no_bgp/expected_result.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/iosxr_netconf/mocked_data/test_get_bgp_config/no_bgp/expected_result.json @@ -0,0 +1 @@ +{} diff --git a/test/iosxr_netconf/mocked_data/test_get_bgp_config/no_bgp/ipv4-bgp-cfg_bgp__running.xml b/test/iosxr_netconf/mocked_data/test_get_bgp_config/no_bgp/ipv4-bgp-cfg_bgp__running.xml new file mode 100644 index 000000000..7a3a0b218 --- /dev/null +++ b/test/iosxr_netconf/mocked_data/test_get_bgp_config/no_bgp/ipv4-bgp-cfg_bgp__running.xml @@ -0,0 +1,6 @@ + + + + diff --git a/test/iosxr_netconf/mocked_data/test_get_bgp_config/normal/expected_result.json b/test/iosxr_netconf/mocked_data/test_get_bgp_config/normal/expected_result.json index 1d60e8893..4ed653140 100644 --- a/test/iosxr_netconf/mocked_data/test_get_bgp_config/normal/expected_result.json +++ b/test/iosxr_netconf/mocked_data/test_get_bgp_config/normal/expected_result.json @@ -1,11 +1,26 @@ { + "_": { + "apply_groups": [], + "description": "", + "export_policy": "", + "import_policy": "", + "local_address": "", + "local_as": 65172, + "multihop_ttl": 0, + "multipath": false, + "neighbors": {}, + "prefix_limit": {}, + "remote_as": 0, + "remove_private_as": false, + "type": "" + }, "EBGP": { "apply_groups": [], "description": "", "export_policy": "EBGP-OUT-POLICY", "import_policy": "EBGP-IN-POLICY", "local_address": "", - "local_as": 0, + "local_as": 65172, "multihop_ttl": 0, "multipath": false, "neighbors": { @@ -15,7 +30,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "nhs": false, "prefix_limit": { "inet": { @@ -37,7 +52,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "nhs": false, "prefix_limit": { "inet": { @@ -65,7 +80,7 @@ "export_policy": "EBGP-VRF-OUT-POLICY", "import_policy": "EBGP-VRF-IN-POLICY", "local_address": "", - "local_as": 0, + "local_as": 65172, "multihop_ttl": 0, "multipath": false, "neighbors": {}, @@ -80,7 +95,7 @@ "export_policy": "IBGPv6-OUT-POLICY", "import_policy": "IBGPv6-IN-POLICY", "local_address": "", - "local_as": 0, + "local_as": 65172, "multihop_ttl": 0, "multipath": false, "neighbors": { @@ -90,7 +105,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "nhs": false, "prefix_limit": { "inet6": { @@ -112,7 +127,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "nhs": false, "prefix_limit": { "inet6": { @@ -140,7 +155,7 @@ "export_policy": "EBGPv6-VRF-OUT-POLICY", "import_policy": "EBGPv6-VRF-IN-POLICY", "local_address": "", - "local_as": 0, + "local_as": 65172, "multihop_ttl": 0, "multipath": false, "neighbors": {}, @@ -155,7 +170,7 @@ "export_policy": "IBGP-OUT-POLICY", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "multihop_ttl": 0, "multipath": false, "neighbors": { @@ -165,7 +180,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "nhs": false, "prefix_limit": { "inet": { @@ -193,7 +208,7 @@ "export_policy": "IBGP-OUT-POLICY", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "multihop_ttl": 0, "multipath": false, "neighbors": { @@ -203,7 +218,7 @@ "export_policy": "", "import_policy": "", "local_address": "", - "local_as": 0, + "local_as": 65172, "nhs": false, "prefix_limit": { "inet6": { diff --git a/test/junos/mocked_data/test_get_bgp_config/nhs/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml b/test/junos/mocked_data/test_get_bgp_config/nhs/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml new file mode 100644 index 000000000..bd581bc2c --- /dev/null +++ b/test/junos/mocked_data/test_get_bgp_config/nhs/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml @@ -0,0 +1,7 @@ + + + + 65001 + + + diff --git a/test/junos/mocked_data/test_get_bgp_config/nhs/expected_result.json b/test/junos/mocked_data/test_get_bgp_config/nhs/expected_result.json index 93c77a2ec..a375a75a0 100644 --- a/test/junos/mocked_data/test_get_bgp_config/nhs/expected_result.json +++ b/test/junos/mocked_data/test_get_bgp_config/nhs/expected_result.json @@ -1 +1,73 @@ -{"internal":{"apply_groups":[],"description":"","export_policy":"","import_policy":"","local_address":"","local_as":0,"multihop_ttl":0,"multipath":false,"neighbors":{"10.10.10.1":{"authentication_key":"","description":"","export_policy":"nhs","import_policy":"","local_address":"","local_as":0,"nhs":true,"prefix_limit":{},"remote_as":0,"route_reflector_client":false}},"prefix_limit":{},"remote_as":0,"remove_private_as":false,"type":"internal"},"internal-2":{"apply_groups":[],"description":"","export_policy":"","import_policy":"","local_address":"","local_as":0,"multihop_ttl":0,"multipath":false,"neighbors":{"10.10.10.2":{"authentication_key":"","description":"","export_policy":"static","import_policy":"","local_address":"","local_as":0,"nhs":false,"prefix_limit":{},"remote_as":0,"route_reflector_client":false}},"prefix_limit":{},"remote_as":0,"remove_private_as":false,"type":"internal"}} +{ + "_": { + "export_policy": "", + "multipath": false, + "prefix_limit": {}, + "description": "", + "local_as": 65001, + "multihop_ttl": 0, + "apply_groups": [], + "remote_as": 0, + "remove_private_as": false, + "local_address": "", + "type": "", + "import_policy": "", + "neighbors": {} + }, + "internal": { + "apply_groups": [], + "description": "", + "export_policy": "", + "import_policy": "", + "local_address": "", + "local_as": 65001, + "multihop_ttl": 0, + "multipath": false, + "neighbors": { + "10.10.10.1": { + "authentication_key": "", + "description": "", + "export_policy": "nhs", + "import_policy": "", + "local_address": "", + "local_as": 65001, + "nhs": true, + "prefix_limit": {}, + "remote_as": 0, + "route_reflector_client": false + } + }, + "prefix_limit": {}, + "remote_as": 0, + "remove_private_as": false, + "type": "internal" + }, + "internal-2": { + "apply_groups": [], + "description": "", + "export_policy": "", + "import_policy": "", + "local_address": "", + "local_as": 65001, + "multihop_ttl": 0, + "multipath": false, + "neighbors": { + "10.10.10.2": { + "authentication_key": "", + "description": "", + "export_policy": "static", + "import_policy": "", + "local_address": "", + "local_as": 65001, + "nhs": false, + "prefix_limit": {}, + "remote_as": 0, + "route_reflector_client": false + } + }, + "prefix_limit": {}, + "remote_as": 0, + "remove_private_as": false, + "type": "internal" + } +} diff --git a/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__policy_options__policy_statement____policy_options___configuration_.xml b/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__policy_options__policy_statement____policy_options___configuration_.xml new file mode 100644 index 000000000..83138436e --- /dev/null +++ b/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__policy_options__policy_statement____policy_options___configuration_.xml @@ -0,0 +1,2 @@ + + diff --git a/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__protocols__bgp__group____bgp___protocols___configuration_.xml b/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__protocols__bgp__group____bgp___protocols___configuration_.xml new file mode 100644 index 000000000..83138436e --- /dev/null +++ b/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__protocols__bgp__group____bgp___protocols___configuration_.xml @@ -0,0 +1,2 @@ + + diff --git a/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml b/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml new file mode 100644 index 000000000..83138436e --- /dev/null +++ b/test/junos/mocked_data/test_get_bgp_config/no_bgp/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml @@ -0,0 +1,2 @@ + + diff --git a/test/junos/mocked_data/test_get_bgp_config/no_bgp/expected_result.json b/test/junos/mocked_data/test_get_bgp_config/no_bgp/expected_result.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/junos/mocked_data/test_get_bgp_config/no_bgp/expected_result.json @@ -0,0 +1 @@ +{} diff --git a/test/junos/mocked_data/test_get_bgp_config/normal/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml b/test/junos/mocked_data/test_get_bgp_config/normal/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml new file mode 100644 index 000000000..6ad44d234 --- /dev/null +++ b/test/junos/mocked_data/test_get_bgp_config/normal/_configuration__routing_options__autonomous_system____routing_options___configuration_.xml @@ -0,0 +1,7 @@ + + + + 13335 + + + diff --git a/test/junos/mocked_data/test_get_bgp_config/normal/expected_result.json b/test/junos/mocked_data/test_get_bgp_config/normal/expected_result.json index 868ef99cc..6c44290dd 100644 --- a/test/junos/mocked_data/test_get_bgp_config/normal/expected_result.json +++ b/test/junos/mocked_data/test_get_bgp_config/normal/expected_result.json @@ -1 +1,69 @@ -{"PEERS-GROUP-NAME": {"neighbors": {"192.168.0.1": {"export_policy": "", "prefix_limit": {"inet": {"unicast": {"limit": 100}}}, "route_reflector_client": false, "description": "Facebook [CDN]", "local_as": 0, "nhs": false, "local_address": "", "remote_as": 32934, "authentication_key": "", "import_policy": ""}, "172.17.17.1": {"export_policy": "", "prefix_limit": {"inet": {"unicast": {"limit": 500}}}, "route_reflector_client": false, "description": "Twitter [CDN]", "local_as": 0, "nhs": false, "local_address": "", "remote_as": 13414, "authentication_key": "", "import_policy": ""}}, "export_policy": "PUBLIC-PEER-OUT", "multipath": true, "prefix_limit": {}, "description": "", "local_as": 13335, "multihop_ttl": 0, "apply_groups": ["B", "G", "P", "-", "P", "R", "E", "F", "I", "X", "-", "L", "I", "M", "I", "T"], "remote_as": 0, "remove_private_as": true, "local_address": "", "type": "external", "import_policy": "PUBLIC-PEER-IN"}} +{ + "_": { + "export_policy": "", + "multipath": false, + "prefix_limit": {}, + "description": "", + "local_as": 13335, + "multihop_ttl": 0, + "apply_groups": [], + "remote_as": 0, + "remove_private_as": false, + "local_address": "", + "type": "", + "import_policy": "", + "neighbors": {} + }, + "PEERS-GROUP-NAME": { + "neighbors": { + "192.168.0.1": { + "export_policy": "", + "prefix_limit": { + "inet": { + "unicast": { + "limit": 100 + } + } + }, + "route_reflector_client": false, + "description": "Facebook [CDN]", + "local_as": 13335, + "nhs": false, + "local_address": "", + "remote_as": 32934, + "authentication_key": "", + "import_policy": "" + }, + "172.17.17.1": { + "export_policy": "", + "prefix_limit": { + "inet": { + "unicast": { + "limit": 500 + } + } + }, + "route_reflector_client": false, + "description": "Twitter [CDN]", + "local_as": 13335, + "nhs": false, + "local_address": "", + "remote_as": 13414, + "authentication_key": "", + "import_policy": "" + } + }, + "export_policy": "PUBLIC-PEER-OUT", + "multipath": true, + "prefix_limit": {}, + "description": "", + "local_as": 13335, + "multihop_ttl": 0, + "apply_groups": ["BGP-PREFIX-LIMIT"], + "remote_as": 0, + "remove_private_as": true, + "local_address": "", + "type": "external", + "import_policy": "PUBLIC-PEER-IN" + } +} diff --git a/test/nxos_ssh/mocked_data/test_get_network_instances/normal/expected_result.json b/test/nxos_ssh/mocked_data/test_get_network_instances/normal/expected_result.json new file mode 100644 index 000000000..ca3d2f110 --- /dev/null +++ b/test/nxos_ssh/mocked_data/test_get_network_instances/normal/expected_result.json @@ -0,0 +1,36 @@ +{ + "management": { + "name": "management", + "type": "L3VRF", + "state": { + "route_distinguisher": "0:0" + }, + "interfaces": { + "interface": { + "mgmt0": {} + } + } + }, + "default": { + "name": "default", + "type": "DEFAULT_INSTANCE", + "state": { + "route_distinguisher": "0:0" + }, + "interfaces": { + "interface": { + "Vlan1": {}, + "Vlan100": {}, + "Vlan101": {}, + "Vlan102": {}, + "Vlan103": {}, + "Vlan104": {}, + "Vlan105": {}, + "loopback1": {}, + "Null0": {}, + "Ethernet1/5": {}, + "Ethernet1/5.1": {} + } + } + } +} diff --git a/test/nxos_ssh/mocked_data/test_get_network_instances/normal/show_vrf_detail___json.txt b/test/nxos_ssh/mocked_data/test_get_network_instances/normal/show_vrf_detail___json.txt new file mode 100644 index 000000000..637f62365 --- /dev/null +++ b/test/nxos_ssh/mocked_data/test_get_network_instances/normal/show_vrf_detail___json.txt @@ -0,0 +1,58 @@ +{ + "TABLE_vrf": { + "ROW_vrf": [ + { + "vrf_name": "default", + "vrf_id": 1, + "vrf_state": "Up", + "vpnid": "unknown", + "rd": "0:0", + "vni": 0, + "max_routes": 0, + "mid_threshold": 0, + "TABLE_tib": { + "ROW_tib": [ + { + "tib_id": 80000001, + "tib_af": "IPv6", + "tib_nonce": 80000001, + "tib_state": "Up" + }, + { + "tib_id": 1, + "tib_af": "IPv4", + "tib_nonce": 1, + "tib_state": "Up" + } + ] + } + }, + { + "vrf_name": "management", + "vrf_id": 2, + "vrf_state": "Up", + "vpnid": "unknown", + "rd": "0:0", + "vni": 0, + "max_routes": 0, + "mid_threshold": 0, + "TABLE_tib": { + "ROW_tib": [ + { + "tib_id": 80000002, + "tib_af": "IPv6", + "tib_nonce": 80000002, + "tib_state": "Up" + }, + { + "tib_id": 2, + "tib_af": "IPv4", + "tib_nonce": 2, + "tib_state": "Up" + } + ] + } + } + ] + } +} diff --git a/test/nxos_ssh/mocked_data/test_get_network_instances/normal/show_vrf_interface___json.txt b/test/nxos_ssh/mocked_data/test_get_network_instances/normal/show_vrf_interface___json.txt new file mode 100644 index 000000000..72a325a0d --- /dev/null +++ b/test/nxos_ssh/mocked_data/test_get_network_instances/normal/show_vrf_interface___json.txt @@ -0,0 +1,78 @@ +{ + "TABLE_if": { + "ROW_if": [ + { + "if_name": "Vlan1", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Vlan100", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Vlan101", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Vlan102", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Vlan103", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Vlan104", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Vlan105", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "loopback1", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Null0", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Ethernet1/5", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "Ethernet1/5.1", + "vrf_name": "default", + "vrf_id": 1, + "soo": "--" + }, + { + "if_name": "mgmt0", + "vrf_name": "management", + "vrf_id": 2, + "soo": "--" + } + ] + } +} diff --git a/test/pyiosxr/test_iosxr.py b/test/pyiosxr/test_iosxr.py index 9c8128c45..b8485659d 100755 --- a/test/pyiosxr/test_iosxr.py +++ b/test/pyiosxr/test_iosxr.py @@ -257,7 +257,7 @@ def test__getattr_show_config(self): def test__getattr__no_show(self): - """Test special attribute __getattr__ agains a no-show command""" + """Test special attribute __getattr__ against a no-show command""" raised = False diff --git a/tox.ini b/tox.ini deleted file mode 100644 index a6f11697f..000000000 --- a/tox.ini +++ /dev/null @@ -1,39 +0,0 @@ -[tox] -envlist = py3{6,7,8},black,pylama -skip_missing_interpreters = true - -[testenv] -deps = - -rrequirements.txt - -rrequirements-dev.txt -passenv = * - -commands = - py.test --cov=napalm --cov-report term-missing -vs --pylama {posargs} - -[testenv:black] -deps = black==20.8b1 - -basepython = python3.6 -commands = - black --check . - -[testenv:pylama] -deps = - -rrequirements-dev.txt - -basepython = python3.6 -commands = - pylama . - -[testenv:sphinx] -deps = - -rdocs/requirements.txt - -basepython = python3.6 - -commands = - make doctest - -whitelist_externals = - make