summaryrefslogtreecommitdiffstats
path: root/anta/tests/connectivity.py
blob: 7222f5632b61e4763c03e81cd8ffbd6a61a99015 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
# 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 functions related to various connectivity checks
"""
# Mypy does not understand AntaTest.Input typing
# mypy: disable-error-code=attr-defined
from __future__ import annotations

from ipaddress import IPv4Address

# Need to keep List for pydantic in python 3.8
from typing import List, Union

from pydantic import BaseModel

from anta.custom_types import Interface
from anta.models import AntaCommand, AntaMissingParamException, AntaTemplate, AntaTest


class VerifyReachability(AntaTest):
    """
    Test network reachability to one or many destination IP(s).

    Expected Results:
        * success: The test will pass if all destination IP(s) are reachable.
        * failure: The test will fail if one or many destination IP(s) are unreachable.
    """

    name = "VerifyReachability"
    description = "Test the network reachability to one or many destination IP(s)."
    categories = ["connectivity"]
    commands = [AntaTemplate(template="ping vrf {vrf} {destination} source {source} repeat {repeat}")]

    class Input(AntaTest.Input):  # pylint: disable=missing-class-docstring
        hosts: List[Host]
        """List of hosts to ping"""

        class Host(BaseModel):
            """Remote host to ping"""

            destination: IPv4Address
            """IPv4 address to ping"""
            source: Union[IPv4Address, Interface]
            """IPv4 address source IP or Egress interface to use"""
            vrf: str = "default"
            """VRF context"""
            repeat: int = 2
            """Number of ping repetition (default=2)"""

    def render(self, template: AntaTemplate) -> list[AntaCommand]:
        return [template.render(destination=host.destination, source=host.source, vrf=host.vrf, repeat=host.repeat) for host in self.inputs.hosts]

    @AntaTest.anta_test
    def test(self) -> None:
        failures = []
        for command in self.instance_commands:
            src = command.params.get("source")
            dst = command.params.get("destination")
            repeat = command.params.get("repeat")

            if any(elem is None for elem in (src, dst, repeat)):
                raise AntaMissingParamException(f"A parameter is missing to execute the test for command {command}")

            if f"{repeat} received" not in command.json_output["messages"][0]:
                failures.append((str(src), str(dst)))

        if not failures:
            self.result.is_success()
        else:
            self.result.is_failure(f"Connectivity test failed for the following source-destination pairs: {failures}")


class VerifyLLDPNeighbors(AntaTest):
    """
    This test verifies that the provided LLDP neighbors are present and connected with the correct configuration.

    Expected Results:
        * success: The test will pass if each of the provided LLDP neighbors is present and connected to the specified port and device.
        * failure: The test will fail if any of the following conditions are met:
            - The provided LLDP neighbor is not found.
            - The system name or port of the LLDP neighbor does not match the provided information.
    """

    name = "VerifyLLDPNeighbors"
    description = "Verifies that the provided LLDP neighbors are connected properly."
    categories = ["connectivity"]
    commands = [AntaCommand(command="show lldp neighbors detail")]

    class Input(AntaTest.Input):  # pylint: disable=missing-class-docstring
        neighbors: List[Neighbor]
        """List of LLDP neighbors"""

        class Neighbor(BaseModel):
            """LLDP neighbor"""

            port: Interface
            """LLDP port"""
            neighbor_device: str
            """LLDP neighbor device"""
            neighbor_port: Interface
            """LLDP neighbor port"""

    @AntaTest.anta_test
    def test(self) -> None:
        command_output = self.instance_commands[0].json_output

        failures: dict[str, list[str]] = {}

        for neighbor in self.inputs.neighbors:
            if neighbor.port not in command_output["lldpNeighbors"]:
                failures.setdefault("port_not_configured", []).append(neighbor.port)
            elif len(lldp_neighbor_info := command_output["lldpNeighbors"][neighbor.port]["lldpNeighborInfo"]) == 0:
                failures.setdefault("no_lldp_neighbor", []).append(neighbor.port)
            elif (
                lldp_neighbor_info[0]["systemName"] != neighbor.neighbor_device
                or lldp_neighbor_info[0]["neighborInterfaceInfo"]["interfaceId_v2"] != neighbor.neighbor_port
            ):
                failures.setdefault("wrong_lldp_neighbor", []).append(neighbor.port)

        if not failures:
            self.result.is_success()
        else:
            self.result.is_failure(f"The following port(s) have issues: {failures}")