Skip to content

Commit

Permalink
Merge branch 'main' into issue_811
Browse files Browse the repository at this point in the history
  • Loading branch information
vitthalmagadum authored Dec 19, 2024
2 parents 224b1e7 + 6ee2c08 commit b091e2a
Showing 25 changed files with 590 additions and 280 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/code-testing.yml
Original file line number Diff line number Diff line change
@@ -119,6 +119,23 @@ jobs:
run: pip install tox tox-gh-actions
- name: "Run pytest via tox for ${{ matrix.python }}"
run: tox
test-python-windows:
name: Pytest on 3.12 for windows
runs-on: windows-2022
needs: [lint-python, type-python]
env:
# Required to prevent asyncssh to fail.
USERNAME: WindowsUser
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install dependencies
run: pip install tox tox-gh-actions
- name: Run pytest via tox for 3.12 on Windows
run: tox
test-documentation:
name: Build offline documentation for testing
runs-on: ubuntu-20.04
2 changes: 1 addition & 1 deletion anta/device.py
Original file line number Diff line number Diff line change
@@ -255,7 +255,7 @@ class AsyncEOSDevice(AntaDevice):
"""

def __init__(
def __init__( # noqa: PLR0913
self,
host: str,
username: str,
36 changes: 36 additions & 0 deletions anta/input_models/avt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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 AVT tests."""

from __future__ import annotations

from ipaddress import IPv4Address

from pydantic import BaseModel, ConfigDict


class AVTPath(BaseModel):
"""AVT (Adaptive Virtual Topology) model representing path details and associated information."""

model_config = ConfigDict(extra="forbid")
vrf: str = "default"
"""VRF context. Defaults to `default`."""
avt_name: str
"""The name of the Adaptive Virtual Topology (AVT)."""
destination: IPv4Address
"""The IPv4 address of the destination peer in the AVT."""
next_hop: IPv4Address
"""The IPv4 address of the next hop used to reach the AVT peer."""
path_type: str | None = None
"""Specifies the type of path for the AVT. If not specified, both types 'direct' and 'multihop' are considered."""

def __str__(self) -> str:
"""Return a human-readable string representation of the AVTPath for reporting.
Examples
--------
AVT CONTROL-PLANE-PROFILE VRF: default (Destination: 10.101.255.2, Next-hop: 10.101.255.1)
"""
return f"AVT {self.avt_name} VRF: {self.vrf} (Destination: {self.destination}, Next-hop: {self.next_hop})"
Empty file added anta/py.typed
Empty file.
2 changes: 1 addition & 1 deletion anta/reporter/csv_reporter.py
Original file line number Diff line number Diff line change
@@ -107,7 +107,7 @@ def generate(cls, results: ResultManager, csv_filename: pathlib.Path) -> None:
]

