Merging upstream version 0.15.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-05 11:39:50 +01:00
parent bfebc2a0f4
commit 0a0cb7f4fd
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
103 changed files with 79620 additions and 742 deletions

View file

@ -19,14 +19,31 @@ ANTA is Python framework that automates tests for Arista devices.
- Automate NRFU (Network Ready For Use) test on a preproduction network
- Automate tests on a live network (periodically or on demand)
- ANTA can be used with:
- The [ANTA CLI](cli/overview.md)
- As a [Python library](advanced_usages/as-python-lib.md) in your own application
- The [ANTA CLI](cli/overview.md)
![anta nrfu](https://raw.githubusercontent.com/arista-netdevops-community/anta/main/docs/imgs/anta-nrfu.svg)
## Install ANTA library
The library will **NOT** install the necessary dependencies for the CLI.
```bash
# Install ANTA CLI
$ pip install anta
# Install ANTA as a library
pip install anta
```
## Install ANTA CLI
If you plan to use ANTA only as a CLI tool you can use `pipx` to install it.
[`pipx`](https://pipx.pypa.io/stable/) is a tool to install and run python applications in isolated environments. Refer to `pipx` instructions to install on your system.
`pipx` installs ANTA in an isolated python environment and makes it available globally.
**This is not recommended if you plan to contribute to ANTA**
```bash
# Install ANTA CLI with pipx
$ pipx install anta[cli]
# Run ANTA CLI
$ anta --help
@ -52,8 +69,11 @@ Commands:
nrfu Run ANTA tests on devices
```
> [!WARNING]
> The ANTA CLI options have changed after version 0.11 and have moved away from the top level `anta` and are now required at their respective commands (e.g. `anta nrfu`). This breaking change occurs after users feedback on making the CLI more intuitive. This change should not affect user experience when using environment variables.
You can also still choose to install it with directly with `pip`:
```bash
$ pip install anta[cli]
```
## Documentation
@ -65,4 +85,6 @@ Contributions are welcome. Please refer to the [contribution guide](contribution
## Credits
Thank you to [Jeremy Schulman](https://github.com/jeremyschulman) for [aio-eapi](https://github.com/jeremyschulman/aio-eapi/tree/main/aioeapi).
Thank you to [Angélique Phillipps](https://github.com/aphillipps), [Colin MacGiollaEáin](https://github.com/colinmacgiolla), [Khelil Sator](https://github.com/ksator), [Matthieu Tache](https://github.com/mtache), [Onur Gashi](https://github.com/onurgashi), [Paul Lavelle](https://github.com/paullavelle), [Guillaume Mulocher](https://github.com/gmuloc) and [Thomas Grimonet](https://github.com/titom73) for their contributions and guidances.

View file

@ -55,7 +55,9 @@ class VerifyTemperature(AntaTest):
[AntaTest](../api/models.md#anta.models.AntaTest) also provide more advanced capabilities like [AntaCommand](../api/models.md#anta.models.AntaCommand) templating using the [AntaTemplate](../api/models.md#anta.models.AntaTemplate) class or test inputs definition and validation using [AntaTest.Input](../api/models.md#anta.models.AntaTest.Input) [pydantic](https://docs.pydantic.dev/latest/) model. This will be discussed in the sections below.
## [AntaTest](../api/models.md#anta.models.AntaTest) structure
## AntaTest structure
Full AntaTest API documentation is available in the [API documentation section](../api/models.md#anta.models.AntaTest)
### Class Attributes
@ -98,7 +100,9 @@ class VerifyTemperature(AntaTest):
The base definition of [AntaTest.Input](../api/models.md#anta.models.AntaTest.Input) provides common test inputs for all [AntaTest](../api/models.md#anta.models.AntaTest) instances:
#### [Input](../api/models.md#anta.models.AntaTest.Input) model
#### Input model
Full `Input` model documentation is available in [API documentation section](../api/models.md#anta.models.AntaTest.Input)
::: anta.models.AntaTest.Input
options:
@ -114,7 +118,9 @@ The base definition of [AntaTest.Input](../api/models.md#anta.models.AntaTest.In
show_root_toc_entry: false
heading_level: 10
#### [ResultOverwrite](../api/models.md#anta.models.AntaTest.Input.ResultOverwrite) model
#### ResultOverwrite model
Full `ResultOverwrite` model documentation is available in [API documentation section](../api/models.md#anta.models.AntaTest.Input.ResultOverwrite)
::: anta.models.AntaTest.Input.ResultOverwrite
options:

9
docs/api/runner.md Normal file
View file

@ -0,0 +1,9 @@
<!--
~ 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.
-->
### ::: anta.runner
options:
filters: ["!^_[^_]", "!__str__"]

20
docs/api/tests.avt.md Normal file
View file

@ -0,0 +1,20 @@
---
anta_title: ANTA catalog for Adaptive Virtual Topology (AVT) tests
---
<!--
~ 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.
-->
::: anta.tests.avt
options:
show_root_heading: false
show_root_toc_entry: false
show_bases: false
merge_init_into_class: false
anta_hide_test_module_description: true
show_labels: true
filters:
- "!test"
- "!render"

View file

@ -13,6 +13,7 @@ This section describes all the available tests provided by the ANTA package.
Here are the tests that we currently provide:
- [AAA](tests.aaa.md)
- [Adaptive Virtual Topology](tests.avt.md)
- [BFD](tests.bfd.md)
- [Configuration](tests.configuration.md)
- [Connectivity](tests.connectivity.md)
@ -26,6 +27,7 @@ Here are the tests that we currently provide:
- [Multicast](tests.multicast.md)
- [Profiles](tests.profiles.md)
- [PTP](tests.ptp.md)
- [Router Path Selection](tests.path_selection.md)
- [Routing Generic](tests.routing.generic.md)
- [Routing BGP](tests.routing.bgp.md)
- [Routing OSPF](tests.routing.ospf.md)

View file

@ -0,0 +1,20 @@
---
anta_title: ANTA catalog for Router path-selection tests
---
<!--
~ 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.
-->
::: anta.tests.path_selection
options:
show_root_heading: false
show_root_toc_entry: false
show_bases: false
merge_init_into_class: false
anta_hide_test_module_description: true
show_labels: true
filters:
- "!test"
- "!render"

View file

@ -0,0 +1,20 @@
---
anta_title: ANTA catalog for IS-IS tests
---
<!--
~ 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.
-->
::: anta.tests.routing.isis
options:
show_root_heading: false
show_root_toc_entry: false
show_bases: false
merge_init_into_class: false
anta_hide_test_module_description: true
show_labels: true
filters:
- "!test"
- "!render"

View file

@ -14,17 +14,28 @@ In large setups, it might be beneficial to construct your inventory based on you
$ anta get from-ansible --help
Usage: anta get from-ansible [OPTIONS]
Build ANTA inventory from an ansible inventory YAML file
Build ANTA inventory from an ansible inventory YAML file.
NOTE: This command does not support inline vaulted variables. Make sure to
comment them out.
Options:
-g, --ansible-group TEXT Ansible group to filter
--ansible-inventory FILENAME
Path to your ansible inventory file to read
-o, --output FILENAME Path to save inventory file
-d, --inventory-directory PATH Directory to save inventory file
--help Show this message and exit.
-o, --output FILE Path to save inventory file [env var:
ANTA_INVENTORY; required]
--overwrite Do not prompt when overriding current inventory
[env var: ANTA_GET_FROM_ANSIBLE_OVERWRITE]
-g, --ansible-group TEXT Ansible group to filter
--ansible-inventory FILE Path to your ansible inventory file to read
[required]
--help Show this message and exit.
```
!!! warning
`anta get from-ansible` does not support inline vaulted variables, comment them out to generate your inventory.
If the vaulted variable is necessary to build the inventory (e.g. `ansible_host`), it needs to be unvaulted for `from-ansible` command to work."
The output is an inventory where the name of the container is added as a tag for each host:
```yaml

View file

@ -173,7 +173,7 @@ The `--output` option allows you to choose the path where the final report will
```bash
anta nrfu --tags LEAF tpl-report --template ./custom_template.j2
```
[![anta nrfu json results](../imgs/anta-nrfu-tpl-report-output.png){ loading=lazy width="1600" }](../imgs/anta-nrfu-tpl-report-output.png)
[![anta nrfu tpl_resultss](../imgs/anta-nrfu-tpl-report-output.png){ loading=lazy width="1600" }](../imgs/anta-nrfu-tpl-report-output.png)
The template `./custom_template.j2` is a simple Jinja2 template:
@ -200,3 +200,9 @@ cat nrfu-tpl-report.txt
* VerifyMlagConfigSanity is [green]SUCCESS[/green] for DC1-LEAF1A
* VerifyMlagReloadDelay is [green]SUCCESS[/green] for DC1-LEAF1A
```
## Dry-run mode
It is possible to run `anta nrfu --dry-run` to execute ANTA up to the point where it should communicate with the network to execute the tests. When using `--dry-run`, all inventory devices are assumed to be online. This can be useful to check how many tests would be run using the catalog and inventory.
[![anta nrfu dry_run](../imgs/anta_nrfu___dry_run.svg){ loading=lazy width="1600" }](../imgs/anta_nrfu___dry_run.svg)

View file

@ -12,9 +12,6 @@ ANTA can also be used as a Python library, allowing you to build your own tools
To start using the ANTA CLI, open your terminal and type `anta`.
!!! warning
The ANTA CLI options have changed after version 0.11 and have moved away from the top level `anta` and are now required at their respective commands (e.g. `anta nrfu`). This breaking change occurs after users feedback on making the CLI more intuitive. This change should not affect user experience when using environment variables.
## Invoking ANTA CLI
```bash

View file

@ -21,12 +21,14 @@ $ cd anta
# Install ANTA in editable mode and its development tools
$ pip install -e .[dev]
# To also install the CLI
$ pip install -e .[dev,cli]
# Verify installation
$ pip list -e
Package Version Editable project location
------- ------- -------------------------
anta 0.14.0 /mnt/lab/projects/anta
anta 0.15.0 /mnt/lab/projects/anta
```
Then, [`tox`](https://tox.wiki/) is configured with few environments to run CI locally:
@ -91,17 +93,20 @@ All submodule should have its own pytest section under `tests/units/anta_tests/<
The Python modules in the `tests/units/anta_tests` folder define test parameters for AntaTest subclasses unit tests.
A generic test function is written for all unit tests in `tests.lib.anta` module.
The `pytest_generate_tests` function definition in `conftest.py` is called during test collection.
The `pytest_generate_tests` function will parametrize the generic test function based on the `DATA` data structure defined in `tests.units.anta_tests` modules.
See https://docs.pytest.org/en/7.3.x/how-to/parametrize.html#basic-pytest-generate-tests-example
The `DATA` structure is a list of dictionaries used to parametrize the test.
The list elements have the following keys:
- `name` (str): Test name as displayed by Pytest.
- `test` (AntaTest): An AntaTest subclass imported in the test module - e.g. VerifyUptime.
- `eos_data` (list[dict]): List of data mocking EOS returned data to be passed to the test.
- `inputs` (dict): Dictionary to instantiate the `test` inputs as defined in the class from `test`.
- `expected` (dict): Expected test result structure, a dictionary containing a key
The `DATA` structure is a list of dictionaries used to parametrize the test. The list elements have the following keys:
- `name` (str): Test name as displayed by Pytest.
- `test` (AntaTest): An AntaTest subclass imported in the test module - e.g. VerifyUptime.
- `eos_data` (list[dict]): List of data mocking EOS returned data to be passed to the test.
- `inputs` (dict): Dictionary to instantiate the `test` inputs as defined in the class from `test`.
- `expected` (dict): Expected test result structure, a dictionary containing a key
`result` containing one of the allowed status (`Literal['success', 'failure', 'unset', 'skipped', 'error']`) and optionally a key `messages` which is a list(str) and each message is expected to be a substring of one of the actual messages in the TestResult object.

View file

@ -1,5 +1,5 @@
---
toc_depth: 4
toc_depth: 2
---
<!--
~ Copyright (c) 2023-2024 Arista Networks, Inc.
@ -7,7 +7,7 @@ toc_depth: 4
~ that can be found in the LICENSE file.
-->
<style>
h4 {
.md-typeset h2 {
visibility: hidden;
font-size: 0em;
height: 0em;

View file

@ -13,7 +13,7 @@ This section shows how to use ANTA with basic configuration. All examples are ba
The easiest way to install ANTA package is to run Python (`>=3.9`) and its pip package to install:
```bash
pip install anta
pip install anta[cli]
```
For more details about how to install package, please see the [requirements and installation](./requirements-and-installation.md) section.
@ -121,6 +121,14 @@ anta.tests.configuration:
## Test your network
### Basic usage in a python script
```python
--8<-- "anta_runner.py"
```
### CLI
ANTA comes with a generic CLI entrypoint to run tests in your network. It requires an inventory file as well as a test catalog.
This entrypoint has multiple options to manage test coverage and reporting.
@ -135,7 +143,7 @@ This entrypoint has multiple options to manage test coverage and reporting.
To run the NRFU, you need to select an output format amongst ["json", "table", "text", "tpl-report"]. For a first usage, `table` is recommended. By default all test results for all devices are rendered but it can be changed to a report per test case or per host
### Default report using table
#### Default report using table
```bash
anta nrfu \
@ -176,7 +184,7 @@ anta nrfu \
└───────────┴──────────────────────────┴─────────────┴──────────────────┴──────────────────────────────────────────────────────────────────────┴───────────────┘
```
### Report in text mode
#### Report in text mode
```bash
$ anta nrfu \
@ -206,7 +214,7 @@ leaf01 :: VerifyMlagConfigSanity :: SKIPPED (MLAG is disabled)
[...]
```
### Report in JSON format
#### Report in JSON format
```bash
$ anta nrfu \

View file

@ -0,0 +1,127 @@
<svg class="rich-terminal" viewBox="0 0 1482 440.4" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Regular"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Bold"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
font-style: bold;
font-weight: 700;
}
.terminal-2602327173-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.terminal-2602327173-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.terminal-2602327173-r1 { fill: #c5c8c6 }
.terminal-2602327173-r2 { fill: #68a0b3 }
.terminal-2602327173-r3 { fill: #98a84b }
.terminal-2602327173-r4 { fill: #4e707b }
.terminal-2602327173-r5 { fill: #608ab1 }
.terminal-2602327173-r6 { fill: #d0b344 }
.terminal-2602327173-r7 { fill: #868887 }
.terminal-2602327173-r8 { fill: #00823d;font-weight: bold }
.terminal-2602327173-r9 { fill: #68a0b3;font-weight: bold }
.terminal-2602327173-r10 { fill: #c5c8c6;font-weight: bold }
</style>
<defs>
<clipPath id="terminal-2602327173-clip-terminal">
<rect x="0" y="0" width="1463.0" height="389.4" />
</clipPath>
<clipPath id="terminal-2602327173-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-1">
<rect x="0" y="25.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-2">
<rect x="0" y="50.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-3">
<rect x="0" y="74.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-4">
<rect x="0" y="99.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-5">
<rect x="0" y="123.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-6">
<rect x="0" y="147.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-7">
<rect x="0" y="172.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-8">
<rect x="0" y="196.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-9">
<rect x="0" y="221.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-10">
<rect x="0" y="245.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-11">
<rect x="0" y="269.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-12">
<rect x="0" y="294.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-13">
<rect x="0" y="318.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-2602327173-line-14">
<rect x="0" y="343.1" width="1464" height="24.65"/>
</clipPath>
</defs>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="438.4" rx="8"/><text class="terminal-2602327173-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">anta&#160;nrfu&#160;--dry-run</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(9, 41)" clip-path="url(#terminal-2602327173-clip-terminal)">
<g class="terminal-2602327173-matrix">
<text class="terminal-2602327173-r1" x="0" y="20" textLength="390.4" clip-path="url(#terminal-2602327173-line-0)">ant@anthill$&#160;anta&#160;nrfu&#160;--dry-run</text><text class="terminal-2602327173-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-2602327173-line-0)">
</text><text class="terminal-2602327173-r2" x="0" y="44.4" textLength="24.4" clip-path="url(#terminal-2602327173-line-1)">╭─</text><text class="terminal-2602327173-r2" x="24.4" y="44.4" textLength="256.2" clip-path="url(#terminal-2602327173-line-1)">─────────────────────</text><text class="terminal-2602327173-r3" x="292.8" y="44.4" textLength="97.6" clip-path="url(#terminal-2602327173-line-1)">Settings</text><text class="terminal-2602327173-r2" x="402.6" y="44.4" textLength="256.2" clip-path="url(#terminal-2602327173-line-1)">─────────────────────</text><text class="terminal-2602327173-r2" x="658.8" y="44.4" textLength="24.4" clip-path="url(#terminal-2602327173-line-1)">─╮</text><text class="terminal-2602327173-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-2602327173-line-1)">
</text><text class="terminal-2602327173-r2" x="0" y="68.8" textLength="12.2" clip-path="url(#terminal-2602327173-line-2)"></text><text class="terminal-2602327173-r2" x="24.4" y="68.8" textLength="634.4" clip-path="url(#terminal-2602327173-line-2)">-&#160;ANTA&#160;Inventory&#160;contains&#160;3&#160;devices&#160;(AsyncEOSDevice)</text><text class="terminal-2602327173-r2" x="671" y="68.8" textLength="12.2" clip-path="url(#terminal-2602327173-line-2)"></text><text class="terminal-2602327173-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-2602327173-line-2)">
</text><text class="terminal-2602327173-r2" x="0" y="93.2" textLength="12.2" clip-path="url(#terminal-2602327173-line-3)"></text><text class="terminal-2602327173-r2" x="24.4" y="93.2" textLength="390.4" clip-path="url(#terminal-2602327173-line-3)">-&#160;Tests&#160;catalog&#160;contains&#160;9&#160;tests</text><text class="terminal-2602327173-r2" x="671" y="93.2" textLength="12.2" clip-path="url(#terminal-2602327173-line-3)"></text><text class="terminal-2602327173-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-2602327173-line-3)">
</text><text class="terminal-2602327173-r2" x="0" y="117.6" textLength="683.2" clip-path="url(#terminal-2602327173-line-4)">╰──────────────────────────────────────────────────────╯</text><text class="terminal-2602327173-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-2602327173-line-4)">
</text><text class="terminal-2602327173-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-2602327173-line-5)">
</text><text class="terminal-2602327173-r4" x="0" y="166.4" textLength="231.8" clip-path="url(#terminal-2602327173-line-6)">[04/29/24&#160;12:12:25]</text><text class="terminal-2602327173-r5" x="244" y="166.4" textLength="97.6" clip-path="url(#terminal-2602327173-line-6)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r1" x="353.8" y="166.4" textLength="292.8" clip-path="url(#terminal-2602327173-line-6)">Preparing&#160;ANTA&#160;NRFU&#160;Run&#160;</text><text class="terminal-2602327173-r6" x="646.6" y="166.4" textLength="36.6" clip-path="url(#terminal-2602327173-line-6)">...</text><text class="terminal-2602327173-r7" x="1317.6" y="166.4" textLength="97.6" clip-path="url(#terminal-2602327173-line-6)">tools.py</text><text class="terminal-2602327173-r7" x="1415.2" y="166.4" textLength="12.2" clip-path="url(#terminal-2602327173-line-6)">:</text><text class="terminal-2602327173-r7" x="1427.4" y="166.4" textLength="36.6" clip-path="url(#terminal-2602327173-line-6)">288</text><text class="terminal-2602327173-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-2602327173-line-6)">
</text><text class="terminal-2602327173-r5" x="244" y="190.8" textLength="97.6" clip-path="url(#terminal-2602327173-line-7)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r1" x="353.8" y="190.8" textLength="244" clip-path="url(#terminal-2602327173-line-7)">Preparing&#160;the&#160;tests&#160;</text><text class="terminal-2602327173-r6" x="597.8" y="190.8" textLength="36.6" clip-path="url(#terminal-2602327173-line-7)">...</text><text class="terminal-2602327173-r7" x="1317.6" y="190.8" textLength="97.6" clip-path="url(#terminal-2602327173-line-7)">tools.py</text><text class="terminal-2602327173-r7" x="1415.2" y="190.8" textLength="12.2" clip-path="url(#terminal-2602327173-line-7)">:</text><text class="terminal-2602327173-r7" x="1427.4" y="190.8" textLength="36.6" clip-path="url(#terminal-2602327173-line-7)">288</text><text class="terminal-2602327173-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-2602327173-line-7)">
</text><text class="terminal-2602327173-r5" x="244" y="215.2" textLength="97.6" clip-path="url(#terminal-2602327173-line-8)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r1" x="353.8" y="215.2" textLength="414.8" clip-path="url(#terminal-2602327173-line-8)">Preparing&#160;the&#160;tests&#160;completed&#160;in:&#160;</text><text class="terminal-2602327173-r8" x="768.6" y="215.2" textLength="85.4" clip-path="url(#terminal-2602327173-line-8)">0:00:00</text><text class="terminal-2602327173-r1" x="854" y="215.2" textLength="12.2" clip-path="url(#terminal-2602327173-line-8)">.</text><text class="terminal-2602327173-r9" x="866.2" y="215.2" textLength="36.6" clip-path="url(#terminal-2602327173-line-8)">001</text><text class="terminal-2602327173-r1" x="902.8" y="215.2" textLength="402.6" clip-path="url(#terminal-2602327173-line-8)">.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r7" x="1317.6" y="215.2" textLength="97.6" clip-path="url(#terminal-2602327173-line-8)">tools.py</text><text class="terminal-2602327173-r7" x="1415.2" y="215.2" textLength="12.2" clip-path="url(#terminal-2602327173-line-8)">:</text><text class="terminal-2602327173-r7" x="1427.4" y="215.2" textLength="36.6" clip-path="url(#terminal-2602327173-line-8)">296</text><text class="terminal-2602327173-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-2602327173-line-8)">
</text><text class="terminal-2602327173-r5" x="244" y="239.6" textLength="97.6" clip-path="url(#terminal-2602327173-line-9)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r1" x="353.8" y="239.6" textLength="939.4" clip-path="url(#terminal-2602327173-line-9)">---&#160;ANTA&#160;NRFU&#160;Run&#160;Information&#160;---&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r7" x="1305.4" y="239.6" textLength="109.8" clip-path="url(#terminal-2602327173-line-9)">runner.py</text><text class="terminal-2602327173-r7" x="1415.2" y="239.6" textLength="12.2" clip-path="url(#terminal-2602327173-line-9)">:</text><text class="terminal-2602327173-r7" x="1427.4" y="239.6" textLength="36.6" clip-path="url(#terminal-2602327173-line-9)">245</text><text class="terminal-2602327173-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-2602327173-line-9)">
</text><text class="terminal-2602327173-r1" x="353.8" y="264" textLength="231.8" clip-path="url(#terminal-2602327173-line-10)">Number&#160;of&#160;devices:&#160;</text><text class="terminal-2602327173-r9" x="585.6" y="264" textLength="12.2" clip-path="url(#terminal-2602327173-line-10)">3</text><text class="terminal-2602327173-r10" x="610" y="264" textLength="12.2" clip-path="url(#terminal-2602327173-line-10)">(</text><text class="terminal-2602327173-r9" x="622.2" y="264" textLength="12.2" clip-path="url(#terminal-2602327173-line-10)">3</text><text class="terminal-2602327173-r1" x="634.4" y="264" textLength="146.4" clip-path="url(#terminal-2602327173-line-10)">&#160;established</text><text class="terminal-2602327173-r10" x="780.8" y="264" textLength="12.2" clip-path="url(#terminal-2602327173-line-10)">)</text><text class="terminal-2602327173-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-2602327173-line-10)">
</text><text class="terminal-2602327173-r1" x="353.8" y="288.4" textLength="390.4" clip-path="url(#terminal-2602327173-line-11)">Total&#160;number&#160;of&#160;selected&#160;tests:&#160;</text><text class="terminal-2602327173-r9" x="744.2" y="288.4" textLength="24.4" clip-path="url(#terminal-2602327173-line-11)">27</text><text class="terminal-2602327173-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-2602327173-line-11)">
</text><text class="terminal-2602327173-r1" x="353.8" y="312.8" textLength="854" clip-path="url(#terminal-2602327173-line-12)">Maximum&#160;number&#160;of&#160;open&#160;file&#160;descriptors&#160;for&#160;the&#160;current&#160;ANTA&#160;process:&#160;</text><text class="terminal-2602327173-r9" x="1207.8" y="312.8" textLength="61" clip-path="url(#terminal-2602327173-line-12)">16384</text><text class="terminal-2602327173-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-2602327173-line-12)">
</text><text class="terminal-2602327173-r1" x="353.8" y="337.2" textLength="939.4" clip-path="url(#terminal-2602327173-line-13)">---------------------------------&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-2602327173-line-13)">
</text><text class="terminal-2602327173-r5" x="244" y="361.6" textLength="97.6" clip-path="url(#terminal-2602327173-line-14)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r1" x="353.8" y="361.6" textLength="463.6" clip-path="url(#terminal-2602327173-line-14)">Preparing&#160;ANTA&#160;NRFU&#160;Run&#160;completed&#160;in:&#160;</text><text class="terminal-2602327173-r8" x="817.4" y="361.6" textLength="85.4" clip-path="url(#terminal-2602327173-line-14)">0:00:00</text><text class="terminal-2602327173-r1" x="902.8" y="361.6" textLength="12.2" clip-path="url(#terminal-2602327173-line-14)">.</text><text class="terminal-2602327173-r9" x="915" y="361.6" textLength="36.6" clip-path="url(#terminal-2602327173-line-14)">006</text><text class="terminal-2602327173-r1" x="951.6" y="361.6" textLength="353.8" clip-path="url(#terminal-2602327173-line-14)">.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r7" x="1317.6" y="361.6" textLength="97.6" clip-path="url(#terminal-2602327173-line-14)">tools.py</text><text class="terminal-2602327173-r7" x="1415.2" y="361.6" textLength="12.2" clip-path="url(#terminal-2602327173-line-14)">:</text><text class="terminal-2602327173-r7" x="1427.4" y="361.6" textLength="36.6" clip-path="url(#terminal-2602327173-line-14)">296</text><text class="terminal-2602327173-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-2602327173-line-14)">
</text><text class="terminal-2602327173-r5" x="244" y="386" textLength="97.6" clip-path="url(#terminal-2602327173-line-15)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r1" x="353.8" y="386" textLength="939.4" clip-path="url(#terminal-2602327173-line-15)">Dry-run&#160;mode,&#160;exiting&#160;before&#160;running&#160;the&#160;tests.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-2602327173-r7" x="1305.4" y="386" textLength="109.8" clip-path="url(#terminal-2602327173-line-15)">runner.py</text><text class="terminal-2602327173-r7" x="1415.2" y="386" textLength="12.2" clip-path="url(#terminal-2602327173-line-15)">:</text><text class="terminal-2602327173-r7" x="1427.4" y="386" textLength="36.6" clip-path="url(#terminal-2602327173-line-15)">257</text><text class="terminal-2602327173-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-2602327173-line-15)">
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -22,24 +22,56 @@ This installation will deploy tests collection, scripts and all their Python req
The ANTA package and the cli require some packages that are not part of the Python standard library. They are indicated in the [pyproject.toml](https://github.com/arista-netdevops-community/anta/blob/main/pyproject.toml) file, under dependencies.
### Install from Pypi server
### Install library from Pypi server
```bash
pip install anta
```
!!! Warning
* This command alone **will not** install the ANTA CLI requirements.
* When using ANTA mode in [AVD](https://avd.arista.com) `eos_validate` role, (currently in preview), ensure you install the documented supported ANTA version for your AVD version.</br>
The latest documented version can be found at: https://avd.arista.com/stable/roles/eos_validate_state/ANTA-Preview.html
### Install ANTA CLI as an application with `pipx`
[`pipx`](https://pipx.pypa.io/stable/) is a tool to install and run python applications in isolated environments. If you plan to use ANTA only as a CLI tool you can use `pipx` to install it. `pipx` installs ANTA in an isolated python environment and makes it available globally.
```
pipx install anta[cli]
```
!!! Info
Please take the time to read through the installation instructions of `pipx` before getting started.
### Install CLI from Pypi server
Alternatively, pip install with `cli` extra is enough to install the ANTA CLI.
```bash
pip install anta[cli]
```
### Install ANTA from github
```bash
pip install git+https://github.com/arista-netdevops-community/anta.git
pip install git+https://github.com/arista-netdevops-community/anta.git#egg=anta[cli]
# You can even specify the branch, tag or commit:
pip install git+https://github.com/arista-netdevops-community/anta.git@<cool-feature-branch>
pip install git+https://github.com/arista-netdevops-community/anta.git@<cool-tag>
pip install git+https://github.com/arista-netdevops-community/anta.git@<more-or-less-cool-hash>
```
pip install git+https://github.com/arista-netdevops-community/anta.git@<cool-feature-branch>#egg=anta[cli]
pip install git+https://github.com/arista-netdevops-community/anta.git@<cool-tag>
pip install git+https://github.com/arista-netdevops-community/anta.git@<cool-tag>#egg=anta[cli]
pip install git+https://github.com/arista-netdevops-community/anta.git@<more-or-less-cool-hash>
pip install git+https://github.com/arista-netdevops-community/anta.git@<more-or-less-cool-hash>#egg=anta[cli]
```
### Check installation
@ -61,12 +93,12 @@ which anta
```bash
# Check ANTA version
anta --version
anta, version v0.14.0
anta, version v0.15.0
```
## EOS Requirements
To get ANTA working, the targeted Arista EOS devices must have the following configuration (assuming you connect to the device using Management interface in MGMT VRF):
To get ANTA working, the targeted Arista EOS devices must have eAPI enabled. They need to use the following configuration (assuming you connect to the device using Management interface in MGMT VRF):
```eos
configure

View file

@ -13,6 +13,7 @@ python generate_svg.py anta ...
# ruff: noqa: T201
import io
import logging
import os
import pathlib
import sys
@ -22,10 +23,17 @@ from importlib.metadata import entry_points
from unittest.mock import patch
from rich.console import Console
from rich.logging import RichHandler
from anta.cli.console import console
from anta.cli.nrfu.utils import anta_progress_bar
root = logging.getLogger()
r = RichHandler(console=console)
root.addHandler(r)
OUTPUT_DIR = pathlib.Path(__file__).parent.parent / "imgs"
@ -43,7 +51,7 @@ def custom_progress_bar() -> None:
if __name__ == "__main__":
# Sane rich size
os.environ["COLUMNS"] = "165"
os.environ["COLUMNS"] = "120"
# stolen from https://github.com/ewels/rich-click/blob/main/src/rich_click/cli.py
args = sys.argv[1:]
@ -64,6 +72,8 @@ if __name__ == "__main__":
print("Usage: python generate_svg.py anta <options>")
sys.exit(1)
# possibly-used-before-assignment - prog / function_name -> not understanding sys.exit here...
# pylint: disable=E0606
sys.argv = [prog, *args[1:]]
module = import_module(module_path)
function = getattr(module, function_name)
@ -71,22 +81,24 @@ if __name__ == "__main__":
# Console to captur everything
new_console = Console(record=True)
# tweaks to record and redirect to a dummy file
pipe = io.StringIO()
console.record = True
console.file = pipe
with redirect_stdout(io.StringIO()) as f:
# tweaks to record and redirect to a dummy file
# Redirect stdout of the program towards another StringIO to capture help
# that is not part or anta rich console
# redirect potential progress bar output to console by patching
with redirect_stdout(io.StringIO()) as f, patch("anta.cli.nrfu.commands.anta_progress_bar", custom_progress_bar), suppress(SystemExit):
function()
# print to our new console the output of anta console
new_console.print(console.export_text())
# print the content of the stdout to our new_console
new_console.print(f.getvalue())
console.print(f"ant@anthill$ {' '.join(sys.argv)}")
# Redirect stdout of the program towards another StringIO to capture help
# that is not part or anta rich console
# redirect potential progress bar output to console by patching
with patch("anta.cli.nrfu.anta_progress_bar", custom_progress_bar), suppress(SystemExit):
function()
if "--help" in args:
console.print(f.getvalue())
filename = f"{'_'.join(x.replace('/', '_').replace('-', '_').replace('.', '_') for x in args)}.svg"
filename = f"{OUTPUT_DIR}/{filename}"
print(f"File saved at {filename}")
new_console.save_svg(filename, title=" ".join(args))
console.save_svg(filename, title=" ".join(args))

View file

@ -42,6 +42,10 @@ Options:
ANTA_NRFU_IGNORE_ERROR]
--hide [success|failure|error|skipped]
Group result by test or device.
--dry-run Run anta nrfu command but stop before
starting to execute the tests. Considers all
devices as connected. [env var:
ANTA_NRFU_DRY_RUN]
--help Show this message and exit.
Commands:

View file

@ -2,11 +2,15 @@
--md-hue: 210;
}
#page {
counter-reset: heading;
}
:root {
/* Color schema based on Arista Color Schema */
/* Default color shades */
--md-default-fg-color: #000000;
--md-default-fg-color--light: #a1a0a0;
--md-default-fg-color--light: #444343;
--md-default-fg-color--lighter: #FFFFFF;
--md-default-fg-color--lightest: #FFFFFF;
--md-default-bg-color: #FFFFFF;
@ -35,12 +39,8 @@
--md-code-bg-color: #E6E6E6;
--md-code-border-color: #0000004f;
--block-code-bg-color: #e4e4e4;
/* --md-code-fg-color: ...; */
font-size: 1.1rem;
/* min-height: 100%;
position: relative;
width: 100%; */
font-feature-settings: "kern","liga";
font-family: var(--md-text-font-family,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
-webkit-font-smoothing: antialiased;
@ -49,15 +49,16 @@
[data-md-color-scheme="slate"] {
/* Default color shades */
--md-default-fg-color--light: #949393;
/* Link color */
--md-typeset-a-color: #75aaf8;
--md-typeset-a-color-fg: #FFFFFF;
--md-typeset-a-color-bg: #27569B;
/* Code block color shades */
/* --md-code-bg-color: #E6E6E6; */
--md-code-border-color: #aec6db4f;
/* --block-code-bg-color: #e4e4e4; */
}
@media only screen and (min-width: 76.25em) {
@ -76,6 +77,7 @@
}
@media only screen {
.md-typeset a:hover {
background-color: var(--md-typeset-a-color-bg);
color: var(--md-typeset-a-color-fg);
@ -102,12 +104,56 @@
color: var(--md-default-fg-color--light);
}
.md-typeset h4 h5 h6 {
font-size: 1.5rem;
margin: 1em 0;
/* font-weight: 700; */
letter-spacing: -.01em;
line-height: 3em;
.md-typeset h2 {
line-height: 2em;
font-size: 1.5rem;
margin: 1em 0;
/* font-weight: 700; */
letter-spacing: -.01em;
color: var(--md-default-fg-color--light);
text-transform: capitalize;
font-style: normal;
font-weight: bolder;
}
.md-typeset h3 {
line-height: 1em;
font-size: 1.3rem;
margin: 1em 0;
/* font-weight: 700; */
letter-spacing: -.01em;
color: var(--md-default-fg-color--light);
text-transform: capitalize;
font-style: normal;
font-weight: bold;
}
.md-typeset h4::before {
content: ">> ";
}
.md-typeset h4 {
font-size: 1.1rem;
margin: 1em 0;
font-weight: 700;
letter-spacing: -.01em;
line-height: 1em;
color: var(--md-default-fg-color--light);
font-style: italic;
text-transform: capitalize;
}
.md-typeset h5,
.md-typeset h6 {
font-size: 0.9rem;
margin: 1em 0;
/* font-weight: 700; */
letter-spacing: -.01em;
/* line-height: 2em; */
color: var(--md-default-fg-color--light);
font-style: italic;
text-transform: capitalize;
text-decoration: underline;
}
.md-typeset table:not([class]) th {
@ -178,8 +224,6 @@
.md-typeset table:not([class]) th {
min-width: 5rem;
padding: .6rem .8rem;
/* color: var(--md-primary-fg-color--light); */
bg: var(--md-footer-fg-color--lighter);
}
.md-footer-copyright {
@ -195,7 +239,6 @@
margin-left: auto;
margin-right: auto;
border-radius: 1%;
/* width: 50%; */
}
}

View file

@ -77,3 +77,14 @@ Example:
```bash
ANTA_DEBUG=true anta -l DEBUG --log-file anta.log nrfu --enable --username username --password arista --inventory inventory.yml -c nrfu.yml text
```
### Troubleshooting on EOS
ANTA is using a specific ID in eAPI requests towards EOS. This allows for easier eAPI requests debugging on the device using EOS configuration `trace CapiApp setting UwsgiRequestContext/4,CapiUwsgiServer/4` to set up CapiApp agent logs.
Then, you can view agent logs using:
```bash
bash tail -f /var/log/agents/CapiApp-*
2024-05-15 15:32:54.056166 1429 UwsgiRequestContext 4 request content b'{"jsonrpc": "2.0", "method": "runCmds", "params": {"version": "latest", "cmds": [{"cmd": "show ip route vrf default 10.255.0.3", "revision": 4}], "format": "json", "autoComplete": false, "expandAliases": false}, "id": "ANTA-VerifyRoutingTableEntry-132366530677328"}'
```

View file

@ -244,3 +244,32 @@ Once you run `anta nrfu table`, you will see following output:
│ spine01 │ VerifyInterfaceUtilization │ success │ │ Verifies interfaces utilization is below 75%. │ interfaces │
└───────────┴────────────────────────────┴─────────────┴────────────┴───────────────────────────────────────────────┴───────────────┘
```
### Example script to merge catalogs
The following script reads all the files in `intended/test_catalogs/` with names `<device_name>-catalog.yml` and merge them together inside one big catalog `anta-catalog.yml`.
```python
#!/usr/bin/env python
from anta.catalog import AntaCatalog
from pathlib import Path
from anta.models import AntaTest
CATALOG_SUFFIX = '-catalog.yml'
CATALOG_DIR = 'intended/test_catalogs/'
if __name__ == "__main__":
catalog = AntaCatalog()
for file in Path(CATALOG_DIR).glob('*'+CATALOG_SUFFIX):
c = AntaCatalog.parse(file)
device = str(file).removesuffix(CATALOG_SUFFIX).removeprefix(CATALOG_DIR)
print(f"Merging test catalog for device {device}")
# Apply filters to all tests for this device
for test in c.tests:
test.inputs.filters = AntaTest.Input.Filters(tags=[device])
catalog.merge(c)
with open(Path('anta-catalog.yml'), "w") as f:
f.write(catalog.dump().yaml())
```