style: ruff format and the lint rules of the driver standard
Mechanical: ruff format, and ruff check --fix with the rule set every NAPALM driver uses (E, F, I, UP, B at line length 100, Python 3.10). What ruff cannot fix comes in the next commit.
This commit is contained in:
1 parent
739283e76b
commit
c2a788fa7a
6 files changed
+307
-278
No files matched your search
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Licensed under the Apache License, Version 2.0
|
||||
|
||||
"""REST API client for HPE/Aruba ProCurve switches (ArubaOS REST interface).
|
||||
@@ -8,13 +7,12 @@ Used automatically by ProcurveDriver when the REST API is reachable.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
|
||||
from napalm.base import helpers as napalm_helpers
|
||||
from napalm.base.exceptions import ConnectionException, ConnectAuthError
|
||||
from napalm.base.exceptions import ConnectAuthError, ConnectionException
|
||||
from napalm_device_types import add_lag_interfaces
|
||||
|
||||
logger = logging.getLogger("napalm_procurve.api")
|
||||
@@ -37,7 +35,7 @@ class ProcurveApiClient:
|
||||
password: str,
|
||||
timeout: int = 60,
|
||||
ssl_verify: bool = False,
|
||||
api_version: Optional[str] = None,
|
||||
api_version: str | None = None,
|
||||
) -> None:
|
||||
self.hostname = hostname
|
||||
self.username = username
|
||||
@@ -47,7 +45,7 @@ class ProcurveApiClient:
|
||||
self._api_version = api_version # None = auto-detect
|
||||
self._proto: str = "https"
|
||||
self._base_url: str = ""
|
||||
self._session: Optional[requests.Session] = None
|
||||
self._session: requests.Session | None = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Detection
|
||||
@@ -59,7 +57,7 @@ class ProcurveApiClient:
|
||||
hostname: str,
|
||||
timeout: int = 5,
|
||||
ssl_verify: bool = False,
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
) -> tuple[str | None, str | None]:
|
||||
"""Probe the device for an ArubaOS REST API.
|
||||
|
||||
Returns ``(api_version, proto)`` on success, ``(None, None)`` if the
|
||||
@@ -86,7 +84,10 @@ class ProcurveApiClient:
|
||||
if resp.status_code in (200, 301, 302, 303, 401, 403, 405):
|
||||
logger.debug(
|
||||
"API detected at %s (HTTP %s, %s %s)",
|
||||
hostname, resp.status_code, proto, ver,
|
||||
hostname,
|
||||
resp.status_code,
|
||||
proto,
|
||||
ver,
|
||||
)
|
||||
return ver, proto
|
||||
except requests.exceptions.SSLError:
|
||||
@@ -115,9 +116,7 @@ class ProcurveApiClient:
|
||||
|
||||
self._session = requests.Session()
|
||||
self._session.verify = self.ssl_verify
|
||||
self._session.headers.update(
|
||||
{"Content-Type": "application/json", "Connection": "close"}
|
||||
)
|
||||
self._session.headers.update({"Content-Type": "application/json", "Connection": "close"})
|
||||
|
||||
url = self._base_url + "login-sessions"
|
||||
resp = self._session.post(
|
||||
@@ -148,9 +147,7 @@ class ProcurveApiClient:
|
||||
"""Log out from the REST API session."""
|
||||
if self._session and self._base_url:
|
||||
try:
|
||||
self._session.delete(
|
||||
self._base_url + "login-sessions", timeout=self.timeout
|
||||
)
|
||||
self._session.delete(self._base_url + "login-sessions", timeout=self.timeout)
|
||||
except Exception:
|
||||
pass
|
||||
if self._session:
|
||||
@@ -162,9 +159,7 @@ class ProcurveApiClient:
|
||||
if not self._session:
|
||||
return False
|
||||
try:
|
||||
resp = self._session.get(
|
||||
self._base_url + "system/status", timeout=5
|
||||
)
|
||||
resp = self._session.get(self._base_url + "system/status", timeout=5)
|
||||
return resp.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
@@ -173,7 +168,7 @@ class ProcurveApiClient:
|
||||
# HTTP helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get(self, endpoint: str) -> Dict[str, Any]:
|
||||
def get(self, endpoint: str) -> dict[str, Any]:
|
||||
"""GET ``{base_url}{endpoint}`` and return parsed JSON (or empty dict)."""
|
||||
url = self._base_url + endpoint
|
||||
try:
|
||||
@@ -183,7 +178,9 @@ class ProcurveApiClient:
|
||||
if resp.status_code == 404:
|
||||
logger.debug("GET %s returned HTTP 404", url)
|
||||
else:
|
||||
logger.warning("GET %s returned HTTP %s: %s", url, resp.status_code, resp.text[:200])
|
||||
logger.warning(
|
||||
"GET %s returned HTTP %s: %s", url, resp.status_code, resp.text[:200]
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("GET %s error: %s", url, exc)
|
||||
return {}
|
||||
@@ -204,7 +201,7 @@ class ProcurveApiClient:
|
||||
# NAPALM data getters (API-backed)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_facts(self) -> Dict:
|
||||
def get_facts(self) -> dict:
|
||||
"""Return NAPALM facts from the REST API."""
|
||||
system = self.get("system/status")
|
||||
if not system:
|
||||
@@ -230,6 +227,7 @@ class ProcurveApiClient:
|
||||
model_raw = system.get("product_model", "")
|
||||
# Extract J-code part numbers (e.g. J9777A) from the model string
|
||||
import re as _re
|
||||
|
||||
_PN_RE = _re.compile(r"\b(J\d{4}[A-Z]{1,2})\b")
|
||||
pn_match = _PN_RE.search(model_raw)
|
||||
part_number = pn_match.group(1) if pn_match else ""
|
||||
@@ -253,14 +251,14 @@ class ProcurveApiClient:
|
||||
"interface_list": iface_list,
|
||||
}
|
||||
|
||||
def get_interfaces(self) -> Dict[str, Dict]:
|
||||
def get_interfaces(self) -> dict[str, dict]:
|
||||
"""Return NAPALM interfaces from the REST API."""
|
||||
ports_data = self.get("ports")
|
||||
stats_data = self.get("port-statistics")
|
||||
|
||||
output: Dict[str, Dict] = {}
|
||||
trunk_groups: Dict[str, list] = {}
|
||||
trunk_modes: Dict[str, str] = {}
|
||||
output: dict[str, dict] = {}
|
||||
trunk_groups: dict[str, list] = {}
|
||||
trunk_modes: dict[str, str] = {}
|
||||
|
||||
port_elements = list(ports_data.get("port_element", []))
|
||||
|
||||
@@ -274,13 +272,15 @@ class ProcurveApiClient:
|
||||
for p in blade.get("data_ports", []):
|
||||
pid = p.get("port_name", "")
|
||||
if pid and pid not in seen_ids:
|
||||
port_elements.append({
|
||||
"id": pid,
|
||||
"name": "",
|
||||
"is_port_up": p.get("operStatus") == "OPER_UP",
|
||||
"is_port_enabled": p.get("adminStatus") == "ADMIN_UP",
|
||||
"trunk_group": "",
|
||||
})
|
||||
port_elements.append(
|
||||
{
|
||||
"id": pid,
|
||||
"name": "",
|
||||
"is_port_up": p.get("operStatus") == "OPER_UP",
|
||||
"is_port_enabled": p.get("adminStatus") == "ADMIN_UP",
|
||||
"trunk_group": "",
|
||||
}
|
||||
)
|
||||
seen_ids.add(pid)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -309,15 +309,18 @@ class ProcurveApiClient:
|
||||
output[pid]["speed"] = float(stat.get("port_speed_mbps", 0))
|
||||
|
||||
# One row per LAG/trunk group alongside its member ports.
|
||||
return add_lag_interfaces(output, {
|
||||
group: "lacp" if trunk_modes.get(group) == "PTT_LACP" else "trunk"
|
||||
for group in trunk_groups
|
||||
})
|
||||
return add_lag_interfaces(
|
||||
output,
|
||||
{
|
||||
group: "lacp" if trunk_modes.get(group) == "PTT_LACP" else "trunk"
|
||||
for group in trunk_groups
|
||||
},
|
||||
)
|
||||
|
||||
def get_interfaces_ip(self) -> Dict[str, Dict]:
|
||||
def get_interfaces_ip(self) -> dict[str, dict]:
|
||||
"""Return NAPALM interfaces IP from the REST API."""
|
||||
vlans_data = self.get("vlans")
|
||||
result: Dict[str, Dict] = {}
|
||||
result: dict[str, dict] = {}
|
||||
|
||||
for vlan in vlans_data.get("vlan_element", []):
|
||||
vlan_id = str(vlan.get("vlan_id", ""))
|
||||
@@ -331,6 +334,7 @@ class ProcurveApiClient:
|
||||
continue
|
||||
try:
|
||||
import netaddr
|
||||
|
||||
plen = netaddr.IPNetwork(f"{addr}/{mask}").prefixlen
|
||||
except Exception:
|
||||
plen = 24
|
||||
@@ -343,12 +347,12 @@ class ProcurveApiClient:
|
||||
# PoE
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_poe_ports(self) -> Dict[str, Dict]:
|
||||
def get_poe_ports(self) -> dict[str, dict]:
|
||||
"""Return PoE configuration for all ports, keyed by port id."""
|
||||
data = self.get("poe/ports")
|
||||
return {p["port_id"]: p for p in data.get("port_poe", []) if p.get("port_id")}
|
||||
|
||||
def set_port_poe(self, port_id: str, payload: Dict[str, Any]) -> requests.Response:
|
||||
def set_port_poe(self, port_id: str, payload: dict[str, Any]) -> requests.Response:
|
||||
"""PUT PoE configuration changes for a single port."""
|
||||
return self.put(f"ports/{port_id}/poe", json=payload)
|
||||
|
||||
@@ -390,9 +394,9 @@ class ProcurveApiClient:
|
||||
)
|
||||
return table
|
||||
|
||||
def get_lldp_neighbors(self) -> Dict[str, list]:
|
||||
def get_lldp_neighbors(self) -> dict[str, list]:
|
||||
"""Return NAPALM LLDP neighbors from the REST API."""
|
||||
result: Dict[str, list] = {}
|
||||
result: dict[str, list] = {}
|
||||
data = self.get("lldp/remote-device")
|
||||
for nbr in data.get("lldp_remote_device_element", []):
|
||||
local_port = nbr.get("local_port", "")
|
||||
@@ -404,9 +408,9 @@ class ProcurveApiClient:
|
||||
)
|
||||
return result
|
||||
|
||||
def get_lldp_neighbors_detail(self) -> Dict[str, list]:
|
||||
def get_lldp_neighbors_detail(self) -> dict[str, list]:
|
||||
"""Return NAPALM LLDP neighbor details from the REST API."""
|
||||
result: Dict[str, list] = {}
|
||||
result: dict[str, list] = {}
|
||||
data = self.get("lldp/remote-device")
|
||||
for nbr in data.get("lldp_remote_device_element", []):
|
||||
local_port = nbr.get("local_port", "")
|
||||
@@ -424,7 +428,7 @@ class ProcurveApiClient:
|
||||
)
|
||||
return result
|
||||
|
||||
def get_config(self) -> Dict[str, str]:
|
||||
def get_config(self) -> dict[str, str]:
|
||||
"""Return running and startup configuration via REST API.
|
||||
|
||||
AOS-Switch returns the config as plain text, not JSON.
|
||||
@@ -447,17 +451,17 @@ class ProcurveApiClient:
|
||||
logger.warning("GET %s error: %s", url, exc)
|
||||
return ""
|
||||
|
||||
def get_ntp_servers(self) -> Dict[str, Dict]:
|
||||
def get_ntp_servers(self) -> dict[str, dict]:
|
||||
"""Return NTP servers from REST API."""
|
||||
data = self.get("ntp/server")
|
||||
servers: Dict[str, Dict] = {}
|
||||
servers: dict[str, dict] = {}
|
||||
for entry in data.get("ntp_server_element", []):
|
||||
addr = entry.get("server_address", "")
|
||||
if addr:
|
||||
servers[addr] = {}
|
||||
return servers
|
||||
|
||||
def get_vlans(self) -> Dict[int, Dict]:
|
||||
def get_vlans(self) -> dict[int, dict]:
|
||||
"""Return VLAN information with proper tagged/untagged separation.
|
||||
|
||||
Fetches ``/vlans`` for VLAN names and ``/vlans-ports`` for port membership.
|
||||
@@ -491,7 +495,7 @@ class ProcurveApiClient:
|
||||
ports_data = self.get("vlans-ports")
|
||||
|
||||
# Build VLAN skeleton from /vlans
|
||||
result: Dict[int, Dict] = {}
|
||||
result: dict[int, dict] = {}
|
||||
for vlan in vlans_data.get("vlan_element", []):
|
||||
try:
|
||||
vid = int(vlan["vlan_id"])
|
||||
@@ -522,8 +526,7 @@ class ProcurveApiClient:
|
||||
|
||||
# Untagged variants: POM_UNTAGGED, POM_NATIVE_UNTAGGED, "Untagged"
|
||||
is_untagged = any(
|
||||
kw in mode_low
|
||||
for kw in ("untagged", "native_untagged", "pom_untagged")
|
||||
kw in mode_low for kw in ("untagged", "native_untagged", "pom_untagged")
|
||||
) and "tagged" not in mode_low.replace("untagged", "")
|
||||
|
||||
# More precise: contains "untagged" anywhere → untagged
|
||||
@@ -576,17 +579,17 @@ class ProcurveApiClient:
|
||||
f"delete_vlan({vlan_id}): REST API returned HTTP {resp.status_code}"
|
||||
)
|
||||
|
||||
def get_snmp_communities(self) -> Dict[str, Dict]:
|
||||
def get_snmp_communities(self) -> dict[str, dict]:
|
||||
"""Return configured SNMP communities from REST API."""
|
||||
data = self.get("snmp-server/community")
|
||||
result: Dict[str, Dict] = {}
|
||||
result: dict[str, dict] = {}
|
||||
for entry in data.get("snmp_community_element", []):
|
||||
name = entry.get("community_name", "")
|
||||
if name:
|
||||
result[name] = {"access_type": entry.get("access_type", "")}
|
||||
return result
|
||||
|
||||
def configure_snmp_community(self, community: str = "public") -> Tuple[bool, str]:
|
||||
def configure_snmp_community(self, community: str = "public") -> tuple[bool, str]:
|
||||
"""Ensure an SNMP read-only community exists via REST resource endpoint.
|
||||
|
||||
Returns (success, message).
|
||||
@@ -595,10 +598,13 @@ class ProcurveApiClient:
|
||||
if community in existing:
|
||||
return True, f"SNMP community '{community}' already exists."
|
||||
|
||||
resp = self.post("snmp-server/community", json={
|
||||
"community_name": community,
|
||||
"access_type": "MIB2",
|
||||
})
|
||||
resp = self.post(
|
||||
"snmp-server/community",
|
||||
json={
|
||||
"community_name": community,
|
||||
"access_type": "MIB2",
|
||||
},
|
||||
)
|
||||
if resp.ok:
|
||||
return True, f"SNMP community '{community}' configured via REST API."
|
||||
if resp.status_code == 409:
|
||||
@@ -615,9 +621,9 @@ class ProcurveApiClient:
|
||||
size: int = 100,
|
||||
count: int = 5,
|
||||
vrf: str = "",
|
||||
) -> Dict:
|
||||
) -> dict:
|
||||
"""Execute ping via REST API (if supported) or return not-implemented."""
|
||||
payload: Dict = {"destination": destination, "count": count, "timeout": timeout}
|
||||
payload: dict = {"destination": destination, "count": count, "timeout": timeout}
|
||||
if source:
|
||||
payload["source"] = source
|
||||
|
||||
|
||||
Reference in new issue
Block a user