try:
with csv_filename.open(mode="w", encoding="utf-8") as csvfile:
with csv_filename.open(mode="w", encoding="utf-8", newline="") as csvfile:
csvwriter = csv.writer(
csvfile,
delimiter=",",
12 changes: 11 additions & 1 deletion anta/result_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
from collections import defaultdict
from functools import cached_property
from itertools import chain
from typing import Any

from anta.result_manager.models import AntaTestStatus, TestResult

@@ -89,6 +90,10 @@ def __init__(self) -> None:
If the status of the added test is error, the status is untouched and the
error_status is set to True.
"""
self.reset()

def reset(self) -> None:
"""Create or reset the attributes of the ResultManager instance."""
self._result_entries: list[TestResult] = []
self.status: AntaTestStatus = AntaTestStatus.UNSET
self.error_status = False
@@ -122,10 +127,15 @@ def results(self, value: list[TestResult]) -> None:
for result in value:
self.add(result)

@property
def dump(self) -> list[dict[str, Any]]:
"""Get a list of dictionary of the results."""
return [result.model_dump() for result in self._result_entries]

@property
def json(self) -> str:
"""Get a JSON representation of the results."""
return json.dumps([result.model_dump() for result in self._result_entries], indent=4)
return json.dumps(self.dump, indent=4)

@property
def sorted_category_stats(self) -> dict[str, CategoryStats]:
70 changes: 40 additions & 30 deletions anta/runner.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
import asyncio
import logging
import os
import resource
import sys
from collections import defaultdict
from typing import TYPE_CHECKING, Any

@@ -26,35 +26,38 @@
from anta.result_manager import ResultManager
from anta.result_manager.models import TestResult

logger = logging.getLogger(__name__)
if os.name == "posix":
import resource

DEFAULT_NOFILE = 16384
DEFAULT_NOFILE = 16384

def adjust_rlimit_nofile() -> tuple[int, int]:
"""Adjust the maximum number of open file descriptors for the ANTA process.
def adjust_rlimit_nofile() -> tuple[int, int]:
"""Adjust the maximum number of open file descriptors for the ANTA process.
The limit is set to the lower of the current hard limit and the value of the ANTA_NOFILE environment variable.
The limit is set to the lower of the current hard limit and the value of the ANTA_NOFILE environment variable.
If the `ANTA_NOFILE` environment variable is not set or is invalid, `DEFAULT_NOFILE` is used.
If the `ANTA_NOFILE` environment variable is not set or is invalid, `DEFAULT_NOFILE` is used.
Returns
-------
tuple[int, int]
The new soft and hard limits for open file descriptors.
"""
try:
nofile = int(os.environ.get("ANTA_NOFILE", DEFAULT_NOFILE))
except ValueError as exception:
logger.warning("The ANTA_NOFILE environment variable value is invalid: %s\nDefault to %s.", exc_to_str(exception), DEFAULT_NOFILE)
nofile = DEFAULT_NOFILE

limits = resource.getrlimit(resource.RLIMIT_NOFILE)
logger.debug("Initial limit numbers for open file descriptors for the current ANTA process: Soft Limit: %s | Hard Limit: %s", limits[0], limits[1])
nofile = min(limits[1], nofile)
logger.debug("Setting soft limit for open file descriptors for the current ANTA process to %s", nofile)
resource.setrlimit(resource.RLIMIT_NOFILE, (nofile, limits[1]))
return resource.getrlimit(resource.RLIMIT_NOFILE)

Returns
-------
tuple[int, int]
The new soft and hard limits for open file descriptors.
"""
try:
nofile = int(os.environ.get("ANTA_NOFILE", DEFAULT_NOFILE))
except ValueError as exception:
logger.warning("The ANTA_NOFILE environment variable value is invalid: %s\nDefault to %s.", exc_to_str(exception), DEFAULT_NOFILE)
nofile = DEFAULT_NOFILE

limits = resource.getrlimit(resource.RLIMIT_NOFILE)
logger.debug("Initial limit numbers for open file descriptors for the current ANTA process: Soft Limit: %s | Hard Limit: %s", limits[0], limits[1])
nofile = min(limits[1], nofile)
logger.debug("Setting soft limit for open file descriptors for the current ANTA process to %s", nofile)
resource.setrlimit(resource.RLIMIT_NOFILE, (nofile, limits[1]))
return resource.getrlimit(resource.RLIMIT_NOFILE)
logger = logging.getLogger(__name__)


def log_cache_statistics(devices: list[AntaDevice]) -> None:
@@ -167,7 +170,8 @@ def prepare_tests(

if total_test_count == 0:
msg = (
f"There are no tests{f' matching the tags {tags} ' if tags else ' '}to run in the current test catalog and device inventory, please verify your inputs."
f"There are no tests{f' matching the tags {tags} ' if tags else ' '}to run in the current "
"test catalog and device inventory, please verify your inputs."
)
logger.warning(msg)
return None
@@ -211,7 +215,7 @@ def get_coroutines(selected_tests: defaultdict[AntaDevice, set[AntaTestDefinitio


@cprofile()
async def main( # noqa: PLR0913
async def main(
manager: ResultManager,
inventory: AntaInventory,
catalog: AntaCatalog,
@@ -246,9 +250,6 @@ async def main( # noqa: PLR0913
dry_run
Build the list of coroutine to run and stop before test execution.
"""
# Adjust the maximum number of open file descriptors for the ANTA process
limits = adjust_rlimit_nofile()

if not catalog.tests:
logger.info("The list of tests is empty, exiting")
return
@@ -269,10 +270,19 @@ async def main( # noqa: PLR0913
"--- ANTA NRFU Run Information ---\n"
f"Number of devices: {len(inventory)} ({len(selected_inventory)} established)\n"
f"Total number of selected tests: {final_tests_count}\n"
f"Maximum number of open file descriptors for the current ANTA process: {limits[0]}\n"
"---------------------------------"
)

if os.name == "posix":
# Adjust the maximum number of open file descriptors for the ANTA process
limits = adjust_rlimit_nofile()
run_info += f"Maximum number of open file descriptors for the current ANTA process: {limits[0]}\n"
else:
# Running on non-Posix system, cannot manage the resource.
limits = (sys.maxsize, sys.maxsize)
run_info += "Running on a non-POSIX system, cannot adjust the maximum number of file descriptors.\n"

run_info += "---------------------------------"

logger.info(run_info)

if final_tests_count > limits[0]:
Loading

0 comments on commit b091e2a

Please sign in to comment.