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

chore: import aioeapi module #676

Merged
merged 16 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
files: ^(anta|docs|scripts|tests)/
files: ^(anta|docs|scripts|tests|asynceapi)/

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
28 changes: 28 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
ANTA Project

Copyright 2024 Arista Networks

This product includes software developed at Arista Networks.

------------------------------------------------------------------------

This product includes software developed by contributors from the
following projects, which are also licensed under the Apache License, Version 2.0:

1. aio-eapi
- Copyright 2024 Jeremy Schulman
- URL: https://github.com/jeremyschulman/aio-eapi

------------------------------------------------------------------------

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
106 changes: 0 additions & 106 deletions anta/aioeapi.py

This file was deleted.

8 changes: 4 additions & 4 deletions anta/cli/exec/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from yaml import safe_load

from anta.cli.console import console
from anta.cli.exec.utils import clear_counters_utils, collect_commands, collect_scheduled_show_tech
from anta.cli.exec import utils
from anta.cli.utils import inventory_options

if TYPE_CHECKING:
Expand All @@ -29,7 +29,7 @@
@inventory_options
def clear_counters(inventory: AntaInventory, tags: set[str] | None) -> None:
"""Clear counter statistics on EOS devices."""
asyncio.run(clear_counters_utils(inventory, tags=tags))
asyncio.run(utils.clear_counters(inventory, tags=tags))


