Skip to content

Commit

Permalink
feat(anta.tests): Add collect knob to influence route collect behavio…
Browse files Browse the repository at this point in the history
…r: one=a route per command, all=all routes in vrf
  • Loading branch information
dlobato committed Jul 26, 2024
1 parent eee49de commit 0094cb3
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 8 deletions.
35 changes: 27 additions & 8 deletions anta/tests/routing/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
# mypy: disable-error-code=attr-defined
from __future__ import annotations

from ipaddress import IPv4Address, ip_interface
from functools import cache
from ipaddress import IPv4Address, IPv4Interface
from typing import ClassVar, Literal

from pydantic import model_validator
Expand Down Expand Up @@ -131,7 +132,10 @@ class VerifyRoutingTableEntry(AntaTest):
name = "VerifyRoutingTableEntry"
description = "Verifies that the provided routes are present in the routing table of a specified VRF."
categories: ClassVar[list[str]] = ["routing"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show ip route vrf {vrf} {route}", revision=4)]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [
AntaTemplate(template="show ip route vrf {vrf} {route}", revision=4),
AntaTemplate(template="show ip route vrf {vrf}", revision=4),
]

class Input(AntaTest.Input):
"""Input model for the VerifyRoutingTableEntry test."""
Expand All @@ -140,20 +144,35 @@ class Input(AntaTest.Input):
"""VRF context. Defaults to `default` VRF."""
routes: list[IPv4Address]
"""List of routes to verify."""
collect: Literal["one", "all"] = "one"
"""Route collect behavior: one=one route per command, all=all routes in vrf per command. Defaults to `one`"""

def render(self, template: AntaTemplate) -> list[AntaCommand]:
"""Render the template for each route in the input list."""
return [template.render(vrf=self.inputs.vrf, route=route) for route in self.inputs.routes]
"""Render the template for the input vrf."""
if template == VerifyRoutingTableEntry.commands[0] and self.inputs.collect == "one":
return [template.render(vrf=self.inputs.vrf, route=route) for route in self.inputs.routes]

if template == VerifyRoutingTableEntry.commands[1] and self.inputs.collect == "all":
return [template.render(vrf=self.inputs.vrf)]

return []

@staticmethod
@cache
def ip_interface_ip(route: str) -> IPv4Address:
"""Return the IP address of the provided ip route with mask."""
return IPv4Interface(route).ip

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifyRoutingTableEntry."""
missing_routes = []
commands_output_route_ips = set()

for command in self.instance_commands:
vrf, route = command.params.vrf, command.params.route
if len(routes := command.json_output["vrfs"][vrf]["routes"]) == 0 or route != ip_interface(next(iter(routes))).ip:
missing_routes.append(str(route))
command_output_vrf = command.json_output["vrfs"][self.inputs.vrf]
commands_output_route_ips |= {self.ip_interface_ip(route) for route in command_output_vrf["routes"]}

missing_routes = [str(route) for route in self.inputs.routes if route not in commands_output_route_ips]

if not missing_routes:
self.result.is_success()
Expand Down
91 changes: 91 additions & 0 deletions tests/units/anta_tests/routing/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,48 @@
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"]},
"expected": {"result": "success"},
},
{
"name": "success-collect-all",
"test": VerifyRoutingTableEntry,
"eos_data": [
{
"vrfs": {
"default": {
"routingDisabled": False,
"allRoutesProgrammedHardware": True,
"allRoutesProgrammedKernel": True,
"defaultRouteState": "notSet",
"routes": {
"10.1.0.1/32": {
"hardwareProgrammed": True,
"routeType": "eBGP",
"routeLeaked": False,
"kernelProgrammed": True,
"routeAction": "forward",
"directlyConnected": False,
"preference": 20,
"metric": 0,
"vias": [{"nexthopAddr": "10.1.255.4", "interface": "Ethernet1"}],
},
"10.1.0.2/32": {
"hardwareProgrammed": True,
"routeType": "eBGP",
"routeLeaked": False,
"kernelProgrammed": True,
"routeAction": "forward",
"directlyConnected": False,
"preference": 20,
"metric": 0,
"vias": [{"nexthopAddr": "10.1.255.6", "interface": "Ethernet2"}],
},
},
},
},
},
],
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"], "collect": "all"},
"expected": {"result": "success"},
},
{
"name": "failure-missing-route",
"test": VerifyRoutingTableEntry,
Expand Down Expand Up @@ -226,4 +268,53 @@
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"]},
"expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"]},
},
{
"name": "failure-wrong-route-collect-all",
"test": VerifyRoutingTableEntry,
"eos_data": [
{
"vrfs": {
"default": {
"routingDisabled": False,
"allRoutesProgrammedHardware": True,
"allRoutesProgrammedKernel": True,
"defaultRouteState": "notSet",
"routes": {
"10.1.0.1/32": {
"hardwareProgrammed": True,
"routeType": "eBGP",
"routeLeaked": False,
"kernelProgrammed": True,
"routeAction": "forward",
"directlyConnected": False,
"preference": 20,
"metric": 0,
"vias": [{"nexthopAddr": "10.1.255.4", "interface": "Ethernet1"}],
},
"10.1.0.55/32": {
"hardwareProgrammed": True,
"routeType": "eBGP",
"routeLeaked": False,
"kernelProgrammed": True,
"routeAction": "forward",
"directlyConnected": False,
"preference": 20,
"metric": 0,
"vias": [{"nexthopAddr": "10.1.255.6", "interface": "Ethernet2"}],
},
},
},
},
},
],
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"], "collect": "all"},
"expected": {"result": "failure", "messages": ["The following route(s) are missing from the routing table of VRF default: ['10.1.0.2']"]},
},
{
"name": "collect-input-error",
"test": VerifyRoutingTableEntry,
"eos_data": {},
"inputs": {"vrf": "default", "routes": ["10.1.0.1", "10.1.0.2"], "collect": "not-valid"},
"expected": {"result": "error", "messages": ["Inputs are not valid"]},
},
]

0 comments on commit 0094cb3

Please sign in to comment.