Repository: aristanetworks/anta (python)

Issue:
Add the test case to verify SNMP Logging Configuration. 
This test case verifies whether SNMP logging is enabled and if the SNMP host and VRF are correctly configured

Pull request:
feat(anta): Added the test case to verify SNMP Logging Configuration
# Description

 This test performs the following checks for each specified host:

     1. Verifies that the SNMP logging is enabled on the device.
     2. Verifies SNMP host matches the expected value.
     3. Ensures that VRF provided matches the expected value.

    Expected Results
    ----------------
    * Success: The test will pass if all of the following conditions are met:
        - The SNMP logging is enabled on the device.
        - The SNMP host matches the expected value.
        - The VRF provided matches the expected value.
    * Failure: The test will fail if any of the following conditions is met:
        - The SNMP logging is disabled on the device.
        - The SNMP host do not matches the expected value.
        - The VRF provided do not matches the expected value.
  
Fixes #821 

# Checklist:

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my code
- [X] I have run pre-commit for code linting and typing (`pre-commit run`)
- [ ] I have made corresponding changes to the documentation
- [X] I have added tests that prove my fix is effective or that my feature works
- [X] New and existing unit tests pass locally with my changes (`tox -e testenv`)


Patch:
diff --git a/anta/input_models/snmp.py b/anta/input_models/snmp.py
index 680d617c..bd7350e4 100644
--- a/anta/input_models/snmp.py
+++ b/anta/input_models/snmp.py
@@ -5,9 +5,30 @@
 
 from __future__ import annotations
 
+from ipaddress import IPv4Address
+
 from pydantic import BaseModel, ConfigDict
 
-from anta.custom_types import SnmpEncryptionAlgorithm, SnmpHashingAlgorithm, SnmpVersion
+from anta.custom_types import Hostname, SnmpEncryptionAlgorithm, SnmpHashingAlgorithm, SnmpVersion
+
+
+class SnmpHost(BaseModel):
+    """Model for a SNMP host."""
+
+    model_config = ConfigDict(extra="forbid")
+    hostname: IPv4Address | Hostname
+    """IPv4 address or hostname of the SNMP notification host."""
+    vrf: str = "default"
+    """Optional VRF for SNMP hosts. If not provided, it defaults to `default`."""
+
+    def __str__(self) -> str:
+        """Return a human-readable string representation of the SnmpHost for reporting.
+
+        Examples
+        --------
+         - Host: 192.168.1.100  VRF: default
+        """
+        return f"Host: {self.hostname} VRF: {self.vrf}"
 
 
 class SnmpUser(BaseModel):


Test patch:
diff --git a/anta/tests/snmp.py b/anta/tests/snmp.py
index ef741043..910d5929 100644
--- a/anta/tests/snmp.py
+++ b/anta/tests/snmp.py
@@ -12,7 +12,7 @@ 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 SnmpUser
+from anta.input_models.snmp import SnmpHost, SnmpUser
 from anta.models import AntaCommand, AntaTest
 from anta.tools import get_value
 
@@ -344,6 +344,77 @@ class VerifySnmpErrorCounters(AntaTest):
             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 VerifySnmpHostLogging(AntaTest):
