Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(anta): Verify cvx status #930

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
efd94ae
ruff lint changes
sarunac Nov 8, 2024
d2dd63c
check for cvx server role and enabled
sarunac Nov 8, 2024
d3e2d35
start testing for enabled and cluster_mode
sarunac Nov 14, 2024
94cc001
Merge remote-tracking branch 'origin/main' into verifyCVXStatus
sarunac Nov 14, 2024
8396236
Merge remote-tracking branch 'origin/main' into verifyCVXStatus
sarunac Nov 14, 2024
0dc9b49
add cvx peer validation tests
sarunac Nov 18, 2024
4aa65a6
add test case for registration state and add example
sarunac Nov 18, 2024
1d381a1
Merge branch 'main' into verifyCVXStatus
sarunac Nov 18, 2024
aae913b
update code to reduce its Cognitive Complexity. add additional testfo…
sarunac Nov 19, 2024
e1d671b
Merge branch 'verifyCVXStatus' of https://github.com/sarunac/anta int…
sarunac Nov 19, 2024
cceba67
add test cases for coverage
sarunac Nov 19, 2024
20996bf
Merge branch 'main' into verifyCVXStatus
gmuloc Nov 28, 2024
8bd6eea
Merge branch 'main' into verifyCVXStatus
sarunac Nov 29, 2024
8b57c0c
address review board and update tests
sarunac Nov 29, 2024
fa29f29
Merge branch 'verifyCVXStatus' of https://github.com/sarunac/anta int…
sarunac Nov 29, 2024
060ec48
Update anta/tests/cvx.py
gmuloc Dec 2, 2024
a4adf76
Update anta/tests/cvx.py
gmuloc Dec 2, 2024
8a2f4c5
Merge branch 'main' into verifyCVXStatus
gmuloc Dec 2, 2024
936bd2e
Merge branch 'main' into verifyCVXStatus
sarunac Dec 2, 2024
6e9c0ab
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 2, 2024
0e8d585
Merge branch 'main' into verifyCVXStatus
sarunac Dec 3, 2024
9b591b7
update docstring to cover different conditions needed
sarunac Dec 3, 2024
d428d51
address review comments
sarunac Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions anta/input_models/cvx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2023-2024 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 containing input models for CVX tests."""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel

from anta.custom_types import Hostname


class CVXPeers(BaseModel):
"""Model for a CVX Cluster Peer."""

peer_name: Hostname
registration_state: Literal["Connecting", "Connected", "Registration error", "Registration complete", "Unexpected peer state"] = "Registration complete"
145 changes: 144 additions & 1 deletion anta/tests/cvx.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@
# mypy: disable-error-code=attr-defined
from __future__ import annotations

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

from anta.models import AntaCommand, AntaTest

if TYPE_CHECKING:
from anta.models import AntaTemplate
from anta.input_models.cvx import CVXPeers
from anta.tools import get_value


class Peer(TypedDict):
"""Class for holding CVX Peer information."""

peer_name: str
registration_state: str
sarunac marked this conversation as resolved.
Show resolved Hide resolved


class VerifyMcsClientMounts(AntaTest):
Expand Down Expand Up @@ -87,3 +96,137 @@ def test(self) -> None:
cluster_status = command_output["clusterStatus"]
if (cluster_state := cluster_status.get("enabled")) != self.inputs.enabled:
self.result.is_failure(f"Management CVX status is not valid: {cluster_state}")


class VerifyCVXClusterStatus(AntaTest):
"""Verifies the CVX Server Cluster status.

Expected Results
----------------
* Success: The test will pass if the CVX Server Cluster is enabled.
* Failure: The test will fail if any of the following conditions are met:
- If the CVX Status is disabled
- If the peers are not in "Registration ok" state

