Skip to content

Commit

Permalink
feat(anta): Added the test case to verify SNMP groups (#886)
Browse files Browse the repository at this point in the history
* issue_853 Added TC for SNMP groups

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Issue_853: Refactored testcase with input model changes

* Issue_853: UUpdated docstrings,test failure message and optimized code

* Issue_853: Updated test failure message

* Issue_853: Optimized code, added UT for view configuration check

* Issue_853: Updated testcase docstrings and fixed Cognitive Complexity issue

* Issue_853: fixed Cognitive Complexity issue

* Issue_853: optimized code to fix the congnitive complexity issues

* Issue_853: optimized code, updated test failure messages, added snmpauth validator

* Issue_853: updated input model unit testcase with snmp auth version change

* Issue_853: Added unit testcases for snmp group input model

* Issue_853: Updated test failure msga nd custom_types.py file

* Refactor: Better annotation

* Issue_853: Updated customtypes, and thier unit test for snmpv3 prefix

* Update anta/input_models/snmp.py

* Updated snmp_v3_prefix

---------

Co-authored-by: VitthalMagadum <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Geetanjali.mane <[email protected]>
Co-authored-by: gmuloc <[email protected]>
Co-authored-by: Carl Baillargeon <[email protected]>
  • Loading branch information
6 people authored Feb 6, 2025
1 parent efef2c9 commit 5780126
Show file tree
Hide file tree
Showing 7 changed files with 504 additions and 10 deletions.
25 changes: 19 additions & 6 deletions anta/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,6 @@ def validate_regex(value: str) -> str:
]
BgpUpdateError = Literal["inUpdErrWithdraw", "inUpdErrIgnore", "inUpdErrDisableAfiSafi", "disabledAfiSafi", "lastUpdErrTime"]
BfdProtocol = Literal["bgp", "isis", "lag", "ospf", "ospfv3", "pim", "route-input", "static-bfd", "static-route", "vrrp", "vxlan"]
SnmpPdu = Literal["inGetPdus", "inGetNextPdus", "inSetPdus", "outGetResponsePdus", "outTrapPdus"]
SnmpErrorCounter = Literal[
"inVersionErrs", "inBadCommunityNames", "inBadCommunityUses", "inParseErrs", "outTooBigErrs", "outNoSuchNameErrs", "outBadValueErrs", "outGeneralErrs"
]
IPv4RouteType = Literal[
"connected",
"static",
Expand Down Expand Up @@ -301,8 +297,25 @@ def validate_regex(value: str) -> str:
"Route Cache Route",
"CBF Leaked Route",
]
DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"]
LogSeverityLevel = Literal["alerts", "critical", "debugging", "emergencies", "errors", "informational", "notifications", "warnings"]


########################################
# SNMP
########################################
def snmp_v3_prefix(auth_type: Literal["auth", "priv", "noauth"]) -> str:
"""Prefix the SNMP authentication type with 'v3'."""
if auth_type == "noauth":
return "v3NoAuth"
return f"v3{auth_type.title()}"


SnmpVersion = Literal["v1", "v2c", "v3"]
SnmpHashingAlgorithm = Literal["MD5", "SHA", "SHA-224", "SHA-256", "SHA-384", "SHA-512"]
SnmpEncryptionAlgorithm = Literal["AES-128", "AES-192", "AES-256", "DES"]
DynamicVlanSource = Literal["dmf", "dot1x", "dynvtep", "evpn", "mlag", "mlagsync", "mvpn", "swfwd", "vccbfd"]
LogSeverityLevel = Literal["alerts", "critical", "debugging", "emergencies", "errors", "informational", "notifications", "warnings"]
SnmpPdu = Literal["inGetPdus", "inGetNextPdus", "inSetPdus", "outGetResponsePdus", "outTrapPdus"]
SnmpErrorCounter = Literal[
"inVersionErrs", "inBadCommunityNames", "inBadCommunityUses", "inParseErrs", "outTooBigErrs", "outNoSuchNameErrs", "outBadValueErrs", "outGeneralErrs"
]
SnmpVersionV3AuthType = Annotated[Literal["auth", "priv", "noauth"], AfterValidator(snmp_v3_prefix)]
48 changes: 45 additions & 3 deletions anta/input_models/snmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@
from __future__ import annotations

from ipaddress import IPv4Address
from typing import Literal
from typing import TYPE_CHECKING, Literal

from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, model_validator

from anta.custom_types import Hostname, Interface, Port, SnmpEncryptionAlgorithm, SnmpHashingAlgorithm, SnmpVersion
from anta.custom_types import Hostname, Interface, Port, SnmpEncryptionAlgorithm, SnmpHashingAlgorithm, SnmpVersion, SnmpVersionV3AuthType

if TYPE_CHECKING:
import sys

if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self


class SnmpHost(BaseModel):
Expand Down Expand Up @@ -83,3 +91,37 @@ def __str__(self) -> str:
- Source Interface: Ethernet1 VRF: default
"""
return f"Source Interface: {self.interface} VRF: {self.vrf}"


class SnmpGroup(BaseModel):
"""Model for an 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: SnmpVersionV3AuthType | None = None
"""SNMPv3 authentication settings. Required when version is v3. Can be provided in the `VerifySnmpGroup` test."""

@model_validator(mode="after")
def validate_inputs(self) -> Self:
"""Validate the inputs provided to the SnmpGroup class."""
if self.version == "v3" and self.authentication is None:
msg = f"{self!s}: `authentication` field is missing in the input"
raise ValueError(msg)
return self

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}"
72 changes: 71 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 @@ -664,3 +664,73 @@ 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 if any specified parameter is not correctly configured.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpGroup:
snmp_groups:
- group_name: Group1
version: v1
read_view: group_read_1
write_view: group_write_1
notify_view: group_notify_1
- group_name: Group2
version: v3
read_view: group_read_2
write_view: group_write_2
notify_view: group_notify_2
authentication: priv
```
"""

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."""

@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

view_types = [view_type for view_type in ["read", "write", "notify"] if getattr(group, f"{view_type}_view")]
# Verify SNMP views, the read, write and notify settings aligning with version-specific requirements.
for view_type in view_types:
expected_view = getattr(group, f"{view_type}_view")
# Verify actual view is configured.
if group_details.get(f"{view_type}View") == "":
self.result.is_failure(f"{group} View: {view_type} - Not configured")
elif (act_view := group_details.get(f"{view_type}View")) != expected_view:
self.result.is_failure(f"{group} - Incorrect {view_type.title()} view - Expected: {expected_view}, Actual: {act_view}")
elif not group_details.get(f"{view_type}ViewConfig"):
self.result.is_failure(f"{group}, {view_type.title()} View: {expected_view} - Not configured")

# 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}")
14 changes: 14 additions & 0 deletions examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,20 @@ 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
- group_name: Group2
version: v3
read_view: group_read_2
write_view: group_write_2
notify_view: group_notify_2
authentication: priv
- VerifySnmpHostLogging:
# Verifies SNMP logging configurations.
hosts:
Expand Down
Loading

0 comments on commit 5780126

Please sign in to comment.