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 description:
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,