anta/anta/tests/snmp.py
Daniel Baumann 3254dea030
Merging upstream version 1.4.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-15 09:34:30 +02:00

726 lines
29 KiB
Python

# Copyright (c) 2023-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
"""Module related to the EOS various SNMP tests."""
# Mypy does not understand AntaTest.Input typing
# mypy: disable-error-code=attr-defined
from __future__ import annotations
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 SnmpGroup, SnmpHost, SnmpSourceInterface, SnmpUser
from anta.models import AntaCommand, AntaTest
from anta.tools import get_value
if TYPE_CHECKING:
from anta.models import AntaTemplate
class VerifySnmpStatus(AntaTest):
"""Verifies if the SNMP agent is enabled.
Expected Results
----------------
* Success: The test will pass if the SNMP agent is enabled in the specified VRF.
* Failure: The test will fail if the SNMP agent is disabled in the specified VRF.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpStatus:
vrf: default
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpStatus test."""
vrf: str = "default"
"""The name of the VRF in which to check for the SNMP agent. Defaults to `default` VRF."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpStatus."""
self.result.is_success()
command_output = self.instance_commands[0].json_output
if not (command_output["enabled"] and self.inputs.vrf in command_output["vrfs"]["snmpVrfs"]):
self.result.is_failure(f"VRF: {self.inputs.vrf} - SNMP agent disabled")
class VerifySnmpIPv4Acl(AntaTest):
"""Verifies if the SNMP agent has IPv4 ACL(s) configured.
Expected Results
----------------
* Success: The test will pass if the SNMP agent has the provided number of IPv4 ACL(s) in the specified VRF.
* Failure: The test will fail if the SNMP agent has not the right number of IPv4 ACL(s) in the specified VRF.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpIPv4Acl:
number: 3
vrf: default
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp ipv4 access-list summary", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpIPv4Acl test."""
number: PositiveInteger
"""The number of expected IPv4 ACL(s)."""
vrf: str = "default"
"""The name of the VRF in which to check for the SNMP agent. Defaults to `default` VRF."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpIPv4Acl."""
self.result.is_success()
command_output = self.instance_commands[0].json_output
ipv4_acl_list = command_output["ipAclList"]["aclList"]
ipv4_acl_number = len(ipv4_acl_list)
if ipv4_acl_number != self.inputs.number:
self.result.is_failure(f"VRF: {self.inputs.vrf} - Incorrect SNMP IPv4 ACL(s) - Expected: {self.inputs.number} Actual: {ipv4_acl_number}")
return
not_configured_acl = [acl["name"] for acl in ipv4_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
if not_configured_acl:
self.result.is_failure(f"VRF: {self.inputs.vrf} - Following SNMP IPv4 ACL(s) not configured or active: {', '.join(not_configured_acl)}")
class VerifySnmpIPv6Acl(AntaTest):
"""Verifies if the SNMP agent has IPv6 ACL(s) configured.
Expected Results
----------------
* Success: The test will pass if the SNMP agent has the provided number of IPv6 ACL(s) in the specified VRF.
* Failure: The test will fail if the SNMP agent has not the right number of IPv6 ACL(s) in the specified VRF.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpIPv6Acl:
number: 3
vrf: default
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp ipv6 access-list summary", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpIPv6Acl test."""
number: PositiveInteger
"""The number of expected IPv6 ACL(s)."""
vrf: str = "default"
"""The name of the VRF in which to check for the SNMP agent. Defaults to `default` VRF."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpIPv6Acl."""
command_output = self.instance_commands[0].json_output
self.result.is_success()
ipv6_acl_list = command_output["ipv6AclList"]["aclList"]
ipv6_acl_number = len(ipv6_acl_list)
if ipv6_acl_number != self.inputs.number:
self.result.is_failure(f"VRF: {self.inputs.vrf} - Incorrect SNMP IPv6 ACL(s) - Expected: {self.inputs.number} Actual: {ipv6_acl_number}")
return
acl_not_configured = [acl["name"] for acl in ipv6_acl_list if self.inputs.vrf not in acl["configuredVrfs"] or self.inputs.vrf not in acl["activeVrfs"]]
if acl_not_configured:
self.result.is_failure(f"VRF: {self.inputs.vrf} - Following SNMP IPv6 ACL(s) not configured or active: {', '.join(acl_not_configured)}")
class VerifySnmpLocation(AntaTest):
"""Verifies the SNMP location of a device.
Expected Results
----------------
* Success: The test will pass if the SNMP location matches the provided input.
* Failure: The test will fail if the SNMP location does not match the provided input.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpLocation:
location: New York
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpLocation test."""
location: str
"""Expected SNMP location of the device."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpLocation."""
self.result.is_success()
# Verifies the SNMP location is configured.
if not (location := get_value(self.instance_commands[0].json_output, "location.location")):
self.result.is_failure("SNMP location is not configured")
return
# Verifies the expected SNMP location.
if location != self.inputs.location:
self.result.is_failure(f"Incorrect SNMP location - Expected: {self.inputs.location} Actual: {location}")
class VerifySnmpContact(AntaTest):
"""Verifies the SNMP contact of a device.
Expected Results
----------------
* Success: The test will pass if the SNMP contact matches the provided input.
* Failure: The test will fail if the SNMP contact does not match the provided input.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpContact:
contact: Jon@example.com
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpContact test."""
contact: str
"""Expected SNMP contact details of the device."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpContact."""
self.result.is_success()
# Verifies the SNMP contact is configured.
if not (contact := get_value(self.instance_commands[0].json_output, "contact.contact")):
self.result.is_failure("SNMP contact is not configured")
return
# Verifies the expected SNMP contact.
if contact != self.inputs.contact:
self.result.is_failure(f"Incorrect SNMP contact - Expected: {self.inputs.contact} Actual: {contact}")
class VerifySnmpPDUCounters(AntaTest):
"""Verifies the SNMP PDU counters.
By default, all SNMP PDU counters will be checked for any non-zero values.
An optional list of specific SNMP PDU(s) can be provided for granular testing.
Expected Results
----------------
* Success: The test will pass if the SNMP PDU counter(s) are non-zero/greater than zero.
* Failure: The test will fail if the SNMP PDU counter(s) are zero/None/Not Found.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpPDUCounters:
pdus:
- outTrapPdus
- inGetNextPdus
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpPDUCounters test."""
pdus: list[SnmpPdu] | None = None
"""Optional list of SNMP PDU counters to be verified. If not provided, test will verifies all PDU counters."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpPDUCounters."""
self.result.is_success()
snmp_pdus = self.inputs.pdus
command_output = self.instance_commands[0].json_output
# Verify SNMP PDU counters.
if not (pdu_counters := get_value(command_output, "counters")):
self.result.is_failure("SNMP counters not found")
return
# In case SNMP PDUs not provided, It will check all the update error counters.
if not snmp_pdus:
snmp_pdus = list(get_args(SnmpPdu))
failures = {pdu for pdu in snmp_pdus if (value := pdu_counters.get(pdu, "Not Found")) == "Not Found" or value == 0}
# Check if any failures
if failures:
self.result.is_failure(f"The following SNMP PDU counters are not found or have zero PDU counters: {', '.join(sorted(failures))}")
class VerifySnmpErrorCounters(AntaTest):
"""Verifies the SNMP error counters.
By default, all error counters will be checked for any non-zero values.
An optional list of specific error counters can be provided for granular testing.
Expected Results
----------------
* Success: The test will pass if the SNMP error counter(s) are zero/None.
* Failure: The test will fail if the SNMP error counter(s) are non-zero/not None/Not Found or is not configured.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpErrorCounters:
error_counters:
- inVersionErrs
- inBadCommunityNames
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpErrorCounters test."""
error_counters: list[SnmpErrorCounter] | None = None
"""Optional list of SNMP error counters to be verified. If not provided, test will verifies all error counters."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpErrorCounters."""
self.result.is_success()
error_counters = self.inputs.error_counters
command_output = self.instance_commands[0].json_output
# Verify SNMP PDU counters.
if not (snmp_counters := get_value(command_output, "counters")):
self.result.is_failure("SNMP counters not found")
return
# In case SNMP error counters not provided, It will check all the error counters.
if not error_counters:
error_counters = list(get_args(SnmpErrorCounter))
error_counters_not_ok = {counter for counter in error_counters if snmp_counters.get(counter)}
# Check if any failures
if error_counters_not_ok:
self.result.is_failure(f"The following SNMP error counters are not found or have non-zero error counters: {', '.join(sorted(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.
This test performs the following checks for each specified user:
1. User exists in SNMP configuration.
2. Group assignment is correct.
3. For SNMPv3 users only:
- Authentication type matches (if specified)
- Privacy type matches (if specified)
Expected Results
----------------
* Success: If all of the following conditions are met:
- All users exist with correct group assignments.
- SNMPv3 authentication and privacy types match specified values.
* Failure: If any of the following occur:
- User not found in SNMP configuration.
- Incorrect group assignment.
- For SNMPv3: Mismatched authentication or privacy types.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpUser:
snmp_users:
- username: test
group_name: test_group
version: v3
auth_type: MD5
priv_type: AES-128
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp user", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpUser test."""
snmp_users: list[SnmpUser]
"""List of SNMP users."""
@field_validator("snmp_users")
@classmethod
def validate_snmp_users(cls, snmp_users: list[SnmpUser]) -> list[SnmpUser]:
"""Validate that 'auth_type' or 'priv_type' field is provided in each SNMPv3 user."""
for user in snmp_users:
if user.version == "v3" and not (user.auth_type or user.priv_type):
msg = f"{user} 'auth_type' or 'priv_type' field is required with 'version: v3'"
raise ValueError(msg)
return snmp_users
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpUser."""
self.result.is_success()
for user in self.inputs.snmp_users:
# Verify SNMP user details.
if not (user_details := get_value(self.instance_commands[0].json_output, f"usersByVersion.{user.version}.users.{user.username}")):
self.result.is_failure(f"{user} - Not found")
continue
if user.group_name != (act_group := user_details.get("groupName", "Not Found")):
self.result.is_failure(f"{user} - Incorrect user group - Actual: {act_group}")
if user.version == "v3":
if user.auth_type and (act_auth_type := get_value(user_details, "v3Params.authType", "Not Found")) != user.auth_type:
self.result.is_failure(f"{user} - Incorrect authentication type - Expected: {user.auth_type} Actual: {act_auth_type}")
if user.priv_type and (act_encryption := get_value(user_details, "v3Params.privType", "Not Found")) != user.priv_type:
self.result.is_failure(f"{user} - Incorrect privacy type - Expected: {user.priv_type} Actual: {act_encryption}")
class VerifySnmpNotificationHost(AntaTest):
"""Verifies the SNMP notification host(s) (SNMP manager) configurations.
This test performs the following checks for each specified host:
1. Verifies that the SNMP host(s) is configured on the device.
2. Verifies that the notification type ("trap" or "inform") matches the expected value.
3. Ensures that UDP port provided matches the expected value.
4. Ensures the following depending on SNMP version:
- For SNMP version v1/v2c, a valid community string is set and matches the expected value.
- For SNMP version v3, a valid user field is set and matches the expected value.
Expected Results
----------------
* Success: The test will pass if all of the following conditions are met:
- The SNMP host(s) is configured on the device.
- The notification type ("trap" or "inform") and UDP port match the expected value.
- Ensures the following depending on SNMP version:
- For SNMP version v1/v2c, a community string is set and it matches the expected value.
- For SNMP version v3, a valid user field is set and matches the expected value.
* Failure: The test will fail if any of the following conditions is met:
- The SNMP host(s) is not configured on the device.
- The notification type ("trap" or "inform") or UDP port do not matches the expected value.
- Ensures the following depending on SNMP version:
- For SNMP version v1/v2c, a community string is not matches the expected value.
- For SNMP version v3, an user field is not matches the expected value.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpNotificationHost:
notification_hosts:
- hostname: spine
vrf: default
notification_type: trap
version: v1
udp_port: 162
community_string: public
- hostname: 192.168.1.100
vrf: default
notification_type: trap
version: v3
udp_port: 162
user: public
```
"""
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp notification host", revision=1)]
class Input(AntaTest.Input):
"""Input model for the VerifySnmpNotificationHost test."""
notification_hosts: list[SnmpHost]
"""List of SNMP host(s)."""
@field_validator("notification_hosts")
@classmethod
def validate_notification_hosts(cls, notification_hosts: list[SnmpHost]) -> list[SnmpHost]:
"""Validate that all required fields are provided in each SNMP Notification Host."""
for host in notification_hosts:
if host.version is None:
msg = f"{host}; 'version' field missing in the input"
raise ValueError(msg)
if host.version in ["v1", "v2c"] and host.community_string is None:
msg = f"{host} Version: {host.version}; 'community_string' field missing in the input"
raise ValueError(msg)
if host.version == "v3" and host.user is None:
msg = f"{host} Version: {host.version}; 'user' field missing in the input"
raise ValueError(msg)
return notification_hosts
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpNotificationHost."""
self.result.is_success()
# If SNMP is not configured, test fails.
if not (snmp_hosts := get_value(self.instance_commands[0].json_output, "hosts")):
self.result.is_failure("No SNMP host is configured")
return
for host in self.inputs.notification_hosts:
vrf = "" if host.vrf == "default" else host.vrf
hostname = str(host.hostname)
notification_type = host.notification_type
version = host.version
udp_port = host.udp_port
community_string = host.community_string
user = host.user
default_value = "Not Found"
host_details = next(
(host for host in snmp_hosts if (host.get("hostname") == hostname and host.get("protocolVersion") == version and host.get("vrf") == vrf)), None
)
# If expected SNMP host is not configured with the specified protocol version, test fails.
if not host_details:
self.result.is_failure(f"{host} Version: {version} - Not configured")
continue
# If actual notification type does not match the expected value, test fails.
if notification_type != (actual_notification_type := get_value(host_details, "notificationType", default_value)):
self.result.is_failure(f"{host} - Incorrect notification type - Expected: {notification_type} Actual: {actual_notification_type}")
# If actual UDP port does not match the expected value, test fails.
if udp_port != (actual_udp_port := get_value(host_details, "port", default_value)):
self.result.is_failure(f"{host} - Incorrect UDP port - Expected: {udp_port} Actual: {actual_udp_port}")
user_found = user != (actual_user := get_value(host_details, "v3Params.user", default_value))
version_user_check = (version == "v3", user_found)
# If SNMP protocol version is v1 or v2c and actual community string does not match the expected value, test fails.
if version in ["v1", "v2c"] and community_string != (actual_community_string := get_value(host_details, "v1v2cParams.communityString", default_value)):
self.result.is_failure(f"{host} Version: {version} - Incorrect community string - Expected: {community_string} Actual: {actual_community_string}")
# If SNMP protocol version is v3 and actual user does not match the expected value, test fails.
elif all(version_user_check):
self.result.is_failure(f"{host} Version: {version} - Incorrect user - Expected: {user} Actual: {actual_user}")
class VerifySnmpSourceInterface(AntaTest):
"""Verifies SNMP source interfaces.
This test performs the following checks:
1. Verifies that source interface(s) are configured for SNMP.
2. For each specified source interface:
- Interface is configured in the specified VRF.
Expected Results
----------------
* Success: The test will pass if the provided SNMP source interface(s) are configured in their specified VRF.
* Failure: The test will fail if any of the provided SNMP source interface(s) are NOT configured in their specified VRF.
Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpSourceInterface:
interfaces:
- interface: Ethernet1
vrf: default
- interface: Management0
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 VerifySnmpSourceInterface test."""
interfaces: list[SnmpSourceInterface]
"""List of source interfaces."""
@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpSourceInterface."""
self.result.is_success()
command_output = self.instance_commands[0].json_output.get("srcIntf", {})
if not (interface_output := command_output.get("sourceInterfaces")):
self.result.is_failure("SNMP source interface(s) not configured")
return
for interface_details in self.inputs.interfaces:
# If the source interface is not configured, or if it does not match the expected value, the test fails.
if not (actual_interface := interface_output.get(interface_details.vrf)):
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}")