Skip to content

ANTA catalog for EVPN tests

Tests

Module related to EVPN tests.

VerifyEVPNType5Routes

Verifies EVPN Type-5 routes for given IP prefixes and VNIs.

It supports multiple levels of verification based on the provided input:

  1. Prefix/VNI only: Verifies there is at least one ‘active’ and ‘valid’ path across all Route Distinguishers (RDs) learning the given prefix and VNI.
  2. Specific Routes (RD/Domain): Verifies that routes matching the specified RDs and domains exist for the prefix/VNI. For each specified route, it checks if at least one of its paths is ‘active’ and ‘valid’.
  3. Specific Paths (Nexthop/Route Targets): Verifies that specific paths exist within a specified route (RD/Domain). For each specified path criteria (nexthop and optional route targets), it finds all matching paths received from the peer and checks if at least one of these matching paths is ‘active’ and ‘valid’. The route targets check ensures all specified RTs are present in the path’s extended communities (subset check).
Expected Results
  • Success:
    • If only prefix/VNI is provided: The prefix/VNI exists in the EVPN table and has at least one active and valid path across all RDs.
    • If specific routes are provided: All specified routes (by RD/Domain) are found, and each has at least one active and valid path (if paths are not specified for the route).
    • If specific paths are provided: All specified routes are found, and for each specified path criteria (nexthop/RTs), at least one matching path exists and is active and valid.
  • Failure:
    • No EVPN Type-5 routes are found for the given prefix/VNI.
    • A specified route (RD/Domain) is not found.
    • No active and valid path is found when required (either globally for the prefix, per specified route, or per specified path criteria).
    • A specified path criteria (nexthop/RTs) does not match any received paths for the route.
Examples
anta.tests.evpn:
  - VerifyEVPNType5Routes:
      prefixes:
        # At least one active/valid path across all RDs
        - address: 192.168.10.0/24
          vni: 10
        # Specific routes each has at least one active/valid path
        - address: 192.168.20.0/24
          vni: 20
          routes:
            - rd: 10.0.0.1:20
              domain: local
            - rd: 10.0.0.2:20
              domain: remote
        # At least one active/valid path matching the nexthop
        - address: 192.168.30.0/24
          vni: 30
          routes:
            - rd: 10.0.0.1:30
              domain: local
              paths:
                - nexthop: 10.1.1.1
        # At least one active/valid path matching nexthop and specific RTs
        - address: 192.168.40.0/24
          vni: 40
          routes:
            - rd: 10.0.0.1:40
              domain: local
              paths:
                - nexthop: 10.1.1.1
                  route_targets:
                    - 40:40

Inputs