Examples
--------
```yaml
anta.tests.cvx:
- VerifyCVXClusterStatus:
enabled: true
cluster_mode: true
role: Master
sarunac marked this conversation as resolved.
Show resolved Hide resolved
peer_status:
- peer_name : cvx-red-2
registration_state: Registration complete
sarunac marked this conversation as resolved.
Show resolved Hide resolved
- peer_name: cvx-red-3
registration_state: Registration complete
```
"""

name = "VerifyCVXClusterStatus"
description = "Verifies the CVX Cluster Status."
categories: ClassVar[list[str]] = ["cvx"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show cvx", revision=1)]

class Input(AntaTest.Input):
"""Input model for the VerifyCVXClusterStatus test."""

enabled: bool
"""Whether management CVX must be enabled (True) or disabled (False)."""
cluster_mode: bool
role: Literal["Master", "Standby", "Disconnected"] = "Master"
peer_status: list[CVXPeers]
CVXPeers: ClassVar[type[CVXPeers]] = CVXPeers
gmuloc marked this conversation as resolved.
Show resolved Hide resolved

@AntaTest.anta_test
def test(self) -> None:
"""Run the main test for VerifyCVXClusterStatus."""
command_output = self.instance_commands[0].json_output
self.result.is_success()

# Validate cluster status
if not self._validate_cluster_status(command_output):
return

# Validate cluster mode and enabled status
if not self._validate_cluster_mode_and_enabled(command_output):
return

# Validate peer status
if not self._validate_peer_status(command_output.get("clusterStatus")):
return
gmuloc marked this conversation as resolved.
Show resolved Hide resolved

def _validate_cluster_status(self, command_output: dict[str, Any]) -> bool:
"""Check if the cluster status is available."""
cluster_status = command_output.get("clusterStatus")
if not cluster_status:
self.result.is_failure("CVX Server is not a cluster")
return False
return True

def _validate_cluster_mode_and_enabled(self, command_output: dict[str, Any]) -> bool:
"""Check if the cluster mode and enabled status are correct."""
if not command_output.get("enabled"):
self.result.is_failure("CVX Server status is not enabled")
return False

cluster_status = command_output.get("clusterStatus")
if not (command_output.get("clusterMode") and cluster_status):
self.result.is_failure("CVX Server is not a cluster")
return False
return True
sarunac marked this conversation as resolved.
Show resolved Hide resolved

def _validate_peer_status(self, cluster_status: dict[str, Any] | None) -> bool:
"""Check peer statuses in the cluster."""
if cluster_status is None:
self.result.is_failure("Cluster status is missing")
return False
sarunac marked this conversation as resolved.
Show resolved Hide resolved

peer_cluster = cluster_status.get("peerStatus")

# Check if peer_cluster is None or not sized
if not peer_cluster or not isinstance(peer_cluster, dict):
sarunac marked this conversation as resolved.
Show resolved Hide resolved
self.result.is_failure("Peer status data is invalid")
return False

# Check peer count
if len(peer_cluster) != len(self.inputs.peer_status):
self.result.is_failure("Unexpected number of peers")
sarunac marked this conversation as resolved.
Show resolved Hide resolved

# Check cluster role
cluster_role = cluster_status.get("role")
if cluster_role != self.inputs.role:
self.result.is_failure(f"CVX Role is not valid: {cluster_role}")
return False
sarunac marked this conversation as resolved.
Show resolved Hide resolved

# Check each peer
for peer in self.inputs.peer_status:
if not self._validate_individual_peer(peer, peer_cluster):
continue
return True
sarunac marked this conversation as resolved.
Show resolved Hide resolved

def _validate_individual_peer(self, peer: Peer, peer_cluster: dict[str, Any]) -> bool:
"""Check an individual peer in the cluster.

Args:
peer (Peer): The peer object containing expected peer details.
peer_cluster (dict[str, Any]): The dictionary representing the peer statuses in the cluster.

