Skip to content

Commit

Permalink
Issue_853: Refactored testcase with input model changes
Browse files Browse the repository at this point in the history
  • Loading branch information
geetanjalimanegslab committed Jan 16, 2025
1 parent de48bd8 commit c383c57
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 163 deletions.
31 changes: 31 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 @@ -54,3 +55,33 @@ def __str__(self) -> str:
- User: Test Group: Test_Group Version: v2c
"""
return f"User: {self.username} Group: {self.group_name} Version: {self.version}"


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

group_name: str
"""SNMP group for the user."""
version: SnmpVersion
"""SNMP protocol version."""
read_view: str | None = None
"""View to restrict read access."""
write_view: str | None = None
"""View to restrict write access."""
notify_view: str | None = None
"""View to restrict notifications."""
authentication: Literal["v3Auth", "v3Priv", "v3NoAuth"] | None = None
"""Advanced authentication in v3 SNMP version. Defaults to None.
- v3Auth: Group using authentication but not privacy
- v3Priv: Group using both authentication and privacy
- v3NoAuth: Group using neither authentication nor privacy
"""

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}"
142 changes: 36 additions & 106 deletions anta/tests/snmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,81 +7,19 @@
# mypy: disable-error-code=attr-defined
from __future__ import annotations

from typing import TYPE_CHECKING, Any, ClassVar, Literal, get_args

from pydantic import BaseModel, model_validator
from typing import TYPE_CHECKING, ClassVar, get_args

from pydantic import field_validator

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

if TYPE_CHECKING:
from anta.models import AntaTemplate


def _get_snmp_group_failures(
version: SnmpVersion,
read_view: str | None,
write_view: str | None,
notify_view: str | None,
authentication: Literal["v3Auth", "v3Priv", "v3NoAuth"] | None,
group_details: dict[str, Any],
) -> str:
"""Validate SNMP group configurations and return failure messages if issues are found.
Parameters
----------
version
SNMP protocol version.
read_view
View to restrict read access.
write_view
View to restrict write access.
notify_view
View to restrict notifications.
authentication
Advanced authentication in v3 SNMP version
group_details
The SNMP group output from device.
Returns
-------
str
Failed log of a group details.
"""
failure: str = ""

def check_view(view_name: str, expected_view: str, default_message: str) -> str:
"""Check actual view and return failure log if any."""
failure_log: str = ""
actual_view = "Not Found" if (view := group_details.get(view_name)) is None else view or default_message
config_view = group_details.get(f"{view_name}Config")

if not config_view:
failure_log += f"\nThe '{expected_view}' view is not configured."
if expected_view != actual_view:
failure_log += f"\nExpected '{expected_view}' as '{view_name}' but found '{actual_view}' instead."
return failure_log

# Check views (read, write, notify)
if read_view:
failure += check_view("readView", read_view, "default: all included")
if write_view:
failure += check_view("writeView", write_view, "no write view specified")
if notify_view:
failure += check_view("notifyView", notify_view, "no notify view specified")

# Check version-specific authentication
if version == "v3" and (actual_auth := group_details.get("secModel")) != authentication:
failure += f"\nExpected '{authentication}' as security model but found '{actual_auth}' instead."

return failure


class VerifySnmpStatus(AntaTest):
"""Verifies whether the SNMP agent is enabled in a specified VRF.
Expand Down Expand Up @@ -556,8 +494,11 @@ def test(self) -> None:
class VerifySnmpGroup(AntaTest):
"""Verifies the SNMP group configurations for specified version(s).
- Verifies that the valid group name and security model version.
- Ensures that the SNMP views, the read, write and notify settings aligning with version-specific requirements.
This test performs the following checks:
1. Verifies that the SNMP group is configured for the specified version.
2. For SNMP version 3, verifies that the security model aligns with the expected value.
3. Ensures that the SNMP views, the read, write and notify settings aligning with version-specific requirements.
Expected Results
----------------
Expand All @@ -578,8 +519,6 @@ class VerifySnmpGroup(AntaTest):
```
"""

