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 groups #886

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
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
27 changes: 27 additions & 0 deletions anta/input_models/snmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from __future__ import annotations

from ipaddress import IPv4Address
from typing import Literal

from pydantic import BaseModel, ConfigDict

Expand Down Expand Up @@ -72,3 +73,29 @@ def __str__(self) -> str:
- Source Interface: Ethernet1 VRF: default
"""
return f"Source Interface: {self.interface} VRF: {self.vrf}"


class SnmpGroup(BaseModel):
"""Model for a SNMP group."""

group_name: str
"""SNMP group name."""
version: SnmpVersion
"""SNMP protocol version."""
read_view: str | None = None
"""Optional field, View to restrict read access."""
write_view: str | None = None
"""Optional field, View to restrict write access."""
notify_view: str | None = None
"""Optional field, View to restrict notifications."""
authentication: Literal["v3Auth", "v3Priv", "v3NoAuth"] | None = None
"""SNMPv3 authentication settings. Required when version is v3."""

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

Examples
--------
- Group: Test_Group Version: v2c
"""
return f"Group: {self.group_name}, Version: {self.version}"
80 changes: 79 additions & 1 deletion anta/tests/snmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from pydantic import field_validator

from anta.custom_types import PositiveInteger, SnmpErrorCounter, SnmpPdu
from anta.input_models.snmp import SnmpHost, SnmpSourceInterface, SnmpUser
from anta.input_models.snmp import SnmpGroup, SnmpHost, SnmpSourceInterface, SnmpUser
from anta.models import AntaCommand, AntaTest
from anta.tools import get_value

Expand Down Expand Up @@ -543,3 +543,81 @@ def test(self) -> None:
self.result.is_failure(f"{interface_details} - Not configured")
elif actual_interface != interface_details.interface:
self.result.is_failure(f"{interface_details} - Incorrect source interface - Actual: {actual_interface}")


class VerifySnmpGroup(AntaTest):
"""Verifies the SNMP group configurations for specified version(s).
This test performs the following checks:
1. Verifies that the SNMP group is configured for the specified version.
2. For SNMP version 3, verify that the security model matches the expected value.
3. Ensures that SNMP group configurations, including read, write, and notify views, align with version-specific requirements.
Expected Results
----------------
* Success: The test will pass if the provided SNMP group and all specified parameters are correctly configured.
* Failure: The test will fail if the provided SNMP group is not configured or specified parameters are not correctly configured.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* Failure: The test will fail if the provided SNMP group is not configured or specified parameters are not correctly configured.
* Failure: The test will fail if the provided SNMP group is not configured or if any specified parameter is not correctly configured.

Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpGroup:
snmp_groups:
- group_name: Group1
Copy link
Collaborator

Choose a reason for hiding this comment

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

show other versions in example - especially with the security model for v3

version: v1
read_view: group_read_1
write_view: group_write_1
notify_view: group_notify_1
```
"""

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

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

snmp_groups: list[SnmpGroup]
"""List of SNMP groups."""

@field_validator("snmp_groups")
@classmethod
def validate_snmp_groups(cls, snmp_groups: list[SnmpGroup]) -> list[SnmpGroup]:
"""Validate the inputs provided to the SnmpGroup class."""
for snmp_group in snmp_groups:
if snmp_group.version == "v3" and snmp_group.authentication is None:
msg = f"{snmp_group}; `authentication` field is required for `version: v3`"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please don't use semi columns

Suggested change
msg = f"{snmp_group}; `authentication` field is required for `version: v3`"
msg = f"{snmp_group}: `authentication` field is required for `version: v3`"

raise ValueError(msg)
return snmp_groups

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpGroup."""
self.result.is_success()
for group in self.inputs.snmp_groups:
# Verify SNMP group details.
if not (group_details := get_value(self.instance_commands[0].json_output, f"groups.{group.group_name}.versions.{group.version}")):
self.result.is_failure(f"{group} - Not configured")
continue

# Verify SNMP views, the read, write and notify settings aligning with version-specific requirements.
for view_type in ["read", "write", "notify"]:
expected_view = getattr(group, f"{view_type}_view")

# Verify actual view is configured.
if expected_view and group_details.get(f"{view_type}View") == "":
self.result.is_failure(f"{group} View: {view_type} - Not configured")
elif expected_view and not all(
[(act_view := group_details.get(f"{view_type}View")) == expected_view, (view_configured := group_details.get(f"{view_type}ViewConfig"))]
):
self.result.is_failure(
f"{group} {view_type.title()} View: {expected_view} - "
f"View configuration mismatch - {view_type.title()} View: {act_view}, "
f"Configured: {view_configured}"
)
Comment on lines +612 to +619
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would rather make two error messages, 1 to indicate the view name is not correct (matcging the expecting one) and 2. to indicate that when the view name is matching, it is not properly configured (Configured: False)

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed


# For version v3, verify that the security model aligns with the expected value.
if group.version == "v3" and (actual_auth := group_details.get("secModel")) != group.authentication:
self.result.is_failure(f"{group} - Incorrect security model - Expected: {group.authentication} Actual: {actual_auth}")
8 changes: 8 additions & 0 deletions examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,14 @@ anta.tests.snmp:
# Verifies the SNMP error counters.
error_counters:
- inVersionErrs
- VerifySnmpGroup:
# Verifies the SNMP group configurations for specified version(s).
snmp_groups:
- group_name: Group1
version: v1
read_view: group_read_1
write_view: group_write_1
notify_view: group_notify_1
- VerifySnmpHostLogging:
# Verifies SNMP logging configurations.
hosts:
Expand Down
Loading
Loading