@click.command()
Expand Down Expand Up @@ -62,7 +62,7 @@ def snapshot(inventory: AntaInventory, tags: set[str] | None, commands_list: Pat
except FileNotFoundError:
logger.error("Error reading %s", commands_list)
sys.exit(1)
asyncio.run(collect_commands(inventory, eos_commands, output, tags=tags))
asyncio.run(utils.collect_commands(inventory, eos_commands, output, tags=tags))


@click.command()
Expand Down Expand Up @@ -98,4 +98,4 @@ def collect_tech_support(
configure: bool,
) -> None:
"""Collect scheduled tech-support from EOS devices."""
asyncio.run(collect_scheduled_show_tech(inventory, output, configure=configure, tags=tags, latest=latest))
asyncio.run(utils.collect_show_tech(inventory, output, configure=configure, tags=tags, latest=latest))
6 changes: 3 additions & 3 deletions anta/cli/exec/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
from pathlib import Path
from typing import TYPE_CHECKING, Literal

from aioeapi import EapiCommandError
from click.exceptions import UsageError
from httpx import ConnectError, HTTPError

from anta.custom_types import REGEXP_PATH_MARKERS
from anta.device import AntaDevice, AsyncEOSDevice
from anta.models import AntaCommand
from asynceapi import EapiCommandError

if TYPE_CHECKING:
from anta.inventory import AntaInventory
Expand All @@ -30,7 +30,7 @@
logger = logging.getLogger(__name__)


async def clear_counters_utils(anta_inventory: AntaInventory, tags: set[str] | None = None) -> None:
async def clear_counters(anta_inventory: AntaInventory, tags: set[str] | None = None) -> None:
"""Clear counters."""

async def clear(dev: AntaDevice) -> None:
Expand Down Expand Up @@ -95,7 +95,7 @@ async def collect(dev: AntaDevice, command: str, outformat: Literal["json", "tex
logger.error("Error when collecting commands: %s", str(r))


async def collect_scheduled_show_tech(inv: AntaInventory, root_dir: Path, *, configure: bool, tags: set[str] | None = None, latest: int | None = None) -> None:
async def collect_show_tech(inv: AntaInventory, root_dir: Path, *, configure: bool, tags: set[str] | None = None, latest: int | None = None) -> None:
"""Collect scheduled show-tech on devices."""

async def collect(device: AntaDevice) -> None:
Expand Down
43 changes: 23 additions & 20 deletions anta/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from asyncssh import SSHClientConnection, SSHClientConnectionOptions
from httpx import ConnectError, HTTPError, TimeoutException

from anta import __DEBUG__, aioeapi
import asynceapi
from anta import __DEBUG__
from anta.logger import anta_log_exception, exc_to_str
from anta.models import AntaCommand

Expand Down Expand Up @@ -116,7 +117,7 @@ def __rich_repr__(self) -> Iterator[tuple[str, Any]]:
yield "disable_cache", self.cache is None

@abstractmethod
async def _collect(self, command: AntaCommand) -> None:
async def _collect(self, command: AntaCommand, *, collection_id: str | None = None) -> None:
"""Collect device command output.

This abstract coroutine can be used to implement any command collection method
Expand All @@ -131,11 +132,11 @@ async def _collect(self, command: AntaCommand) -> None:

Args:
----
command: the command to collect

command: The command to collect.
collection_id: An identifier used to build the eAPI request ID.
"""

async def collect(self, command: AntaCommand) -> None:
async def collect(self, command: AntaCommand, *, collection_id: str | None = None) -> None:
"""Collect the output for a specified command.

When caching is activated on both the device and the command,
Expand All @@ -148,8 +149,8 @@ async def collect(self, command: AntaCommand) -> None:

Args:
----
command (AntaCommand): The command to process.

command: The command to collect.
collection_id: An identifier used to build the eAPI request ID.
"""
# Need to ignore pylint no-member as Cache is a proxy class and pylint is not smart enough
# https://github.com/pylint-dev/pylint/issues/7258
Expand All @@ -161,20 +162,20 @@ async def collect(self, command: AntaCommand) -> None:
logger.debug("Cache hit for %s on %s", command.command, self.name)
command.output = cached_output
else:
await self._collect(command=command)
await self._collect(command=command, collection_id=collection_id)
await self.cache.set(command.uid, command.output) # pylint: disable=no-member
else:
await self._collect(command=command)
await self._collect(command=command, collection_id=collection_id)

async def collect_commands(self, commands: list[AntaCommand]) -> None:
async def collect_commands(self, commands: list[AntaCommand], *, collection_id: str | None = None) -> None:
"""Collect multiple commands.

Args:
----
commands: the commands to collect

commands: The commands to collect.
collection_id: An identifier used to build the eAPI request ID.
"""
await asyncio.gather(*(self.collect(command=command) for command in commands))
await asyncio.gather(*(self.collect(command=command, collection_id=collection_id) for command in commands))

@abstractmethod
async def refresh(self) -> None:
Expand Down Expand Up @@ -270,7 +271,7 @@ def __init__(
raise ValueError(message)
self.enable = enable
self._enable_password = enable_password
self._session: aioeapi.Device = aioeapi.Device(host=host, port=port, username=username, password=password, proto=proto, timeout=timeout)
self._session: asynceapi.Device = asynceapi.Device(host=host, port=port, username=username, password=password, proto=proto, timeout=timeout)
ssh_params: dict[str, Any] = {}
if insecure:
ssh_params["known_hosts"] = None
Expand Down Expand Up @@ -305,7 +306,7 @@ def _keys(self) -> tuple[Any, ...]:
"""
return (self._session.host, self._session.port)

async def _collect(self, command: AntaCommand) -> None: # noqa: C901 function is too complex - because of many required except blocks
async def _collect(self, command: AntaCommand, *, collection_id: str | None = None) -> None: # noqa: C901 function is too complex - because of many required except blocks #pylint: disable=line-too-long
"""Collect device command output from EOS using aio-eapi.

Supports outformat `json` and `text` as output structure.
Expand All @@ -314,9 +315,10 @@ async def _collect(self, command: AntaCommand) -> None: # noqa: C901 function

Args:
----
command: the AntaCommand to collect.
command: The command to collect.
collection_id: An identifier used to build the eAPI request ID.
"""
commands: list[dict[str, Any]] = []
commands: list[dict[str, str | int]] = []
if self.enable and self._enable_password is not None:
commands.append(
{
Expand All @@ -329,14 +331,15 @@ async def _collect(self, command: AntaCommand) -> None: # noqa: C901 function
commands.append({"cmd": "enable"})
commands += [{"cmd": command.command, "revision": command.revision}] if command.revision else [{"cmd": command.command}]
try:
response: list[dict[str, Any]] = await self._session.cli(
response: list[dict[str, Any] | str] = await self._session.cli(
commands=commands,
ofmt=command.ofmt,
version=command.version,
)
req_id=f"ANTA-{collection_id}-{id(command)}" if collection_id else f"ANTA-{id(command)}",
) # type: ignore[assignment] # multiple commands returns a list
# Do not keep response of 'enable' command
command.output = response[-1]
except aioeapi.EapiCommandError as e:
except asynceapi.EapiCommandError as e:
# This block catches exceptions related to EOS issuing an error.
command.errors = e.errors
if command.requires_privileges:
Expand Down
2 changes: 1 addition & 1 deletion anta/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ async def collect(self) -> None:
"""Collect outputs of all commands of this test class from the device of this test instance."""
try:
if self.blocked is False:
await self.device.collect_commands(self.instance_commands)
await self.device.collect_commands(self.instance_commands, collection_id=self.name)
except Exception as e: # pylint: disable=broad-exception-caught
# device._collect() is user-defined code.
# We need to catch everything if we want the AntaTest object
Expand Down
12 changes: 12 additions & 0 deletions asynceapi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (c) 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.
# Initially written by Jeremy Schulman at https://github.com/jeremyschulman/aio-eapi
gmuloc marked this conversation as resolved.
Show resolved Hide resolved

"""Arista EOS eAPI asyncio client."""

from .config_session import SessionConfig
from .device import Device
from .errors import EapiCommandError

__all__ = ["Device", "SessionConfig", "EapiCommandError"]
Loading
Loading