name = "VerifySnmpGroup"
description = "Verifies the SNMP group configurations for specified version(s)."
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp group", revision=1)]

Expand All @@ -589,40 +528,20 @@ class Input(AntaTest.Input):
snmp_groups: list[SnmpGroup]
"""List of SNMP groups."""

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

group_name: str
"""SNMP group for the user."""
version: SnmpVersion
"""SNMP protocol version."""
read_view: str | None = None
"""View to restrict read access."""
write_view: str | None = None
"""View to restrict write access."""
notify_view: str | None = None
"""View to restrict notifications."""
authentication: Literal["v3Auth", "v3Priv", "v3NoAuth"] | None = None
"""Advanced authentication in v3 SNMP version. Defaults to None.
- v3Auth: Group using authentication but not privacy
- v3Priv: Group using both authentication and privacy
- v3NoAuth: Group using neither authentication nor privacy
"""

@model_validator(mode="after")
def validate_inputs(self: BaseModel) -> BaseModel:
"""Validate the inputs provided to the SnmpGroup class."""
if self.version == "v3" and self.authentication is None:
msg = "SNMP versions v3, advanced authentication is required."
@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}; advanced `authentication` is required."
raise ValueError(msg)
return self
return snmp_groups

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpGroup."""
self.result.is_success()
failures: str = ""

for group in self.inputs.snmp_groups:
group_name = group.group_name
version = group.version
Expand All @@ -633,14 +552,25 @@ def test(self) -> None:

# Verify SNMP group details.
if not (group_details := get_value(self.instance_commands[0].json_output, f"groups.{group_name}.versions.{version}")):
failures += f"SNMP group '{group_name}' is not configured with security model '{version}'.\n"
self.result.is_failure(f"{group} - Not configured")
continue

# Collecting failures logs if any.
failure_logs = _get_snmp_group_failures(version, read_view, write_view, notify_view, authentication, group_details)
if failure_logs:
failures += f"For SNMP group {group_name} with SNMP version {version}:{failure_logs}\n"

# Check if there are any failures.
if failures:
self.result.is_failure(failures)
# Verify SNMP views, the read, write and notify settings aligning with version-specific requirements.
if group_details.get("readView") != read_view:
self.result.is_failure(f"{group} - Incorrect read view - Expected: {read_view} Actual: {group_details.get('readView')}")
elif not group_details.get("readViewConfig"):
self.result.is_failure(f"{group} - Read view not configured")

if group_details.get("writeView") != write_view:
self.result.is_failure(f"{group} - Incorrect write view - Expected: {write_view} Actual: {group_details.get('writeView')}")
elif not group_details.get("writeViewConfig"):
self.result.is_failure(f"{group} - Write view not configured")

if group_details.get("notifyView") != notify_view:
self.result.is_failure(f"{group} - Incorrect notify view - Expected: {notify_view} Actual: {group_details.get('notifyView')}")
elif not group_details.get("notifyViewConfig"):
self.result.is_failure(f"{group} - Notify view not configured")

# For version v3, verify that the security model aligns with the expected value.
if version == "v3" and (actual_auth := group_details.get("secModel")) != authentication:
self.result.is_failure(f"{group} - Incorrect security model - Expected: {authentication} Actual: {actual_auth}")
28 changes: 9 additions & 19 deletions examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,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 Expand Up @@ -800,25 +808,7 @@ anta.tests.snmp:
group_name: test_group
version: v3
auth_type: MD5
priv_type: AES-128 - 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: v2c
read_view: group_read_2
write_view: group_write_2
notify_view: group_notify_2
- group_name: Group3
version: v3
authentication: v3Auth
read_view: group_read_3
write_view: group_write_3
notify_view: group_notify_3

priv_type: AES-128
anta.tests.software:
- VerifyEOSExtensions:
# Verifies that all EOS extensions installed on the device are enabled for boot persistence.
Expand Down
Loading

0 comments on commit c383c57

Please sign in to comment.