From bca2c654615dc419777af4ae0a1db30c7e839539 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 15:50:18 +0200 Subject: [PATCH 1/9] ci: Remove blocked actions (#693) --- .github/workflows/code-testing.yml | 37 +++++++++++------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/.github/workflows/code-testing.yml b/.github/workflows/code-testing.yml index 5c06d4552..d8b2879a9 100644 --- a/.github/workflows/code-testing.yml +++ b/.github/workflows/code-testing.yml @@ -59,30 +59,19 @@ jobs: pip install . - name: install dev requirements run: pip install .[dev] - missing-documentation: - name: "Warning documentation is missing" - runs-on: ubuntu-20.04 - needs: [file-changes] - if: needs.file-changes.outputs.cli == 'true' && needs.file-changes.outputs.docs == 'false' - steps: - - name: Documentation is missing - uses: GrantBirki/comment@v2.0.10 - with: - body: | - Please consider that documentation is missing under `docs/` folder. - You should update documentation to reflect your change, or maybe not :) - lint-yaml: - name: Run linting for yaml files - runs-on: ubuntu-20.04 - needs: [file-changes, check-requirements] - if: needs.file-changes.outputs.code == 'true' - steps: - - uses: actions/checkout@v4 - - name: yaml-lint - uses: ibiqlik/action-yamllint@v3 - with: - config_file: .yamllint.yml - file_or_dir: . + # @gmuloc: commenting this out for now + #missing-documentation: + # name: "Warning documentation is missing" + # runs-on: ubuntu-20.04 + # needs: [file-changes] + # if: needs.file-changes.outputs.cli == 'true' && needs.file-changes.outputs.docs == 'false' + # steps: + # - name: Documentation is missing + # uses: GrantBirki/comment@v2.0.10 + # with: + # body: | + # Please consider that documentation is missing under `docs/` folder. + # You should update documentation to reflect your change, or maybe not :) lint-python: name: Check the code style runs-on: ubuntu-20.04 From 56dd832354355b3d6c55f3bca002d2bf57efa273 Mon Sep 17 00:00:00 2001 From: Thomas Grimonet Date: Wed, 5 Jun 2024 15:50:42 +0200 Subject: [PATCH 2/9] feat(anta.tests): Add testcases for ISIS SR Adjascency Segments (#678) --------- Co-authored-by: gmuloc --- anta/tests/routing/isis.py | 429 +++++- docs/stylesheets/extra.material.css | 6 +- docs/usage-inventory-catalog.md | 2 +- pyproject.toml | 4 + tests/conftest.py | 5 +- tests/units/anta_tests/routing/test_isis.py | 1350 ++++++++++++++++++- 6 files changed, 1785 insertions(+), 11 deletions(-) diff --git a/anta/tests/routing/isis.py b/anta/tests/routing/isis.py index addc08375..afa75b548 100644 --- a/anta/tests/routing/isis.py +++ b/anta/tests/routing/isis.py @@ -7,6 +7,7 @@ # mypy: disable-error-code=attr-defined from __future__ import annotations +from ipaddress import IPv4Address, IPv4Network from typing import Any, ClassVar, Literal from pydantic import BaseModel @@ -118,6 +119,20 @@ def _get_interface_data(interface: str, vrf: str, command_output: dict[str, Any] return None +def _get_adjacency_segment_data_by_neighbor(neighbor: str, instance: str, vrf: str, command_output: dict[str, Any]) -> dict[str, Any] | None: + """Extract data related to an IS-IS interface for testing.""" + search_path = f"vrfs.{vrf}.isisInstances.{instance}.adjacencySegments" + if get_value(dictionary=command_output, key=search_path, default=None) is None: + return None + + isis_instance = get_value(dictionary=command_output, key=search_path, default=None) + + return next( + (segment_data for segment_data in isis_instance if neighbor == segment_data["ipAddress"]), + None, + ) + + class VerifyISISNeighborState(AntaTest): """Verifies all IS-IS neighbors are in UP state. @@ -211,14 +226,15 @@ def test(self) -> None: isis_neighbor_count = _get_isis_neighbors_count(command_output) if len(isis_neighbor_count) == 0: self.result.is_skipped("No IS-IS neighbor detected") + return for interface in self.inputs.interfaces: eos_data = [ifl_data for ifl_data in isis_neighbor_count if ifl_data["interface"] == interface.name and ifl_data["level"] == interface.level] if not eos_data: self.result.is_failure(f"No neighbor detected for interface {interface.name}") - return + continue if eos_data[0]["count"] != interface.count: self.result.is_failure( - f"Interface {interface.name}:" + f"Interface {interface.name}: " f"expected Level {interface.level}: count {interface.count}, " f"got Level {eos_data[0]['level']}: count {eos_data[0]['count']}" ) @@ -284,7 +300,8 @@ def test(self) -> None: self.result.is_success() if len(command_output["vrfs"]) == 0: - self.result.is_failure("IS-IS is not configured on device") + self.result.is_skipped("IS-IS is not configured on device") + return # Check for p2p interfaces for interface in self.inputs.interfaces: @@ -306,3 +323,409 @@ def test(self) -> None: self.result.is_failure(f"Interface {interface.name} in VRF {interface.vrf} is not running in passive mode") else: self.result.is_failure(f"Interface {interface.name} not found in VRF {interface.vrf}") + + +class VerifyISISSegmentRoutingAdjacencySegments(AntaTest): + """Verifies ISIS Segment Routing Adjacency Segments. + + Verify that all expected Adjacency segments are correctly visible for each interface. + + Expected Results + ---------------- + * Success: The test will pass if all listed interfaces have correct adjacencies. + * Failure: The test will fail if any of the listed interfaces has not expected list of adjacencies. + * Skipped: The test will be skipped if no ISIS SR Adjacency is found. + + Examples + -------- + ```yaml + anta.tests.routing: + isis: + - VerifyISISSegmentRoutingAdjacencySegments: + instances: + - name: CORE-ISIS + vrf: default + segments: + - interface: Ethernet2 + address: 10.0.1.3 + sid_origin: dynamic + + ``` + """ + + name = "VerifyISISSegmentRoutingAdjacencySegments" + description = "Verify expected Adjacency segments are correctly visible for each interface." + categories: ClassVar[list[str]] = ["isis", "segment-routing"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis segment-routing adjacency-segments", ofmt="json")] + + class Input(AntaTest.Input): + """Input model for the VerifyISISSegmentRoutingAdjacencySegments test.""" + + instances: list[IsisInstance] + + class IsisInstance(BaseModel): + """ISIS Instance model definition.""" + + name: str + """ISIS instance name.""" + vrf: str = "default" + """VRF name where ISIS instance is configured.""" + segments: list[Segment] + """List of Adjacency segments configured in this instance.""" + + class Segment(BaseModel): + """Segment model definition.""" + + interface: Interface + """Interface name to check.""" + level: Literal[1, 2] = 2 + """ISIS level configured for interface. Default is 2.""" + sid_origin: Literal["dynamic"] = "dynamic" + """Adjacency type""" + address: IPv4Address + """IP address of remote end of segment.""" + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyISISSegmentRoutingAdjacencySegments.""" + command_output = self.instance_commands[0].json_output + self.result.is_success() + + if len(command_output["vrfs"]) == 0: + self.result.is_skipped("IS-IS is not configured on device") + return + + # initiate defaults + failure_message = [] + skip_vrfs = [] + skip_instances = [] + + # Check if VRFs and instances are present in output. + for instance in self.inputs.instances: + vrf_data = get_value( + dictionary=command_output, + key=f"vrfs.{instance.vrf}", + default=None, + ) + if vrf_data is None: + skip_vrfs.append(instance.vrf) + failure_message.append(f"VRF {instance.vrf} is not configured to run segment routging.") + + elif get_value(dictionary=vrf_data, key=f"isisInstances.{instance.name}", default=None) is None: + skip_instances.append(instance.name) + failure_message.append(f"Instance {instance.name} is not found in vrf {instance.vrf}.") + + # Check Adjacency segments + for instance in self.inputs.instances: + if instance.vrf not in skip_vrfs and instance.name not in skip_instances: + for input_segment in instance.segments: + eos_segment = _get_adjacency_segment_data_by_neighbor( + neighbor=str(input_segment.address), + instance=instance.name, + vrf=instance.vrf, + command_output=command_output, + ) + if eos_segment is None: + failure_message.append(f"Your segment has not been found: {input_segment}.") + + elif ( + eos_segment["localIntf"] != input_segment.interface + or eos_segment["level"] != input_segment.level + or eos_segment["sidOrigin"] != input_segment.sid_origin + ): + failure_message.append(f"Your segment is not correct: Expected: {input_segment} - Found: {eos_segment}.") + if failure_message: + self.result.is_failure("\n".join(failure_message)) + + +class VerifyISISSegmentRoutingDataplane(AntaTest): + """ + Verify dataplane of a list of ISIS-SR instances. + + Expected Results + ---------------- + * Success: The test will pass if all instances have correct dataplane configured + * Failure: The test will fail if one of the instances has incorrect dataplane configured + * Skipped: The test will be skipped if ISIS is not running + + Examples + -------- + ```yaml + anta.tests.routing: + isis: + - VerifyISISSegmentRoutingDataplane: + instances: + - name: CORE-ISIS + vrf: default + dataplane: MPLS + ``` + """ + + name = "VerifyISISSegmentRoutingDataplane" + description = "Verify dataplane of a list of ISIS-SR instances" + categories: ClassVar[list[str]] = ["isis", "segment-routing"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis segment-routing", ofmt="json")] + + class Input(AntaTest.Input): + """Input model for the VerifyISISSegmentRoutingDataplane test.""" + + instances: list[IsisInstance] + + class IsisInstance(BaseModel): + """ISIS Instance model definition.""" + + name: str + """ISIS instance name.""" + vrf: str = "default" + """VRF name where ISIS instance is configured.""" + dataplane: Literal["MPLS", "mpls", "unset"] = "MPLS" + """Configured dataplane for the instance.""" + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyISISSegmentRoutingDataplane.""" + command_output = self.instance_commands[0].json_output + self.result.is_success() + + if len(command_output["vrfs"]) == 0: + self.result.is_skipped("IS-IS-SR is not running on device.") + return + + # initiate defaults + failure_message = [] + skip_vrfs = [] + skip_instances = [] + + # Check if VRFs and instances are present in output. + for instance in self.inputs.instances: + vrf_data = get_value( + dictionary=command_output, + key=f"vrfs.{instance.vrf}", + default=None, + ) + if vrf_data is None: + skip_vrfs.append(instance.vrf) + failure_message.append(f"VRF {instance.vrf} is not configured to run segment routing.") + + elif get_value(dictionary=vrf_data, key=f"isisInstances.{instance.name}", default=None) is None: + skip_instances.append(instance.name) + failure_message.append(f"Instance {instance.name} is not found in vrf {instance.vrf}.") + + # Check Adjacency segments + for instance in self.inputs.instances: + if instance.vrf not in skip_vrfs and instance.name not in skip_instances: + eos_dataplane = get_value(dictionary=command_output, key=f"vrfs.{instance.vrf}.isisInstances.{instance.name}.dataPlane", default=None) + if instance.dataplane.upper() != eos_dataplane: + failure_message.append(f"ISIS instance {instance.name} is not running dataplane {instance.dataplane} ({eos_dataplane})") + + if failure_message: + self.result.is_failure("\n".join(failure_message)) + + +class VerifyISISSegmentRoutingTunnels(AntaTest): + """ + Verify ISIS-SR tunnels computed by device. + + Expected Results + ---------------- + * Success: The test will pass if all listed tunnels are computed on device. + * Failure: The test will fail if one of the listed tunnels is missing. + * Skipped: The test will be skipped if ISIS-SR is not configured. + + Examples + -------- + ```yaml + anta.tests.routing: + isis: + - VerifyISISSegmentRoutingTunnels: + entries: + # Check only endpoint + - endpoint: 1.0.0.122/32 + # Check endpoint and via TI-LFA + - endpoint: 1.0.0.13/32 + vias: + - type: tunnel + tunnel_id: ti-lfa + # Check endpoint and via IP routers + - endpoint: 1.0.0.14/32 + vias: + - type: ip + nexthop: 1.1.1.1 + ``` + """ + + name = "VerifyISISSegmentRoutingTunnels" + description = "Verify ISIS-SR tunnels computed by device" + categories: ClassVar[list[str]] = ["isis", "segment-routing"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show isis segment-routing tunnel", ofmt="json")] + + class Input(AntaTest.Input): + """Input model for the VerifyISISSegmentRoutingTunnels test.""" + + entries: list[Entry] + """List of tunnels to check on device.""" + + class Entry(BaseModel): + """Definition of a tunnel entry.""" + + endpoint: IPv4Network + """Endpoint IP of the tunnel.""" + vias: list[Vias] | None = None + """Optional list of path to reach endpoint.""" + + class Vias(BaseModel): + """Definition of a tunnel path.""" + + nexthop: IPv4Address | None = None + """Nexthop of the tunnel. If None, then it is not tested. Default: None""" + type: Literal["ip", "tunnel"] | None = None + """Type of the tunnel. If None, then it is not tested. Default: None""" + interface: Interface | None = None + """Interface of the tunnel. If None, then it is not tested. Default: None""" + tunnel_id: Literal["TI-LFA", "ti-lfa", "unset"] | None = None + """Computation method of the tunnel. If None, then it is not tested. Default: None""" + + def _eos_entry_lookup(self, search_value: IPv4Network, entries: dict[str, Any], search_key: str = "endpoint") -> dict[str, Any] | None: + return next( + (entry_value for entry_id, entry_value in entries.items() if str(entry_value[search_key]) == str(search_value)), + None, + ) + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyISISSegmentRoutingTunnels. + + This method performs the main test logic for verifying ISIS Segment Routing tunnels. + It checks the command output, initiates defaults, and performs various checks on the tunnels. + + Returns + ------- + None + """ + command_output = self.instance_commands[0].json_output + self.result.is_success() + + # initiate defaults + failure_message = [] + + if len(command_output["entries"]) == 0: + self.result.is_skipped("IS-IS-SR is not running on device.") + return + + for input_entry in self.inputs.entries: + eos_entry = self._eos_entry_lookup(search_value=input_entry.endpoint, entries=command_output["entries"]) + if eos_entry is None: + failure_message.append(f"Tunnel to {input_entry} is not found.") + elif input_entry.vias is not None: + failure_src = [] + for via_input in input_entry.vias: + if not self._check_tunnel_type(via_input, eos_entry): + failure_src.append("incorrect tunnel type") + if not self._check_tunnel_nexthop(via_input, eos_entry): + failure_src.append("incorrect nexthop") + if not self._check_tunnel_interface(via_input, eos_entry): + failure_src.append("incorrect interface") + if not self._check_tunnel_id(via_input, eos_entry): + failure_src.append("incorrect tunnel ID") + + if failure_src: + failure_message.append(f"Tunnel to {input_entry.endpoint!s} is incorrect: {', '.join(failure_src)}") + + if failure_message: + self.result.is_failure("\n".join(failure_message)) + + def _check_tunnel_type(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool: + """ + Check if the tunnel type specified in `via_input` matches any of the tunnel types in `eos_entry`. + + Args: + via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input tunnel type to check. + eos_entry (dict[str, Any]): The EOS entry containing the tunnel types. + + Returns + ------- + bool: True if the tunnel type matches any of the tunnel types in `eos_entry`, False otherwise. + """ + if via_input.type is not None: + return any( + via_input.type + == get_value( + dictionary=eos_via, + key="type", + default="undefined", + ) + for eos_via in eos_entry["vias"] + ) + return True + + def _check_tunnel_nexthop(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool: + """ + Check if the tunnel nexthop matches the given input. + + Args: + via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object. + eos_entry (dict[str, Any]): The EOS entry dictionary. + + Returns + ------- + bool: True if the tunnel nexthop matches, False otherwise. + """ + if via_input.nexthop is not None: + return any( + str(via_input.nexthop) + == get_value( + dictionary=eos_via, + key="nexthop", + default="undefined", + ) + for eos_via in eos_entry["vias"] + ) + return True + + def _check_tunnel_interface(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool: + """ + Check if the tunnel interface exists in the given EOS entry. + + Args: + via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input via object. + eos_entry (dict[str, Any]): The EOS entry dictionary. + + Returns + ------- + bool: True if the tunnel interface exists, False otherwise. + """ + if via_input.interface is not None: + return any( + via_input.interface + == get_value( + dictionary=eos_via, + key="interface", + default="undefined", + ) + for eos_via in eos_entry["vias"] + ) + return True + + def _check_tunnel_id(self, via_input: VerifyISISSegmentRoutingTunnels.Input.Entry.Vias, eos_entry: dict[str, Any]) -> bool: + """ + Check if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias. + + Args: + via_input (VerifyISISSegmentRoutingTunnels.Input.Entry.Vias): The input vias to check. + eos_entry (dict[str, Any]): The EOS entry to compare against. + + Returns + ------- + bool: True if the tunnel ID matches any of the tunnel IDs in the EOS entry's vias, False otherwise. + """ + if via_input.tunnel_id is not None: + return any( + via_input.tunnel_id.upper() + == get_value( + dictionary=eos_via, + key="tunnelId.type", + default="undefined", + ).upper() + for eos_via in eos_entry["vias"] + ) + return True diff --git a/docs/stylesheets/extra.material.css b/docs/stylesheets/extra.material.css index 09d7c8d88..ad58c04d9 100644 --- a/docs/stylesheets/extra.material.css +++ b/docs/stylesheets/extra.material.css @@ -128,12 +128,8 @@ font-weight: bold; } - .md-typeset h4::before { - content: ">> "; - } - .md-typeset h4 { - font-size: 1.1rem; + font-size: 0.9rem; margin: 1em 0; font-weight: 700; letter-spacing: -.01em; diff --git a/docs/usage-inventory-catalog.md b/docs/usage-inventory-catalog.md index e698dca6f..30f458498 100644 --- a/docs/usage-inventory-catalog.md +++ b/docs/usage-inventory-catalog.md @@ -269,7 +269,7 @@ if __name__ == "__main__": # Apply filters to all tests for this device for test in c.tests: test.inputs.filters = AntaTest.Input.Filters(tags=[device]) - catalog.merge(c) + catalog = catalog.merge(c) with open(Path('anta-catalog.yml'), "w") as f: f.write(catalog.dump().yaml()) ``` diff --git a/pyproject.toml b/pyproject.toml index 8ea1ff28d..5f0eb09c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -398,6 +398,10 @@ runtime-evaluated-base-classes = ["pydantic.BaseModel", "anta.models.AntaTest.In "C901", # TODO: test function is too complex, needs a refactor "PLR0911", # TODO: Too many return statements, same as above needs a refactor ] +"anta/tests/routing/isis.py" = [ + "C901", # TODO: test function is too complex, needs a refactor + "PLR0912" # Too many branches (15/12) (too-many-branches), needs a refactor +] "anta/decorators.py" = [ "ANN401", # Ok to use Any type hint in our decorators ] diff --git a/tests/conftest.py b/tests/conftest.py index d6b1b8c99..e31533840 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,7 +44,10 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: It will parametrize test cases based on the `DATA` data structure defined in `tests.units.anta_tests` modules. See `tests/units/anta_tests/README.md` for more information on how to use it. Test IDs are generated using the `build_test_id` function above. + + Checking that only the function "test" is parametrized with data to allow for writing tests for helper functions + in each module. """ - if "tests.units.anta_tests" in metafunc.module.__package__: + if "tests.units.anta_tests" in metafunc.module.__package__ and metafunc.function.__name__ == "test": # This is a unit test for an AntaTest subclass metafunc.parametrize("data", metafunc.module.DATA, ids=build_test_id) diff --git a/tests/units/anta_tests/routing/test_isis.py b/tests/units/anta_tests/routing/test_isis.py index ec4110519..2167ea434 100644 --- a/tests/units/anta_tests/routing/test_isis.py +++ b/tests/units/anta_tests/routing/test_isis.py @@ -3,11 +3,23 @@ # that can be found in the LICENSE file. """Tests for anta.tests.routing.ospf.py.""" +# pylint: disable=too-many-lines + from __future__ import annotations from typing import Any -from anta.tests.routing.isis import VerifyISISInterfaceMode, VerifyISISNeighborCount, VerifyISISNeighborState +import pytest + +from anta.tests.routing.isis import ( + VerifyISISInterfaceMode, + VerifyISISNeighborCount, + VerifyISISNeighborState, + VerifyISISSegmentRoutingAdjacencySegments, + VerifyISISSegmentRoutingDataplane, + VerifyISISSegmentRoutingTunnels, + _get_interface_data, +) from tests.lib.anta import test # noqa: F401; pylint: disable=W0611 DATA: list[dict[str, Any]] = [ @@ -154,6 +166,18 @@ "messages": ["Some neighbors are not in the correct state (UP): [{'vrf': 'default', 'instance': 'CORE-ISIS', 'neighbor': 's1-p01', 'state': 'down'}]."], }, }, + { + "name": "skipped - no neighbor", + "test": VerifyISISNeighborState, + "eos_data": [ + {"vrfs": {"default": {"isisInstances": {"CORE-ISIS": {"neighbors": {}}}}}}, + ], + "inputs": None, + "expected": { + "result": "skipped", + "messages": ["No IS-IS neighbor detected"], + }, + }, { "name": "success only default vrf", "test": VerifyISISNeighborCount, @@ -226,6 +250,108 @@ }, "expected": {"result": "success"}, }, + { + "name": "skipped - no neighbor", + "test": VerifyISISNeighborCount, + "eos_data": [ + {"vrfs": {"default": {"isisInstances": {"CORE-ISIS": {"interfaces": {}}}}}}, + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "level": 2, "count": 1}, + ] + }, + "expected": { + "result": "skipped", + "messages": ["No IS-IS neighbor detected"], + }, + }, + { + "name": "failure - missing interface", + "test": VerifyISISNeighborCount, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Ethernet1": { + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 0, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + } + } + }, + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet2", "level": 2, "count": 1}, + ] + }, + "expected": { + "result": "failure", + "messages": ["No neighbor detected for interface Ethernet2"], + }, + }, + { + "name": "failure - wrong count", + "test": VerifyISISNeighborCount, + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Ethernet1": { + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 3, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + } + } + }, + ], + "inputs": { + "interfaces": [ + {"name": "Ethernet1", "level": 2, "count": 1}, + ] + }, + "expected": { + "result": "failure", + "messages": ["Interface Ethernet1: expected Level 2: count 1, got Level 2: count 3"], + }, + }, { "name": "success VerifyISISInterfaceMode only default vrf", "test": VerifyISISInterfaceMode, @@ -567,4 +693,1226 @@ ], }, }, + { + "name": "skipped VerifyISISInterfaceMode no vrf", + "test": VerifyISISInterfaceMode, + "eos_data": [{"vrfs": {}}], + "inputs": { + "interfaces": [ + {"name": "Loopback0", "mode": "passive"}, + {"name": "Ethernet2", "mode": "passive"}, + {"name": "Ethernet1", "mode": "point-to-point", "vrf": "default"}, + ] + }, + "expected": {"result": "skipped", "messages": ["IS-IS is not configured on device"]}, + }, + { + "name": "Skipped of VerifyISISSegmentRoutingAdjacencySegments no VRF.", + "test": VerifyISISSegmentRoutingAdjacencySegments, + "eos_data": [{"vrfs": {}}], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "default", + "segments": [ + { + "interface": "Ethernet2", + "address": "10.0.1.3", + "sid_origin": "dynamic", + } + ], + } + ] + }, + "expected": {"result": "skipped", "messages": ["IS-IS is not configured on device"]}, + }, + { + "test": VerifyISISSegmentRoutingAdjacencySegments, + "name": "Success of VerifyISISSegmentRoutingAdjacencySegments in default VRF.", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + "adjSidAllocationMode": "SrOnly", + "adjSidPoolBase": 116384, + "adjSidPoolSize": 16384, + "adjacencySegments": [ + { + "ipAddress": "10.0.1.3", + "localIntf": "Ethernet2", + "sid": 116384, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + { + "ipAddress": "10.0.1.1", + "localIntf": "Ethernet1", + "sid": 116385, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + ], + "receivedGlobalAdjacencySegments": [], + "misconfiguredAdjacencySegments": [], + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "default", + "segments": [ + { + "interface": "Ethernet2", + "address": "10.0.1.3", + "sid_origin": "dynamic", + } + ], + } + ] + }, + "expected": { + "result": "success", + "messages": [], + }, + }, + { + "test": VerifyISISSegmentRoutingAdjacencySegments, + "name": "Failure of VerifyISISSegmentRoutingAdjacencySegments in default VRF for incorrect segment definition.", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + "adjSidAllocationMode": "SrOnly", + "adjSidPoolBase": 116384, + "adjSidPoolSize": 16384, + "adjacencySegments": [ + { + "ipAddress": "10.0.1.3", + "localIntf": "Ethernet2", + "sid": 116384, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + { + "ipAddress": "10.0.1.1", + "localIntf": "Ethernet1", + "sid": 116385, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + ], + "receivedGlobalAdjacencySegments": [], + "misconfiguredAdjacencySegments": [], + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "default", + "segments": [ + { + "interface": "Ethernet2", + "address": "10.0.1.3", + "sid_origin": "dynamic", + }, + { + "interface": "Ethernet3", + "address": "10.0.1.2", + "sid_origin": "dynamic", + }, + ], + } + ] + }, + "expected": { + "result": "failure", + "messages": ["Your segment has not been found: interface='Ethernet3' level=2 sid_origin='dynamic' address=IPv4Address('10.0.1.2')."], + }, + }, + { + "test": VerifyISISSegmentRoutingAdjacencySegments, + "name": "Failure of VerifyISISSegmentRoutingAdjacencySegments with incorrect VRF.", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + "adjSidAllocationMode": "SrOnly", + "adjSidPoolBase": 116384, + "adjSidPoolSize": 16384, + "adjacencySegments": [ + { + "ipAddress": "10.0.1.3", + "localIntf": "Ethernet2", + "sid": 116384, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + { + "ipAddress": "10.0.1.1", + "localIntf": "Ethernet1", + "sid": 116385, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + ], + "receivedGlobalAdjacencySegments": [], + "misconfiguredAdjacencySegments": [], + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "custom", + "segments": [ + { + "interface": "Ethernet2", + "address": "10.0.1.3", + "sid_origin": "dynamic", + }, + { + "interface": "Ethernet3", + "address": "10.0.1.2", + "sid_origin": "dynamic", + }, + ], + } + ] + }, + "expected": { + "result": "failure", + "messages": ["VRF custom is not configured to run segment routging."], + }, + }, + { + "test": VerifyISISSegmentRoutingAdjacencySegments, + "name": "Failure of VerifyISISSegmentRoutingAdjacencySegments with incorrect Instance.", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + "adjSidAllocationMode": "SrOnly", + "adjSidPoolBase": 116384, + "adjSidPoolSize": 16384, + "adjacencySegments": [ + { + "ipAddress": "10.0.1.3", + "localIntf": "Ethernet2", + "sid": 116384, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + { + "ipAddress": "10.0.1.1", + "localIntf": "Ethernet1", + "sid": 116385, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + ], + "receivedGlobalAdjacencySegments": [], + "misconfiguredAdjacencySegments": [], + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS2", + "vrf": "default", + "segments": [ + { + "interface": "Ethernet2", + "address": "10.0.1.3", + "sid_origin": "dynamic", + }, + { + "interface": "Ethernet3", + "address": "10.0.1.2", + "sid_origin": "dynamic", + }, + ], + } + ] + }, + "expected": { + "result": "failure", + "messages": ["Instance CORE-ISIS2 is not found in vrf default."], + }, + }, + { + "test": VerifyISISSegmentRoutingAdjacencySegments, + "name": "Failure of VerifyISISSegmentRoutingAdjacencySegments with incorrect segment info.", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + "adjSidAllocationMode": "SrOnly", + "adjSidPoolBase": 116384, + "adjSidPoolSize": 16384, + "adjacencySegments": [ + { + "ipAddress": "10.0.1.3", + "localIntf": "Ethernet2", + "sid": 116384, + "lan": False, + "sidOrigin": "dynamic", + "protection": "unprotected", + "flags": { + "b": False, + "v": True, + "l": True, + "f": False, + "s": False, + }, + "level": 2, + }, + ], + "receivedGlobalAdjacencySegments": [], + "misconfiguredAdjacencySegments": [], + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "default", + "segments": [ + { + "interface": "Ethernet2", + "address": "10.0.1.3", + "sid_origin": "dynamic", + "level": 1, # Wrong level + }, + ], + } + ] + }, + "expected": { + "result": "failure", + "messages": [ + ( + "Your segment is not correct: Expected: interface='Ethernet2' level=1 sid_origin='dynamic' address=IPv4Address('10.0.1.3') - " + "Found: {'ipAddress': '10.0.1.3', 'localIntf': 'Ethernet2', 'sid': 116384, 'lan': False, 'sidOrigin': 'dynamic', 'protection': " + "'unprotected', 'flags': {'b': False, 'v': True, 'l': True, 'f': False, 's': False}, 'level': 2}." + ) + ], + }, + }, + { + "test": VerifyISISSegmentRoutingDataplane, + "name": "Check VerifyISISSegmentRoutingDataplane is running successfully", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "default", + "dataplane": "MPLS", + }, + ] + }, + "expected": { + "result": "success", + "messages": [], + }, + }, + { + "test": VerifyISISSegmentRoutingDataplane, + "name": "Check VerifyISISSegmentRoutingDataplane is failing with incorrect dataplane", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "default", + "dataplane": "unset", + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["ISIS instance CORE-ISIS is not running dataplane unset (MPLS)"], + }, + }, + { + "test": VerifyISISSegmentRoutingDataplane, + "name": "Check VerifyISISSegmentRoutingDataplane is failing for unknown instance", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS2", + "vrf": "default", + "dataplane": "unset", + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["Instance CORE-ISIS2 is not found in vrf default."], + }, + }, + { + "test": VerifyISISSegmentRoutingDataplane, + "name": "Check VerifyISISSegmentRoutingDataplane is failing for unknown VRF", + "eos_data": [ + { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "dataPlane": "MPLS", + "routerId": "1.0.0.11", + "systemId": "0168.0000.0011", + "hostname": "s1-pe01", + } + } + } + } + } + ], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "wrong_vrf", + "dataplane": "unset", + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["VRF wrong_vrf is not configured to run segment routing."], + }, + }, + { + "test": VerifyISISSegmentRoutingDataplane, + "name": "Check VerifyISISSegmentRoutingDataplane is skipped", + "eos_data": [{"vrfs": {}}], + "inputs": { + "instances": [ + { + "name": "CORE-ISIS", + "vrf": "wrong_vrf", + "dataplane": "unset", + }, + ] + }, + "expected": { + "result": "skipped", + "messages": ["IS-IS-SR is not running on device"], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "runs successfully", + "eos_data": [ + { + "entries": { + "3": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "31": { + "endpoint": "1.0.0.13/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "32": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "2": { + "endpoint": "1.0.0.111/32", + "vias": [ + { + "type": "tunnel", + "tunnelId": {"type": "TI-LFA", "index": 4}, + "labels": ["3"], + } + ], + }, + } + } + ], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + {"endpoint": "1.0.0.13/32", "vias": [{"type": "ip"}]}, + { + "endpoint": "1.0.0.111/32", + "vias": [{"type": "tunnel", "tunnel_id": "ti-lfa"}], + }, + { + "endpoint": "1.0.0.122/32", + "vias": [ + {"interface": "Ethernet1", "nexthop": "10.0.1.1"}, # Testing empty type + {"type": "ip", "interface": "Ethernet2", "nexthop": "10.0.1.3"}, + ], + }, + ] + }, + "expected": { + "result": "success", + "messages": [], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "is skipped if not entry founf in EOS", + "eos_data": [{"entries": {}}], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + ] + }, + "expected": { + "result": "skipped", + "messages": ["IS-IS-SR is not running on device."], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "runs successfully", + "eos_data": [ + { + "entries": { + "2": { + "endpoint": "1.0.0.111/32", + "vias": [ + { + "type": "tunnel", + "tunnelId": {"type": "TI-LFA", "index": 4}, + "labels": ["3"], + } + ], + }, + } + } + ], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + ] + }, + "expected": { + "result": "failure", + "messages": ["Tunnel to endpoint=IPv4Network('1.0.0.122/32') vias=None is not found."], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "fails with incorrect tunnel type", + "eos_data": [ + { + "entries": { + "3": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "31": { + "endpoint": "1.0.0.13/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "32": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "2": { + "endpoint": "1.0.0.111/32", + "vias": [ + { + "type": "tunnel", + "tunnelId": {"type": "TI-LFA", "index": 4}, + "labels": ["3"], + } + ], + }, + } + } + ], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + {"endpoint": "1.0.0.13/32", "vias": [{"type": "tunnel"}]}, + ] + }, + "expected": { + "result": "failure", + "messages": ["Tunnel to 1.0.0.13/32 is incorrect: incorrect tunnel type"], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "fails with incorrect nexthop", + "eos_data": [ + { + "entries": { + "3": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "31": { + "endpoint": "1.0.0.13/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "32": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "2": { + "endpoint": "1.0.0.111/32", + "vias": [ + { + "type": "tunnel", + "tunnelId": {"type": "TI-LFA", "index": 4}, + "labels": ["3"], + } + ], + }, + } + } + ], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + {"endpoint": "1.0.0.13/32", "vias": [{"type": "ip"}]}, + { + "endpoint": "1.0.0.122/32", + "vias": [ + {"type": "ip", "interface": "Ethernet1", "nexthop": "10.0.1.2"}, + {"type": "ip", "interface": "Ethernet2", "nexthop": "10.0.1.3"}, + ], + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["Tunnel to 1.0.0.122/32 is incorrect: incorrect nexthop"], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "fails with incorrect nexthop", + "eos_data": [ + { + "entries": { + "3": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "31": { + "endpoint": "1.0.0.13/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "32": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "2": { + "endpoint": "1.0.0.111/32", + "vias": [ + { + "type": "tunnel", + "tunnelId": {"type": "TI-LFA", "index": 4}, + "labels": ["3"], + } + ], + }, + } + } + ], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + {"endpoint": "1.0.0.13/32", "vias": [{"type": "ip"}]}, + { + "endpoint": "1.0.0.122/32", + "vias": [ + {"type": "ip", "interface": "Ethernet4", "nexthop": "10.0.1.1"}, + {"type": "ip", "interface": "Ethernet2", "nexthop": "10.0.1.3"}, + ], + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["Tunnel to 1.0.0.122/32 is incorrect: incorrect interface"], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "fails with incorrect interface", + "eos_data": [ + { + "entries": { + "3": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "31": { + "endpoint": "1.0.0.13/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "32": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "2": { + "endpoint": "1.0.0.111/32", + "vias": [ + { + "type": "tunnel", + "tunnelId": {"type": "TI-LFA", "index": 4}, + "labels": ["3"], + } + ], + }, + } + } + ], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + {"endpoint": "1.0.0.13/32", "vias": [{"type": "ip"}]}, + { + "endpoint": "1.0.0.122/32", + "vias": [ + {"type": "ip", "interface": "Ethernet1", "nexthop": "10.0.1.2"}, + {"type": "ip", "interface": "Ethernet2", "nexthop": "10.0.1.3"}, + ], + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["Tunnel to 1.0.0.122/32 is incorrect: incorrect nexthop"], + }, + }, + { + "test": VerifyISISSegmentRoutingTunnels, + "name": "fails with incorrect tunnel ID type", + "eos_data": [ + { + "entries": { + "3": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "31": { + "endpoint": "1.0.0.13/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "32": { + "endpoint": "1.0.0.122/32", + "vias": [ + { + "type": "ip", + "nexthop": "10.0.1.1", + "interface": "Ethernet1", + "labels": ["900021"], + }, + { + "type": "ip", + "nexthop": "10.0.1.3", + "interface": "Ethernet2", + "labels": ["900021"], + }, + ], + }, + "2": { + "endpoint": "1.0.0.111/32", + "vias": [ + { + "type": "tunnel", + "tunnelId": {"type": "TI-LFA", "index": 4}, + "labels": ["3"], + } + ], + }, + } + } + ], + "inputs": { + "entries": [ + {"endpoint": "1.0.0.122/32"}, + {"endpoint": "1.0.0.13/32", "vias": [{"type": "ip"}]}, + { + "endpoint": "1.0.0.111/32", + "vias": [ + {"type": "tunnel", "tunnel_id": "unset"}, + ], + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["Tunnel to 1.0.0.111/32 is incorrect: incorrect tunnel ID"], + }, + }, ] + + +COMMAND_OUTPUT = { + "vrfs": { + "default": { + "isisInstances": { + "CORE-ISIS": { + "interfaces": { + "Loopback0": { + "enabled": True, + "intfLevels": { + "2": { + "ipv4Metric": 10, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "areaProxyBoundary": False, + }, + "Ethernet1": { + "intfLevels": { + "2": { + "ipv4Metric": 10, + "numAdjacencies": 1, + "linkId": "84", + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": False, + "v4Protection": "link", + "v6Protection": "disabled", + } + }, + "interfaceSpeed": 1000, + "areaProxyBoundary": False, + }, + } + } + } + }, + "EMPTY": {"isisInstances": {}}, + "NO_INTERFACES": {"isisInstances": {"CORE-ISIS": {}}}, + } +} +EXPECTED_LOOPBACK_0_OUTPUT = { + "enabled": True, + "intfLevels": { + "2": { + "ipv4Metric": 10, + "sharedSecretProfile": "", + "isisAdjacencies": [], + "passive": True, + "v4Protection": "disabled", + "v6Protection": "disabled", + } + }, + "areaProxyBoundary": False, +} + + +@pytest.mark.parametrize( + ("interface", "vrf", "expected_value"), + [ + pytest.param("Loopback0", "WRONG_VRF", None, id="VRF_not_found"), + pytest.param("Loopback0", "EMPTY", None, id="VRF_no_ISIS_instances"), + pytest.param("Loopback0", "NO_INTERFACES", None, id="ISIS_instance_no_interfaces"), + pytest.param("Loopback42", "default", None, id="interface_not_found"), + pytest.param("Loopback0", "default", EXPECTED_LOOPBACK_0_OUTPUT, id="interface_found"), + ], +) +def test__get_interface_data(interface: str, vrf: str, expected_value: dict[str, Any] | None) -> None: + """Test anta.tests.routing.isis._get_interface_data.""" + assert _get_interface_data(interface, vrf, COMMAND_OUTPUT) == expected_value From dbdd82f9679f0806c80601e2e15f2a3aaf534741 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 22:50:01 +0200 Subject: [PATCH 3/9] ci: Test sonarcloud (#694) --- .sonarcloud.properties | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .sonarcloud.properties diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 000000000..3414f6bc4 --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1,16 @@ +# Path to sources +sonar.sources=. +# Ignoring examples and tests +sonar.exclusions=examples/,tests/ +#sonar.inclusions= + +# Path to tests +sonar.tests=tests/ +#sonar.test.exclusions= +#sonar.test.inclusions= + +# Source encoding +#sonar.sourceEncoding=UTF-8 + +# Exclusions for copy-paste detection +#sonar.cpd.exclusions=, From d3f6b9f1bdf3459c5c9acc8bf66fdaefc2ef0a4f Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 23:02:49 +0200 Subject: [PATCH 4/9] Update .sonarcloud.properties --- .sonarcloud.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sonarcloud.properties b/.sonarcloud.properties index 3414f6bc4..96afd091b 100644 --- a/.sonarcloud.properties +++ b/.sonarcloud.properties @@ -1,7 +1,7 @@ # Path to sources sonar.sources=. # Ignoring examples and tests -sonar.exclusions=examples/,tests/ +sonar.exclusions=examples/ #sonar.inclusions= # Path to tests From a4d8e01e02b65be13c1f6fdc70094c5e50483153 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 23:12:01 +0200 Subject: [PATCH 5/9] Update .sonarcloud.properties --- .sonarcloud.properties | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.sonarcloud.properties b/.sonarcloud.properties index 96afd091b..d69f7c133 100644 --- a/.sonarcloud.properties +++ b/.sonarcloud.properties @@ -1,7 +1,6 @@ # Path to sources -sonar.sources=. -# Ignoring examples and tests -sonar.exclusions=examples/ +sonar.sources=anta/ +#sonar.exclusions=examples/ #sonar.inclusions= # Path to tests @@ -12,5 +11,8 @@ sonar.tests=tests/ # Source encoding #sonar.sourceEncoding=UTF-8 +# Python version (for python projects only) +sonar.python.version=3.9,3.10,3.11,3.12 + # Exclusions for copy-paste detection #sonar.cpd.exclusions=, From 67a1e49dd18a1bd679dbf8be5389803d1b39fc64 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 23:14:47 +0200 Subject: [PATCH 6/9] Update .sonarcloud.properties --- .sonarcloud.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.sonarcloud.properties b/.sonarcloud.properties index d69f7c133..a62654f39 100644 --- a/.sonarcloud.properties +++ b/.sonarcloud.properties @@ -1,6 +1,6 @@ # Path to sources -sonar.sources=anta/ -#sonar.exclusions=examples/ +#sonar.sources=. +sonar.exclusions=examples/ #sonar.inclusions= # Path to tests From b594ed7cdcb68521bd3dae679c442363c78da123 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 23:16:43 +0200 Subject: [PATCH 7/9] Update .sonarcloud.properties --- .sonarcloud.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.sonarcloud.properties b/.sonarcloud.properties index a62654f39..4cd2ea5e9 100644 --- a/.sonarcloud.properties +++ b/.sonarcloud.properties @@ -1,6 +1,6 @@ # Path to sources -#sonar.sources=. -sonar.exclusions=examples/ +sonar.sources=anta/,asynceapi/ +#sonar.exclusions= #sonar.inclusions= # Path to tests From eb542afc62ca8d3e2b19e1694addef179e59dbe5 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 23:33:28 +0200 Subject: [PATCH 8/9] ci: Allow-list for secret-scanner (#695) --- .arista/secret_allowlist.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .arista/secret_allowlist.yaml diff --git a/.arista/secret_allowlist.yaml b/.arista/secret_allowlist.yaml new file mode 100644 index 000000000..fea5054de --- /dev/null +++ b/.arista/secret_allowlist.yaml @@ -0,0 +1,10 @@ +# Arista Secret Scanner allow list + +version: v1.0 +allowed_secrets: +- secret_pattern: "https://ansible:ansible@192.168.0.2" + category: FALSE_POSITIVE + reason: Used as example in documentation +- secret_pattern: "https://ansible:ansible@192.168.0.17" + category: FALSE_POSITIVE + reason: Used as example in documentation From 35a4bdd2d75d6ee39b89ec03c1c22269b3a579f9 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Wed, 5 Jun 2024 23:46:43 +0200 Subject: [PATCH 9/9] ci: Opt-in for Secret-scanner workflow (#696) --- .github/workflows/secret-scanner.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/secret-scanner.yml diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml new file mode 100644 index 000000000..3a35e7e03 --- /dev/null +++ b/.github/workflows/secret-scanner.yml @@ -0,0 +1,15 @@ +# Secret-scanner workflow from Arista Networks. +on: + pull_request: + types: [synchronize] + push: + branches: + - main +name: Secret Scanner (go/secret-scanner) +jobs: + scan_secret: + name: Scan incoming changes + runs-on: ubuntu-latest + steps: + - name: Run scanner + uses: aristanetworks/secret-scanner-service@main