diff --git a/anta/tests/routing/ospf.py b/anta/tests/routing/ospf.py index 38ba26958..d8ebbcd42 100644 --- a/anta/tests/routing/ospf.py +++ b/anta/tests/routing/ospf.py @@ -60,6 +60,31 @@ def _get_not_full_ospf_neighbors(ospf_neighbor_json: dict[str, Any]) -> list[dic ] +def _get_ospf_max_lsa_info(ospf_process_json: dict[str, Any]) -> list[dict[str, Any]]: + """Return information about OSPF instances and their LSAs. + + Args: + ---- + ospf_process_json (dict[str, Any]): OSPF process information in JSON format. + + Returns + ------- + list[dict[str, Any]]: A list of dictionaries containing OSPF LSAs information. + + """ + return [ + { + "vrf": vrf, + "instance": instance, + "maxLsa": instance_data.get("maxLsaInformation", {}).get("maxLsa"), + "maxLsaThreshold": instance_data.get("maxLsaInformation", {}).get("maxLsaThreshold"), + "numLsa": instance_data.get("lsaInformation", {}).get("numLsa"), + } + for vrf, vrf_data in ospf_process_json.get("vrfs", {}).items() + for instance, instance_data in vrf_data.get("instList", {}).items() + ] + + class VerifyOSPFNeighborState(AntaTest): """Verifies all OSPF neighbors are in FULL state. @@ -139,3 +164,44 @@ def test(self) -> None: not_full_neighbors = _get_not_full_ospf_neighbors(command_output) if not_full_neighbors: self.result.is_failure(f"Some neighbors are not correctly configured: {not_full_neighbors}.") + + +class VerifyOSPFMaxLSA(AntaTest): + """Verifies LSAs present in the OSPF link state database did not cross the maximum LSA Threshold. + + Expected Results + ---------------- + * Success: The test will pass if all OSPF instances did not cross the maximum LSA Threshold. + * Failure: The test will fail if some OSPF instances crossed the maximum LSA Threshold. + * Skipped: The test will be skipped if no OSPF instance is found. + + Examples + -------- + ```yaml + anta.tests.routing: + ospf: + - VerifyOSPFMaxLSA: + ``` + """ + + name = "VerifyOSPFMaxLSA" + description = "Verifies all OSPF instances did not cross the maximum LSA threshold." + categories: ClassVar[list[str]] = ["ospf"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show ip ospf")] + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyOSPFMaxLSA.""" + command_output = self.instance_commands[0].json_output + ospf_instance_info = _get_ospf_max_lsa_info(command_output) + if not ospf_instance_info: + self.result.is_skipped("No OSPF instance found.") + return + all_instances_within_threshold = all(instance["numLsa"] <= instance["maxLsa"] * (instance["maxLsaThreshold"] / 100) for instance in ospf_instance_info) + if all_instances_within_threshold: + self.result.is_success() + else: + exceeded_instances = [ + instance["instance"] for instance in ospf_instance_info if instance["numLsa"] > instance["maxLsa"] * (instance["maxLsaThreshold"] / 100) + ] + self.result.is_failure(f"OSPF Instances {exceeded_instances} crossed the maximum LSA threshold.") diff --git a/examples/tests.yaml b/examples/tests.yaml index 72606b112..b32b48bb6 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -491,3 +491,4 @@ anta.tests.routing: - VerifyOSPFNeighborState: - VerifyOSPFNeighborCount: number: 3 + - VerifyOSPFMaxLSA: diff --git a/tests/units/anta_tests/routing/test_ospf.py b/tests/units/anta_tests/routing/test_ospf.py index 3bd213f84..81d8010ae 100644 --- a/tests/units/anta_tests/routing/test_ospf.py +++ b/tests/units/anta_tests/routing/test_ospf.py @@ -7,7 +7,7 @@ from typing import Any -from anta.tests.routing.ospf import VerifyOSPFNeighborCount, VerifyOSPFNeighborState +from anta.tests.routing.ospf import VerifyOSPFMaxLSA, VerifyOSPFNeighborCount, VerifyOSPFNeighborState from tests.lib.anta import test # noqa: F401; pylint: disable=W0611 DATA: list[dict[str, Any]] = [ @@ -294,4 +294,118 @@ "inputs": {"number": 3}, "expected": {"result": "skipped", "messages": ["no OSPF neighbor found"]}, }, + { + "name": "success", + "test": VerifyOSPFMaxLSA, + "eos_data": [ + { + "vrfs": { + "default": { + "instList": { + "1": { + "instanceId": 1, + "maxLsaInformation": { + "maxLsa": 12000, + "maxLsaThreshold": 75, + }, + "routerId": "1.1.1.1", + "lsaInformation": { + "lsaArrivalInterval": 1000, + "lsaStartInterval": 1000, + "lsaHoldInterval": 5000, + "lsaMaxWaitInterval": 5000, + "numLsa": 9, + }, + }, + }, + }, + "TEST": { + "instList": { + "10": { + "instanceId": 10, + "maxLsaInformation": { + "maxLsa": 1000, + "maxLsaThreshold": 75, + }, + "routerId": "20.20.20.20", + "lsaInformation": { + "lsaArrivalInterval": 1000, + "lsaStartInterval": 1000, + "lsaHoldInterval": 5000, + "lsaMaxWaitInterval": 5000, + "numLsa": 5, + }, + }, + }, + }, + }, + }, + ], + "inputs": None, + "expected": {"result": "success"}, + }, + { + "name": "failure", + "test": VerifyOSPFMaxLSA, + "eos_data": [ + { + "vrfs": { + "default": { + "instList": { + "1": { + "instanceId": 1, + "maxLsaInformation": { + "maxLsa": 12000, + "maxLsaThreshold": 75, + }, + "routerId": "1.1.1.1", + "lsaInformation": { + "lsaArrivalInterval": 1000, + "lsaStartInterval": 1000, + "lsaHoldInterval": 5000, + "lsaMaxWaitInterval": 5000, + "numLsa": 11500, + }, + }, + }, + }, + "TEST": { + "instList": { + "10": { + "instanceId": 10, + "maxLsaInformation": { + "maxLsa": 1000, + "maxLsaThreshold": 75, + }, + "routerId": "20.20.20.20", + "lsaInformation": { + "lsaArrivalInterval": 1000, + "lsaStartInterval": 1000, + "lsaHoldInterval": 5000, + "lsaMaxWaitInterval": 5000, + "numLsa": 1500, + }, + }, + }, + }, + }, + }, + ], + "inputs": None, + "expected": { + "result": "failure", + "messages": ["OSPF Instances ['1', '10'] crossed the maximum LSA threshold."], + }, + }, + { + "name": "skipped", + "test": VerifyOSPFMaxLSA, + "eos_data": [ + { + "vrfs": {}, + }, + ], + "inputs": None, + "expected": {"result": "skipped", "messages": ["No OSPF instance found."]}, + }, ]