Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(anta): Added the test case to Verify SNMP Notification Host #838

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions anta/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,4 @@ def validate_regex(value: str) -> str:
"Route Cache Route",
"CBF Leaked Route",
]
SnmpVersion = Literal["v1", "v2c", "v3"]
42 changes: 42 additions & 0 deletions anta/input_models/snmp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright (c) 2023-2025 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 containing input models for SNMP tests."""

from __future__ import annotations

from ipaddress import IPv4Address
from typing import Literal

from pydantic import BaseModel, ConfigDict

from anta.custom_types import Hostname, Port, SnmpVersion


class SNMPHost(BaseModel):
"""Model for a SNMP Host."""

model_config = ConfigDict(extra="forbid")
hostname: IPv4Address | Hostname
"""IPv4 address of the SNMP notification host."""
vrf: str = "default"
"""Optional VRF for SNMP Hosts. If not provided, it defaults to `default`."""
notification_type: Literal["trap", "inform"] = "trap"
"""Type of SNMP notification (trap or inform), it defaults to trap."""
version: SnmpVersion | None = None
"""SNMP protocol version.Required field in the `VerifySNMPNotificationHost` test."""
udp_port: Port | int = 162
"""UDP port for SNMP. If not provided then defaults to 162."""
community_string: str | None = None
"""Optional SNMP community string for authentication,required for SNMP version is v1 or v2c. Can be provided in the `VerifySNMPNotificationHost` test."""
user: str | None = None
"""Optional SNMP user for authentication, required for SNMP version v3. Can be provided in the `VerifySNMPNotificationHost` test."""

def __str__(self) -> str:
"""Return a human-readable string representation of the Host for reporting.

Examples
--------
- Host: 192.168.1.100 VRF: default
"""
return f"Host: {self.hostname} VRF: {self.vrf}"
113 changes: 113 additions & 0 deletions anta/tests/snmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

from typing import TYPE_CHECKING, ClassVar, get_args

from pydantic import field_validator

from anta.custom_types import PositiveInteger, SnmpErrorCounter, SnmpPdu
from anta.input_models.snmp import SNMPHost
from anta.models import AntaCommand, AntaTest
from anta.tools import get_value

Expand Down Expand Up @@ -339,3 +342,113 @@ def test(self) -> None:
self.result.is_success()
else:
self.result.is_failure(f"The following SNMP error counters are not found or have non-zero error counters:\n{error_counters_not_ok}")


class VerifySNMPNotificationHost(AntaTest):
"""Verifies the SNMP notification host (SNMP manager) configurations.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have more brief description about what configuration we are verifying

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated docstring.


This test performs the following checks for each specified host:

1. Verifies that the SNMP host and hostname is configured on the device.
2. Verifies that the notification type matches the expected value.
3. Ensures that UDP port provided matches the expected value.
4. Ensures that the a valid community string is properly set for SNMP version v1/v2c and it should matches the expected value.
5. Ensures that the a valid user field is properly set for SNMP version v3 and it should matches the expected value.

Expected Results
----------------
* Success: The test will pass if all of the following conditions are met:
- The SNMP host and hostname is configured on the device.
- The notification type and UDP port matches the expected value.
- The valid community string is properly set for SNMP version v1/v2c and it should matches the expected value.
- The valid user field is included for SNMP v3 and it should matches the expected value.

* Failure: The test will fail if any of the following conditions is met:
- The SNMP host or hostname is not configured on the device.
- The notification type or UDP port do not matches the expected value.
- The community string is for SNMP version v1/v2c is not matches the expected value.
- The user field for SNMP version v3 is not matches the expected value.

Examples
--------
```yaml
anta.tests.snmp:
- VerifySNMPNotificationHost:
notification_hosts:
- hostname: 192.168.1.100
vrf: default
notification_type: trap
version: v1
udp_port: 162
community_string: public
user: public
```
"""

categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp notification host", revision=1)]

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

notification_hosts: list[SNMPHost]
"""List of SNMP hosts."""

@field_validator("notification_hosts")
@classmethod
def validate_notification_hosts(cls, notification_hosts: list[SNMPHost]) -> list[SNMPHost]:
"""Validate that all required fields are provided in each SNMP Notification Host."""
for host in notification_hosts:
if host.version is None:
msg = f"{host}; 'version' field missing in the input"
raise ValueError(msg)
if host.version in ["v1", "v2c"] and host.community_string is None:
msg = f"{host} Version: {host.version}; 'community_string' field missing in the input"
raise ValueError(msg)
if host.version == "v3" and host.user is None:
msg = f"{host} Version: {host.version}; 'user' field missing in the input"
raise ValueError(msg)
return notification_hosts

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

# If SNMP host is not configured, test fails.
if not (snmp_hosts := get_value(self.instance_commands[0].json_output, "hosts")):
self.result.is_failure("No SNMP host is configured.")
return

for host in self.inputs.notification_hosts:
vrf = "" if host.vrf == "default" else host.vrf
hostname = str(host.hostname)
notification_type = host.notification_type
version = host.version
udp_port = host.udp_port
community_string = host.community_string
user = host.user

host_details = next(
(host for host in snmp_hosts if (host.get("hostname") == hostname and host.get("protocolVersion") == version and host.get("vrf") == vrf)), None
)
# If expected SNMP hostname is not configured with the specified protocol version, test fails.
if not host_details:
self.result.is_failure(f"{host} Version: {version} - Not configured")
continue

# If actual notification type do not matches the expected value, test fails.
if notification_type != (actual_notification_type := get_value(host_details, "notificationType", "Not Found")):
self.result.is_failure(f"{host} - Incorrect notification type - Expected: {notification_type} Actual: {actual_notification_type}")

# If actual udp port do not matches the expected value, test fails.
if udp_port != (actual_udp_port := get_value(host_details, "port", "Not Found")):
self.result.is_failure(f"{host} - Incorrect UDP port - Expected: {udp_port} Actual: {actual_udp_port}")

# If SNMP protocol version is v1 or v2c and actual community string do not matches the expected value, test fails.
if version in ["v1", "v2c"] and community_string != (actual_community_string := get_value(host_details, "v1v2cParams.communityString", "Not Found")):
self.result.is_failure(f"{host} Version: {version} - Incorrect community string - Expected: {community_string} Actual: {actual_community_string}")

# If SNMP protocol version is v3 and actual user do not matches the expected value, test fails.
elif version == "v3" and user != (actual_user := get_value(host_details, "v3Params.user", "Not Found")):
self.result.is_failure(f"{host} Version: {version} - Incorrect user - Expected: {user} Actual: {actual_user}")
15 changes: 15 additions & 0 deletions docs/api/tests.snmp.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ anta_title: ANTA catalog for SNMP tests
~ that can be found in the LICENSE file.
-->

# Tests

::: anta.tests.snmp
options:
show_root_heading: false
Expand All @@ -18,3 +20,16 @@ anta_title: ANTA catalog for SNMP tests
filters:
- "!test"
- "!render"

# Input models

::: anta.input_models.snmp

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: ["!^__str__"]
10 changes: 10 additions & 0 deletions examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,16 @@ anta.tests.services:
# Verifies the hostname of a device.
hostname: s1-spine1
anta.tests.snmp:
- VerifySNMPNotificationHost:
# Verifies the SNMP notification host (SNMP manager) configurations.
notification_hosts:
- hostname: 192.168.1.100
vrf: default
notification_type: trap
version: v1
udp_port: 162
community_string: public
user: public
- VerifySnmpContact:
# Verifies the SNMP contact of a device.
contact: [email protected]
Expand Down
Loading
Loading