Returns
-------
bool: True if the peer is valid, False otherwise.
sarunac marked this conversation as resolved.
Show resolved Hide resolved
"""
# Retrieve the peer status from the peer cluster
eos_peer_status = get_value(peer_cluster, peer.peer_name, separator="..")
if eos_peer_status is None:
self.result.is_failure(f"{peer.peer_name} is not present")
return False

# Validate the registration state of the peer
peer_reg_state = eos_peer_status.get("registrationState")
if peer_reg_state != peer.registration_state:
self.result.is_failure(f"{peer.peer_name} registration state is not complete: {peer_reg_state}")
return False

return True
9 changes: 9 additions & 0 deletions examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ anta.tests.cvx:
- VerifyMcsClientMounts:
- VerifyManagementCVX:
enabled: true
- VerifyCVXClusterStatus:
enabled: true
cluster_mode: true
role: Master
peer_status:
- peer_name : cvx-red-2
registration_state: Registration complete
sarunac marked this conversation as resolved.
Show resolved Hide resolved
- peer_name: cvx-red-3
registration_state: Registration complete

anta.tests.connectivity:
- VerifyReachability:
Expand Down
172 changes: 171 additions & 1 deletion tests/units/anta_tests/test_cvx.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from typing import Any

from anta.tests.cvx import VerifyManagementCVX, VerifyMcsClientMounts
from anta.tests.cvx import VerifyCVXClusterStatus, VerifyManagementCVX, VerifyMcsClientMounts
from tests.units.anta_tests import test

DATA: list[dict[str, Any]] = [
Expand Down Expand Up @@ -146,4 +146,174 @@
"inputs": {"enabled": False},
"expected": {"result": "failure", "messages": ["Management CVX status is not valid: None"]},
},
{
"name": "success-all",
"test": VerifyCVXClusterStatus,
"eos_data": [
{
"enabled": True,
"clusterMode": True,
"clusterStatus": {
"role": "Master",
"peerStatus": {
"cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration complete"},
"cvx-red-3": {"peerName": "cvx-red-3", "registrationState": "Registration complete"},
},
},
}
],
"inputs": {
"enabled": True,
"cluster_mode": True,
"role": "Master",
"peer_status": [
{"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
],
},
"expected": {"result": "success"},
},
{
"name": "failure-invalid-role",
"test": VerifyCVXClusterStatus,
"eos_data": [
{
"enabled": True,
"clusterMode": True,
"clusterStatus": {
"role": "Standby",
"peerStatus": {
"cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration complete"},
"cvx-red-3": {"peerName": "cvx-red-3", "registrationState": "Registration complete"},
},
},
}
],
"inputs": {
"enabled": True,
"cluster_mode": True,
"role": "Master",
"peer_status": [
{"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
],
},
"expected": {"result": "failure", "messages": ["CVX Role is not valid: Standby"]},
},
{
"name": "failure-cvx-enabled",
"test": VerifyCVXClusterStatus,
"eos_data": [
{
"enabled": False,
"clusterMode": True,
"clusterStatus": {
"role": "Master",
"peerStatus": {},
},
}
],
"inputs": {
"enabled": True,
"cluster_mode": True,
"role": "Master",
"peer_status": [],
},
"expected": {"result": "failure", "messages": ["CVX Server status is not enabled"]},
},
{
"name": "failure-cluster-enabled",
"test": VerifyCVXClusterStatus,
"eos_data": [
{
"enabled": True,
"clusterMode": False,
"clusterStatus": {},
}
],
"inputs": {
"enabled": True,
"cluster_mode": True,
"role": "Master",
"peer_status": [],
},
"expected": {"result": "failure", "messages": ["CVX Server is not a cluster"]},
},
{
"name": "failure-missing-peers",
"test": VerifyCVXClusterStatus,
"eos_data": [
{
"enabled": True,
"clusterMode": True,
"clusterStatus": {
"role": "Master",
"peerStatus": {
"cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration complete"},
},
},
}
],
"inputs": {
"enabled": True,
"cluster_mode": True,
"role": "Master",
"peer_status": [
{"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
],
},
"expected": {"result": "failure", "messages": ["Unexpected number of peers", "cvx-red-3 is not present"]},
},
{
"name": "failure-invalid-peers",
"test": VerifyCVXClusterStatus,
"eos_data": [
{
"enabled": True,
"clusterMode": True,
"clusterStatus": {
"role": "Master",
"peerStatus": {},
},
}
],
"inputs": {
"enabled": True,
"cluster_mode": True,
"role": "Master",
"peer_status": [
{"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
],
},
"expected": {"result": "failure", "messages": ["Peer status data is invalid"]},
},
{
"name": "failure-registration-error",
"test": VerifyCVXClusterStatus,
"eos_data": [
{
"enabled": True,
"clusterMode": True,
"clusterStatus": {
"role": "Master",
"peerStatus": {
"cvx-red-2": {"peerName": "cvx-red-2", "registrationState": "Registration error"},
"cvx-red-3": {"peerName": "cvx-red-3", "registrationState": "Registration complete"},
},
},
}
],
"inputs": {
"enabled": True,
"cluster_mode": True,
"role": "Master",
"peer_status": [
{"peer_name": "cvx-red-2", "registrationState": "Registration complete"},
{"peer_name": "cvx-red-3", "registrationState": "Registration complete"},
],
},
"expected": {"result": "failure", "messages": ["cvx-red-2 registration state is not complete: Registration error"]},
},
]
Loading