+    """Verifies SNMP logging configurations.
+
+    This test performs the following checks:
+
+     1. SNMP logging is enabled globally.
+     2. For each specified SNMP host:
+         - Host exists in configuration.
+         - Host's VRF assignment matches expected value.
+
+    Expected Results
+    ----------------
+    * Success: The test will pass if all of the following conditions are met:
+        - SNMP logging is enabled on the device.
+        - All specified hosts are configured with correct VRF assignments.
+    * Failure: The test will fail if any of the following conditions is met:
+        - SNMP logging is disabled on the device.
+        - SNMP host not found in configuration.
+        - Host's VRF assignment doesn't match expected value.
+
+    Examples
+    --------
+    ```yaml
+    anta.tests.snmp:
+      - VerifySnmpHostLogging:
+          hosts:
+            - hostname: 192.168.1.100
+              vrf: default
+            - hostname: 192.168.1.103
+              vrf: MGMT
+    ```
+    """
+
+    categories: ClassVar[list[str]] = ["snmp"]
+    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)]
+
+    class Input(AntaTest.Input):
+        """Input model for the VerifySnmpHostLogging test."""
+
+        hosts: list[SnmpHost]
+        """List of SNMP hosts."""
+
+    @AntaTest.anta_test
+    def test(self) -> None:
+        """Main test function for VerifySnmpHostLogging."""
+        self.result.is_success()
+
+        command_output = self.instance_commands[0].json_output.get("logging", {})
+        # If SNMP logging is disabled, test fails.
+        if not command_output.get("loggingEnabled"):
+            self.result.is_failure("SNMP logging is disabled")
+            return
+
+        host_details = command_output.get("hosts", {})
+
+        for host in self.inputs.hosts:
+            hostname = str(host.hostname)
+            vrf = host.vrf
+            actual_snmp_host = host_details.get(hostname, {})
+
+            # If SNMP host is not configured on the device, test fails.
+            if not actual_snmp_host:
+                self.result.is_failure(f"{host} - Not configured")
+                continue
+
+            # If VRF is not matches the expected value, test fails.
+            actual_vrf = "default" if (vrf_name := actual_snmp_host.get("vrf")) == "" else vrf_name
+            if actual_vrf != vrf:
+                self.result.is_failure(f"{host} - Incorrect VRF - Actual: {actual_vrf}")
+
+
 class VerifySnmpUser(AntaTest):
     """Verifies the SNMP user configurations.
 
diff --git a/examples/tests.yaml b/examples/tests.yaml
index db1d179d..387d3bb0 100644
--- a/examples/tests.yaml
+++ b/examples/tests.yaml
@@ -727,6 +727,13 @@ anta.tests.snmp:
       # Verifies the SNMP error counters.
       error_counters:
         - inVersionErrs
+  - VerifySnmpHostLogging:
+      # Verifies SNMP logging configurations.
+      hosts:
+        - hostname: 192.168.1.100
+          vrf: default
+        - hostname: 192.168.1.103
+          vrf: MGMT
   - VerifySnmpIPv4Acl:
       # Verifies if the SNMP agent has IPv4 ACL(s) configured.
       number: 3
diff --git a/tests/units/anta_tests/test_snmp.py b/tests/units/anta_tests/test_snmp.py
index d2eb6b87..ebbf9489 100644
--- a/tests/units/anta_tests/test_snmp.py
+++ b/tests/units/anta_tests/test_snmp.py
@@ -10,6 +10,7 @@ from typing import Any
 from anta.tests.snmp import (
     VerifySnmpContact,
     VerifySnmpErrorCounters,
+    VerifySnmpHostLogging,
     VerifySnmpIPv4Acl,
     VerifySnmpIPv6Acl,
     VerifySnmpLocation,
@@ -320,6 +321,57 @@ DATA: list[dict[str, Any]] = [
             ],
         },
     },
+    {
+        "name": "success",
+        "test": VerifySnmpHostLogging,
+        "eos_data": [
+            {
+                "logging": {
+                    "loggingEnabled": True,
+                    "hosts": {
+                        "192.168.1.100": {"port": 162, "vrf": ""},
+                        "192.168.1.101": {"port": 162, "vrf": "MGMT"},
+                        "snmp-server-01": {"port": 162, "vrf": "default"},
+                    },
+                }
+            }
+        ],
+        "inputs": {
+            "hosts": [
+                {"hostname": "192.168.1.100", "vrf": "default"},
+                {"hostname": "192.168.1.101", "vrf": "MGMT"},
+                {"hostname": "snmp-server-01", "vrf": "default"},
+            ]
+        },
+        "expected": {"result": "success"},
+    },
+    {
+        "name": "failure-logging-disabled",
+        "test": VerifySnmpHostLogging,
+        "eos_data": [{"logging": {"loggingEnabled": False}}],
+        "inputs": {"hosts": [{"hostname": "192.168.1.100", "vrf": "default"}, {"hostname": "192.168.1.101", "vrf": "MGMT"}]},
+        "expected": {"result": "failure", "messages": ["SNMP logging is disabled"]},
+    },
+    {
+        "name": "failure-mismatch-vrf",
+        "test": VerifySnmpHostLogging,
+        "eos_data": [{"logging": {"loggingEnabled": True, "hosts": {"192.168.1.100": {"port": 162, "vrf": "MGMT"}, "192.168.1.101": {"port": 162, "vrf": "Test"}}}}],
+        "inputs": {"hosts": [{"hostname": "192.168.1.100", "vrf": "default"}, {"hostname": "192.168.1.101", "vrf": "MGMT"}]},
+        "expected": {
+            "result": "failure",
+            "messages": ["Host: 192.168.1.100 VRF: default - Incorrect VRF - Actual: MGMT", "Host: 192.168.1.101 VRF: MGMT - Incorrect VRF - Actual: Test"],
+        },
+    },
+    {
+        "name": "failure-host-not-configured",
+        "test": VerifySnmpHostLogging,
+        "eos_data": [{"logging": {"loggingEnabled": True, "hosts": {"192.168.1.100": {"port": 162, "vrf": "MGMT"}, "192.168.1.103": {"port": 162, "vrf": "Test"}}}}],
+        "inputs": {"hosts": [{"hostname": "192.168.1.101", "vrf": "default"}, {"hostname": "192.168.1.102", "vrf": "MGMT"}]},
+        "expected": {
+            "result": "failure",
+            "messages": ["Host: 192.168.1.101 VRF: default - Not configured", "Host: 192.168.1.102 VRF: MGMT - Not configured"],
+        },
+    },
     {
         "name": "success",
         "test": VerifySnmpUser,