From 2586243e90a66ebf7a3d35f56bf233463a8f8041 Mon Sep 17 00:00:00 2001 From: Mahesh Kumar Date: Wed, 19 Jun 2024 00:00:28 -0700 Subject: [PATCH] issue-710: Added the validation for hardware flow tracker --- anta/tests/flow_tracking.py | 186 +++++++++ docs/api/tests.flow_tracking.md | 20 + docs/api/tests.md | 1 + examples/tests.yaml | 15 + mkdocs.yml | 1 + tests/units/anta_tests/test_flow_tracking.py | 391 +++++++++++++++++++ 6 files changed, 614 insertions(+) create mode 100644 anta/tests/flow_tracking.py create mode 100644 docs/api/tests.flow_tracking.md create mode 100644 tests/units/anta_tests/test_flow_tracking.py diff --git a/anta/tests/flow_tracking.py b/anta/tests/flow_tracking.py new file mode 100644 index 000000000..bab8860e6 --- /dev/null +++ b/anta/tests/flow_tracking.py @@ -0,0 +1,186 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Module related to the flow tracking tests.""" + +# Mypy does not understand AntaTest.Input typing +# mypy: disable-error-code=attr-defined +from __future__ import annotations + +from typing import ClassVar + +from pydantic import BaseModel + +from anta.models import AntaCommand, AntaTemplate, AntaTest +from anta.tools import get_failed_logs + + +def validate_record_export(record_export: dict[str, str], tracker_info: dict[str, str]) -> str: + """ + Validate the record export configuration against the tracker info. + + Args: + record_export (dict): The expected record export configuration. + tracker_info (dict): The actual tracker info from the command output. + + Returns + ------- + str : A failure message if the record export configuration does not match, otherwise blank string. + """ + failed_log = "" + actual_export = {"inactive timeout": tracker_info.get("inactiveTimeout"), "interval": tracker_info.get("activeInterval")} + expected_export = {"inactive timeout": record_export.get("on_inactive_timeout"), "interval": record_export.get("on_interval")} + if actual_export != expected_export: + failed_log = get_failed_logs(expected_export, actual_export) + return failed_log + + +def validate_exporters(exporters: list[dict[str, str]], tracker_info: dict[str, str]) -> str: + """ + Validate the exporter configurations against the tracker info. + + Args: + exporters (list[dict]): The list of expected exporter configurations. + tracker_info (dict): The actual tracker info from the command output. + + Returns + ------- + str: Failure message if any exporter configuration does not match. + """ + failed_log = "" + for exporter in exporters: + exporter_name = exporter["name"] + actual_exporter_info = tracker_info["exporters"].get(exporter_name) + if not actual_exporter_info: + failed_log += f"\nExporter `{exporter_name}` is not configured." + continue + + expected_exporter_data = {"local interface": exporter["local_interface"], "template interval": exporter["template_interval"]} + actual_exporter_data = {"local interface": actual_exporter_info["localIntf"], "template interval": actual_exporter_info["templateInterval"]} + + if expected_exporter_data != actual_exporter_data: + failed_msg = get_failed_logs(expected_exporter_data, actual_exporter_data) + failed_log += f"\nExporter `{exporter_name}`: {failed_msg}" + return failed_log + + +class VerifyHardwareFlowTrackerStatus(AntaTest): + """ + Verifies if hardware flow tracking is running and an input tracker is active. + + This test optionally verifies the tracker interval/timeout and exporter configuration. + + Expected Results + ---------------- + * Success: The test will pass if hardware flow tracking is running and an input tracker is active. + * Failure: The test will fail if hardware flow tracking is not running, an input tracker is not active, + or the tracker interval/timeout and exporter configuration does not match the expected values. + + Examples + -------- + ```yaml + anta.tests.flow_tracking: + - VerifyFlowTrackingHardware: + trackers: + - name: FLOW-TRACKER + record_export: + on_inactive_timeout: 70000 + on_interval: 300000 + exporters: + - name: CV-TELEMETRY + local_interface: Loopback0 + template_interval: 3600000 + ``` + """ + + name = "VerifyHardwareFlowTrackerStatus" + description = ( + "Verifies if hardware flow tracking is running and an input tracker is active. Optionally verifies the tracker interval/timeout and exporter configuration." + ) + categories: ClassVar[list[str]] = ["flow tracking"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show flow tracking hardware tracker {name}", revision=1)] + + class Input(AntaTest.Input): + """Input model for the VerifyHardwareFlowTrackerStatus test.""" + + trackers: list[FlowTracker] + """List of flow trackers to verify.""" + + class FlowTracker(BaseModel): + """Detail of a flow tracker.""" + + name: str + """Name of the flow tracker.""" + + record_export: RecordExport | None = None + """Record export configuration for the flow tracker.""" + + exporters: list[Exporter] | None = None + """List of exporters for the flow tracker.""" + + class RecordExport(BaseModel): + """Record export configuration.""" + + on_inactive_timeout: int + """Timeout in milliseconds for exporting records when inactive.""" + + on_interval: int + """Interval in milliseconds for exporting records.""" + + class Exporter(BaseModel): + """Detail of an exporter.""" + + name: str + """Name of the exporter.""" + + local_interface: str + """Local interface used by the exporter.""" + + template_interval: int + """Template interval in milliseconds for the exporter.""" + + def render(self, template: AntaTemplate) -> list[AntaCommand]: + """Render the template for each hardware tracker.""" + return [template.render(name=tracker.name) for tracker in self.inputs.trackers] + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifyHardwareFlowTrackerStatus.""" + self.result.is_success() + for command, tracker_input in zip(self.instance_commands, self.inputs.trackers): + hardware_tracker_name = command.params.name + record_export = tracker_input.record_export.model_dump() if tracker_input.record_export else None + exporters = [exporter.model_dump() for exporter in tracker_input.exporters] if tracker_input.exporters else None + command_output = command.json_output + + # Check if hardware flow tracking is configured + if not command_output.get("running"): + self.result.is_failure("Hardware flow tracking is not running.") + return + + # Check if the input hardware tracker is configured + tracker_info = command_output["trackers"].get(hardware_tracker_name) + if not tracker_info: + self.result.is_failure(f"Hardware flow tracker `{hardware_tracker_name}` is not configured.") + continue + + # Check if the input hardware tracker is active + if not tracker_info.get("active"): + self.result.is_failure(f"Hardware flow tracker `{hardware_tracker_name}` is not active.") + continue + + # Check the input hardware tracker timeouts + failure_msg = "" + if record_export: + record_export_failure = validate_record_export(record_export, tracker_info) + if record_export_failure: + failure_msg += record_export_failure + + # Check the input hardware tracker exporters' configuration + if exporters: + exporters_failure = validate_exporters(exporters, tracker_info) + if exporters_failure: + failure_msg += exporters_failure + + if failure_msg: + self.result.is_failure(f"{hardware_tracker_name}: {failure_msg}\n") diff --git a/docs/api/tests.flow_tracking.md b/docs/api/tests.flow_tracking.md new file mode 100644 index 000000000..0df0b1dc8 --- /dev/null +++ b/docs/api/tests.flow_tracking.md @@ -0,0 +1,20 @@ +--- +anta_title: ANTA catalog for flow tracking tests +--- + + +::: anta.tests.flow_tracking + options: + show_root_heading: false + show_root_toc_entry: false + show_bases: false + merge_init_into_class: false + anta_hide_test_module_description: true + show_labels: true + filters: + - "!test" + - "!render" diff --git a/docs/api/tests.md b/docs/api/tests.md index b11f0badb..2775a01ee 100644 --- a/docs/api/tests.md +++ b/docs/api/tests.md @@ -18,6 +18,7 @@ Here are the tests that we currently provide: - [Configuration](tests.configuration.md) - [Connectivity](tests.connectivity.md) - [Field Notice](tests.field_notices.md) +- [Flow Tracking](tests.flow_tracking.md) - [GreenT](tests.greent.md) - [Hardware](tests.hardware.md) - [Interfaces](tests.interfaces.md) diff --git a/examples/tests.yaml b/examples/tests.yaml index cb3d19b5f..0cf0e883d 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -110,6 +110,21 @@ anta.tests.field_notices: - VerifyFieldNotice44Resolution: - VerifyFieldNotice72Resolution: +anta.tests.flow_tracking: + - VerifyHardwareFlowTrackerStatus: + trackers: + - name: FLOW-TRACKER + record_export: + on_inactive_timeout: 700000 + on_interval: 3000000 + exporters: + - name: CV-TELEMETRY + local_interface: Loopback11 + template_interval: 3600 + - name: CVP-TELEMETRY + local_interface: Loopback01 + template_interval: 36000000 + anta.tests.greent: - VerifyGreenT: - VerifyGreenTCounters: diff --git a/mkdocs.yml b/mkdocs.yml index db08e737d..291fb2bda 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -193,6 +193,7 @@ nav: - Configuration: api/tests.configuration.md - Connectivity: api/tests.connectivity.md - Field Notices: api/tests.field_notices.md + - Flow Tracking: api/test.flow_tracking.md - GreenT: api/tests.greent.md - Hardware: api/tests.hardware.md - Interfaces: api/tests.interfaces.md diff --git a/tests/units/anta_tests/test_flow_tracking.py b/tests/units/anta_tests/test_flow_tracking.py new file mode 100644 index 000000000..21b47222a --- /dev/null +++ b/tests/units/anta_tests/test_flow_tracking.py @@ -0,0 +1,391 @@ +# Copyright (c) 2023-2024 Arista Networks, Inc. +# Use of this source code is governed by the Apache License 2.0 +# that can be found in the LICENSE file. +"""Test inputs for anta.tests.flow_tracking.""" + +from __future__ import annotations + +from typing import Any + +from anta.tests.flow_tracking import VerifyHardwareFlowTrackerStatus +from tests.lib.anta import test # noqa: F401; pylint: disable=W0611 + +DATA: list[dict[str, Any]] = [ + { + "name": "success", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [ + { + "trackers": { + "FLOW-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + { + "trackers": { + "HARDWARE-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + ], + "inputs": {"trackers": [{"name": "FLOW-TRACKER"}, {"name": "HARDWARE-TRACKER"}]}, + "expected": {"result": "success"}, + }, + { + "name": "success-with-optional-field", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [ + { + "trackers": { + "FLOW-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + { + "trackers": { + "HARDWARE-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + ], + "inputs": { + "trackers": [ + { + "name": "FLOW-TRACKER", + "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000}, + "exporters": [{"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}], + }, + { + "name": "HARDWARE-TRACKER", + "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000}, + "exporters": [{"name": "CVP-TELEMETRY", "local_interface": "Loopback10", "template_interval": 3600000}], + }, + ] + }, + "expected": {"result": "success"}, + }, + { + "name": "failure-flow-tracking-not-running", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [{"trackers": {}, "running": False}], + "inputs": {"trackers": [{"name": "FLOW-TRACKER"}]}, + "expected": { + "result": "failure", + "messages": ["Hardware flow tracking is not running."], + }, + }, + { + "name": "failure-tracker-not-configured", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [ + { + "trackers": { + "HARDWARE-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}}, + } + }, + "running": True, + } + ], + "inputs": {"trackers": [{"name": "FLOW-Sample"}]}, + "expected": { + "result": "failure", + "messages": ["Hardware flow tracker `FLOW-Sample` is not configured."], + }, + }, + { + "name": "failure-tracker-not-active", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [ + { + "trackers": { + "FLOW-TRACKER": { + "active": False, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + { + "trackers": { + "HARDWARE-TRACKER": { + "active": False, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + ], + "inputs": { + "trackers": [ + { + "name": "FLOW-TRACKER", + "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000}, + "exporters": [{"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}], + }, + { + "name": "HARDWARE-TRACKER", + "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000}, + "exporters": [{"name": "CVP-TELEMETRY", "local_interface": "Loopback10", "template_interval": 3600000}], + }, + ] + }, + "expected": { + "result": "failure", + "messages": ["Hardware flow tracker `FLOW-TRACKER` is not active.", "Hardware flow tracker `HARDWARE-TRACKER` is not active."], + }, + }, + { + "name": "failure-incorrect-record-export", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [ + { + "trackers": { + "FLOW-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + { + "trackers": { + "HARDWARE-TRACKER": { + "active": True, + "inactiveTimeout": 6000, + "activeInterval": 30000, + "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + ], + "inputs": { + "trackers": [ + { + "name": "FLOW-TRACKER", + "record_export": {"on_inactive_timeout": 6000, "on_interval": 30000}, + }, + { + "name": "HARDWARE-TRACKER", + "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000}, + }, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "FLOW-TRACKER: \n" + "Expected `6000` as the inactive timeout, but found `60000` instead.\nExpected `30000` as the interval, but found `300000` instead.\n", + "HARDWARE-TRACKER: \n" + "Expected `60000` as the inactive timeout, but found `6000` instead.\nExpected `300000` as the interval, but found `30000` instead.\n", + ], + }, + }, + { + "name": "failure-incorrect-exporters", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [ + { + "trackers": { + "FLOW-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": { + "CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}, + "CVP-FLOW": {"localIntf": "Loopback0", "templateInterval": 3600000}, + }, + } + }, + "running": True, + }, + { + "trackers": { + "HARDWARE-TRACKER": { + "active": True, + "inactiveTimeout": 6000, + "activeInterval": 30000, + "exporters": { + "CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}, + "Hardware-flow": {"localIntf": "Loopback10", "templateInterval": 3600000}, + }, + } + }, + "running": True, + }, + ], + "inputs": { + "trackers": [ + { + "name": "FLOW-TRACKER", + "exporters": [ + {"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}, + {"name": "CVP-FLOW", "local_interface": "Loopback10", "template_interval": 3500000}, + ], + }, + { + "name": "HARDWARE-TRACKER", + "exporters": [ + {"name": "Hardware-flow", "local_interface": "Loopback99", "template_interval": 3000000}, + {"name": "Reverse-flow", "local_interface": "Loopback101", "template_interval": 3000000}, + ], + }, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "FLOW-TRACKER: \n" + "Exporter `CVP-FLOW`: \n" + "Expected `Loopback10` as the local interface, but found `Loopback0` instead.\n" + "Expected `3500000` as the template interval, but found `3600000` instead.\n", + "HARDWARE-TRACKER: \n" + "Exporter `Hardware-flow`: \n" + "Expected `Loopback99` as the local interface, but found `Loopback10` instead.\n" + "Expected `3000000` as the template interval, but found `3600000` instead.\n" + "Exporter `Reverse-flow` is not configured.\n", + ], + }, + }, + { + "name": "failure-all-type", + "test": VerifyHardwareFlowTrackerStatus, + "eos_data": [ + { + "trackers": { + "HARDWARE-TRACKER": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + { + "trackers": { + "FLOW-TRIGGER": { + "active": False, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": {"CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + { + "trackers": { + "HARDWARE-FLOW": { + "active": True, + "inactiveTimeout": 6000, + "activeInterval": 30000, + "exporters": {"CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}}, + } + }, + "running": True, + }, + { + "trackers": { + "FLOW-TRACKER2": { + "active": True, + "inactiveTimeout": 60000, + "activeInterval": 300000, + "exporters": { + "CV-TELEMETRY": {"localIntf": "Loopback0", "templateInterval": 3600000}, + "CVP-FLOW": {"localIntf": "Loopback0", "templateInterval": 3600000}, + }, + } + }, + "running": True, + }, + { + "trackers": { + "HARDWARE-TRACKER2": { + "active": True, + "inactiveTimeout": 6000, + "activeInterval": 30000, + "exporters": { + "CVP-TELEMETRY": {"localIntf": "Loopback10", "templateInterval": 3600000}, + "Hardware-flow": {"localIntf": "Loopback10", "templateInterval": 3600000}, + }, + } + }, + "running": True, + }, + ], + "inputs": { + "trackers": [ + {"name": "FLOW-Sample"}, + { + "name": "FLOW-TRIGGER", + "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000}, + "exporters": [{"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}], + }, + { + "name": "HARDWARE-FLOW", + "record_export": {"on_inactive_timeout": 60000, "on_interval": 300000}, + }, + { + "name": "FLOW-TRACKER2", + "exporters": [ + {"name": "CV-TELEMETRY", "local_interface": "Loopback0", "template_interval": 3600000}, + {"name": "CVP-FLOW", "local_interface": "Loopback10", "template_interval": 3500000}, + ], + }, + { + "name": "HARDWARE-TRACKER2", + "exporters": [ + {"name": "Hardware-flow", "local_interface": "Loopback99", "template_interval": 3000000}, + {"name": "Reverse-flow", "local_interface": "Loopback101", "template_interval": 3000000}, + ], + }, + ] + }, + "expected": { + "result": "failure", + "messages": [ + "Hardware flow tracker `FLOW-Sample` is not configured.", + "Hardware flow tracker `FLOW-TRIGGER` is not active.", + "HARDWARE-FLOW: \n" + "Expected `60000` as the inactive timeout, but found `6000` instead.\nExpected `300000` as the interval, but found `30000` instead.\n", + "FLOW-TRACKER2: \nExporter `CVP-FLOW`: \n" + "Expected `Loopback10` as the local interface, but found `Loopback0` instead.\n" + "Expected `3500000` as the template interval, but found `3600000` instead.\n", + "HARDWARE-TRACKER2: \nExporter `Hardware-flow`: \n" + "Expected `Loopback99` as the local interface, but found `Loopback10` instead.\n" + "Expected `3000000` as the template interval, but found `3600000` instead.\n" + "Exporter `Reverse-flow` is not configured.\n", + ], + }, + }, +]