Skip to content

Commit

Permalink
feat(anta): Use http head request to check_connection
Browse files Browse the repository at this point in the history
* HEAD request to /command-api url
* Handle HTTPStatus error in refresh, log error message
* Update unit tests
  • Loading branch information
dlobato committed Oct 11, 2024
1 parent 92cbcd3 commit 0235699
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 99 deletions.
11 changes: 7 additions & 4 deletions anta/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,12 +450,17 @@ async def refresh(self) -> None:
"""Update attributes of an AsyncEOSDevice instance.
This coroutine must update the following attributes of AsyncEOSDevice:
- is_online: When a device IP is reachable and a port can be open
- is_online: When a device eAPI HTTP endpoint is accessible
- established: When a command execution succeeds
- hw_model: The hardware model of the device
"""
logger.debug("Refreshing device %s", self.name)
self.is_online = await self._session.check_connection()
try:
self.is_online = await self._session.check_connection()
except HTTPError as e:
self.is_online = False
logger.warning("Could not connect to device %s: %s", self.name, e)

if self.is_online:
show_version = AntaCommand(command="show version")
await self._collect(show_version)
Expand All @@ -469,8 +474,6 @@ async def refresh(self) -> None:
# and it is nice to get a meaninfule error message
elif self.hw_model == "":
logger.critical("Got an empty 'modelName' in the 'show version' returned by device %s", self.name)
else:
logger.warning("Could not connect to device %s: cannot open eAPI port", self.name)

self.established = bool(self.is_online and self.hw_model)

Expand Down
63 changes: 0 additions & 63 deletions asynceapi/aio_portcheck.py

This file was deleted.

13 changes: 8 additions & 5 deletions asynceapi/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
# -----------------------------------------------------------------------------
# Private Imports
# -----------------------------------------------------------------------------
from .aio_portcheck import port_check_url
from .config_session import SessionConfig
from .errors import EapiCommandError

Expand Down Expand Up @@ -51,6 +50,7 @@ class Device(httpx.AsyncClient):
"""

auth = None
EAPI_COMMAND_API_URL = "/command-api"
EAPI_OFMT_OPTIONS = ("json", "text")
EAPI_DEFAULT_OFMT = "json"

Expand Down Expand Up @@ -112,17 +112,20 @@ def __init__(

async def check_connection(self) -> bool:
"""
Check the target device to ensure that the eAPI port is open and accepting connections.
Check the target device eAPI HTTP endpoint with a HEAD request.
It is recommended that a Caller checks the connection before involving cli commands,
but this step is not required.
Returns
-------
bool
True when the device eAPI is accessible, False otherwise.
True when the device eAPI HTTP endpoint is accessible (2xx status code),
otherwise an HTTPStatusError exception is raised.
"""
return await port_check_url(self.base_url)
response = await self.head(self.EAPI_COMMAND_API_URL, timeout=5)
response.raise_for_status()
return True

async def cli( # noqa: PLR0913
self,
Expand Down Expand Up @@ -283,7 +286,7 @@ async def jsonrpc_exec(self, jsonrpc: dict[str, Any]) -> list[dict[str, Any] | s
The list of command results; either dict or text depending on the
JSON-RPC format parameter.
"""
res = await self.post("/command-api", json=jsonrpc)
res = await self.post(self.EAPI_COMMAND_API_URL, json=jsonrpc)
res.raise_for_status()
body = res.json()

Expand Down
8 changes: 4 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
# that can be found in the LICENSE file.
"""See https://docs.pytest.org/en/stable/reference/fixtures.html#conftest-py-sharing-fixtures-across-multiple-files."""

import asyncio
from collections.abc import Iterator
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch

import pytest
import respx
Expand Down Expand Up @@ -42,7 +40,8 @@ def inventory(request: pytest.FixtureRequest) -> Iterator[AntaInventory]:
)
if reachable:
# This context manager makes all devices reachable
with patch("asyncio.open_connection", AsyncMock(spec=asyncio.open_connection, return_value=(Mock(), Mock()))), respx.mock:
with respx.mock:
respx.head(path="/command-api")
respx.post(path="/command-api", headers={"Content-Type": "application/json-rpc"}, json__params__cmds__0__cmd="show version").respond(
json={
"result": [
Expand All @@ -54,5 +53,6 @@ def inventory(request: pytest.FixtureRequest) -> Iterator[AntaInventory]:
)
yield inv
else:
with patch("asyncio.open_connection", AsyncMock(spec=asyncio.open_connection, side_effect=TimeoutError)):
with respx.mock:
respx.head(path="/command-api").respond(status_code=401)
yield inv
25 changes: 2 additions & 23 deletions tests/units/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,29 +343,8 @@
pytest.param(
{},
(
{"return_value": False},
{
"return_value": {
"mfgName": "Arista",
"modelName": "DCS-7280CR3-32P4-F",
"hardwareRevision": "11.00",
"serialNumber": "JPE19500066",
"systemMacAddress": "fc:bd:67:3d:13:c5",
"hwMacAddress": "fc:bd:67:3d:13:c5",
"configMacAddress": "00:00:00:00:00:00",
"version": "4.31.1F-34361447.fraserrel (engineering build)",
"architecture": "x86_64",
"internalVersion": "4.31.1F-34361447.fraserrel",
"internalBuildId": "4940d112-a2fc-4970-8b5a-a16cd03fd08c",
"imageFormatVersion": "3.0",
"imageOptimization": "Default",
"bootupTimestamp": 1700729434.5892005,
"uptime": 20666.78,
"memTotal": 8099732,
"memFree": 4989568,
"isIntlVersion": False,
}
},
{"side_effect": HTTPError(message="Unauthorized")},
{},
),
{"is_online": False, "established": False, "hw_model": None},
id="is not online",
Expand Down

0 comments on commit 0235699

Please sign in to comment.