Name Type Description Default
prefixes list[EVPNType5Prefix]
List of EVPN Type-5 prefixes to verify.
-
Source code in anta/tests/evpn.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
class VerifyEVPNType5Routes(AntaTest):
    """Verifies EVPN Type-5 routes for given IP prefixes and VNIs.

    It supports multiple levels of verification based on the provided input:

    1.  **Prefix/VNI only:** Verifies there is at least one 'active' and 'valid' path across all
        Route Distinguishers (RDs) learning the given prefix and VNI.
    2.  **Specific Routes (RD/Domain):** Verifies that routes matching the specified RDs and domains
        exist for the prefix/VNI. For each specified route, it checks if at least one of its paths
        is 'active' and 'valid'.
    3.  **Specific Paths (Nexthop/Route Targets):** Verifies that specific paths exist within a
        specified route (RD/Domain). For each specified path criteria (nexthop and optional route targets),
        it finds all matching paths received from the peer and checks if at least one of these
        matching paths is 'active' and 'valid'. The route targets check ensures all specified RTs
        are present in the path's extended communities (subset check).

    Expected Results
    ----------------
    * Success:
        - If only prefix/VNI is provided: The prefix/VNI exists in the EVPN table
          and has at least one active and valid path across all RDs.
        - If specific routes are provided: All specified routes (by RD/Domain) are found,
          and each has at least one active and valid path (if paths are not specified for the route).
        - If specific paths are provided: All specified routes are found, and for each specified path criteria (nexthop/RTs),
          at least one matching path exists and is active and valid.
    * Failure:
        - No EVPN Type-5 routes are found for the given prefix/VNI.
        - A specified route (RD/Domain) is not found.
        - No active and valid path is found when required (either globally for the prefix, per specified route, or per specified path criteria).
        - A specified path criteria (nexthop/RTs) does not match any received paths for the route.

    Examples
    --------
    ```yaml
    anta.tests.evpn:
      - VerifyEVPNType5Routes:
          prefixes:
            # At least one active/valid path across all RDs
            - address: 192.168.10.0/24
              vni: 10
            # Specific routes each has at least one active/valid path
            - address: 192.168.20.0/24
              vni: 20
              routes:
                - rd: 10.0.0.1:20
                  domain: local
                - rd: 10.0.0.2:20
                  domain: remote
            # At least one active/valid path matching the nexthop
            - address: 192.168.30.0/24
              vni: 30
              routes:
                - rd: 10.0.0.1:30
                  domain: local
                  paths:
                    - nexthop: 10.1.1.1
            # At least one active/valid path matching nexthop and specific RTs
            - address: 192.168.40.0/24
              vni: 40
              routes:
                - rd: 10.0.0.1:40
                  domain: local
                  paths:
                    - nexthop: 10.1.1.1
                      route_targets:
                        - 40:40
    ```
    """

    categories: ClassVar[list[str]] = ["bgp"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show bgp evpn route-type ip-prefix {address} vni {vni}", revision=2)]

    class Input(AntaTest.Input):
        """Input model for the VerifyEVPNType5Routes test."""

        prefixes: list[EVPNType5Prefix]
        """List of EVPN Type-5 prefixes to verify."""

    def render(self, template: AntaTemplate) -> list[AntaCommand]:
        """Render the template for each EVPN Type-5 prefix in the input list."""
        return [template.render(address=str(prefix.address), vni=prefix.vni) for prefix in self.inputs.prefixes]

    # NOTE: The following static methods can be moved at the module level if needed for other EVPN tests
    @staticmethod
    def _get_all_paths(evpn_routes_data: dict[str, Any]) -> list[dict[str, Any]]:
        """Extract all 'evpnRoutePaths' from the entire 'evpnRoutes' dictionary."""
        all_paths = []
        for route_data in evpn_routes_data.values():
            all_paths.extend(route_data["evpnRoutePaths"])
        return all_paths

    @staticmethod
    def _find_route(evpn_routes_data: dict[str, Any], rd_to_find: str, domain_to_find: str) -> dict[str, Any] | None:
        """Find the specific route block for a given RD and domain."""
        for route_data in evpn_routes_data.values():
            if route_data["routeKeyDetail"].get("rd") == rd_to_find and route_data["routeKeyDetail"].get("domain") == domain_to_find:
                return route_data
        return None

    @staticmethod
    def _find_paths(paths: list[dict[str, Any]], nexthop: str, route_targets: list[str] | None = None) -> list[dict[str, Any]]:
        """Find all matching paths for a given nexthop and RTs."""
        route_targets = [f"Route-Target-AS:{rt}" for rt in route_targets] if route_targets is not None else []
        return [path for path in paths if path["nextHop"] == nexthop and set(route_targets).issubset(set(path["routeDetail"]["extCommunities"]))]

    @staticmethod
    def _has_active_valid_path(paths: list[dict[str, Any]]) -> bool:
        """Check if any path in the list is active and valid."""
        return any(path["routeType"]["active"] and path["routeType"]["valid"] for path in paths)

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyEVPNType5Routes."""
        self.result.is_success()

        for command, prefix_input in zip(self.instance_commands, self.inputs.prefixes):
            # Verify that the prefix is in the BGP EVPN table
            evpn_routes_data = command.json_output.get("evpnRoutes")
            if not evpn_routes_data:
                self.result.is_failure(f"{prefix_input} - No EVPN Type-5 routes found")
                continue

            # Delegate verification logic for this prefix
            self._verify_routes_for_prefix(prefix_input, evpn_routes_data)

    def _verify_routes_for_prefix(self, prefix_input: EVPNType5Prefix, evpn_routes_data: dict[str, Any]) -> None:
        """Verify EVPN routes for an input prefix."""
        # Case: routes not provided for the prefix, check that at least one EVPN Type-5 route
        # has at least one active and valid path across all learned routes from all RDs combined
        if prefix_input.routes is None:
            all_paths = self._get_all_paths(evpn_routes_data)
            if not self._has_active_valid_path(all_paths):
                self.result.is_failure(f"{prefix_input} - No active and valid path found across all RDs")
            return

        # Case: routes *is* provided, check each specified route
        for route_input in prefix_input.routes:
            # Try to find a route with matching RD and domain
            route_data = self._find_route(evpn_routes_data, route_input.rd, route_input.domain)
            if route_data is None:
                self.result.is_failure(f"{prefix_input} {route_input} - Route not found")
                continue

            # Route found, now check its paths based on route_input criteria
            self._verify_paths_for_route(prefix_input, route_input, route_data)

    def _verify_paths_for_route(self, prefix_input: EVPNType5Prefix, route_input: EVPNRoute, route_data: dict[str, Any]) -> None:
        """Verify paths for a specific EVPN route (route_data) based on route_input criteria."""
        route_paths = route_data["evpnRoutePaths"]

        # Case: paths not provided for the route, check that at least one path is active/valid
        if route_input.paths is None:
            if not self._has_active_valid_path(route_paths):
                self.result.is_failure(f"{prefix_input} {route_input} - No active and valid path found")
            return

        # Case: paths *is* provided, check each specified path criteria
        for path_input in route_input.paths:
            self._verify_single_path(prefix_input, route_input, path_input, route_paths)

    def _verify_single_path(self, prefix_input: EVPNType5Prefix, route_input: EVPNRoute, path_input: EVPNPath, available_paths: list[dict[str, Any]]) -> None:
        """Verify if at least one active/valid path exists among available_paths matching the path_input criteria."""
        # Try to find all paths matching nexthop and RTs criteria from the available paths for this route
        matching_paths = self._find_paths(available_paths, path_input.nexthop, path_input.route_targets)
        if not matching_paths:
            self.result.is_failure(f"{prefix_input} {route_input} {path_input} - Path not found")
            return

        # Check that at least one matching path is active/valid
        if not self._has_active_valid_path(matching_paths):
            self.result.is_failure(f"{prefix_input} {route_input} {path_input} - No active and valid path found")

Input models

Module containing input models for EVPN tests.

EVPNPath

Model for an EVPN Type-5 path for a route.

Name Type Description Default
nexthop str
Expected next-hop IPv4 or IPv6 address. Can be an empty string for local paths.
-
route_targets list[str] | None
List of expected RTs following the `ASN(asplain):nn` or `ASN(asdot):nn` or `IP-address:nn` format.
None
Source code in anta/input_models/evpn.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class EVPNPath(BaseModel):
    """Model for an EVPN Type-5 path for a route."""

    model_config = ConfigDict(extra="forbid")
    nexthop: str
    """Expected next-hop IPv4 or IPv6 address. Can be an empty string for local paths."""
    route_targets: list[str] | None = None
    """List of expected RTs following the `ASN(asplain):nn` or `ASN(asdot):nn` or `IP-address:nn` format."""

    def __str__(self) -> str:
        """Return a human-readable string representation of the EVPNPath for reporting."""
        value = f"Nexthop: {self.nexthop}"
        if self.route_targets:
            value += f" RTs: {', '.join(self.route_targets)}"
        return value

EVPNRoute

Model for an EVPN Type-5 route for a prefix.

Name Type Description Default
rd str
Expected route distinguisher `<admin>:<local assignment>` of the route.
-
domain Literal['local', 'remote']
EVPN domain. Can be remote on gateway nodes in a multi-domain EVPN VXLAN fabric.
'local'
paths list[EVPNPath] | None
Specific paths to verify for this route.
None
Source code in anta/input_models/evpn.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class EVPNRoute(BaseModel):
    """Model for an EVPN Type-5 route for a prefix."""

    model_config = ConfigDict(extra="forbid")
    rd: str
    """Expected route distinguisher `<admin>:<local assignment>` of the route."""
    domain: Literal["local", "remote"] = "local"
    """EVPN domain. Can be remote on gateway nodes in a multi-domain EVPN VXLAN fabric."""
    paths: list[EVPNPath] | None = None
    """Specific paths to verify for this route."""

    def __str__(self) -> str:
        """Return a human-readable string representation of the EVPNRoute for reporting."""
        value = f"RD: {self.rd}"
        if self.domain == "remote":
            value += " Domain: remote"
        return value

EVPNType5Prefix

Model for an EVPN Type-5 prefix.

Name Type Description Default
address IPv4Interface | IPv6Interface
IPv4 or IPv6 prefix address to verify.
-
vni Vni
VNI associated with the prefix.
-
routes list[EVPNRoute] | None
Specific EVPN routes to verify for this prefix.
None
Source code in anta/input_models/evpn.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class EVPNType5Prefix(BaseModel):
    """Model for an EVPN Type-5 prefix."""

    model_config = ConfigDict(extra="forbid")
    address: IPv4Interface | IPv6Interface
    """IPv4 or IPv6 prefix address to verify."""
    vni: Vni
    """VNI associated with the prefix."""
    routes: list[EVPNRoute] | None = None
    """Specific EVPN routes to verify for this prefix."""

    def __str__(self) -> str:
        """Return a human-readable string representation of the EVPNType5Prefix for reporting."""
        return f"Prefix: {self.address} VNI: {self.vni}"