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:
Christian Manivong committed 2026-10-23 08:32:50 +02:00
1 parent 739283e76b
commit c2a788fa7a
6 files changed
+307 -278

No files matched your search

+117 -123
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0
"""NAPALM driver for HPE/Aruba ProCurve switches.
@@ -18,15 +17,16 @@ Netmiko device type: ``hp_procurve`` / ``hp_procurve_telnet``
import logging
import re
import socket
from typing import Any, ClassVar, Dict, List, Optional, Union
from typing import Any, ClassVar
import netaddr
from netmiko import ConnectHandler
from netmiko.exceptions import (
NetmikoAuthenticationException,
NetmikoTimeoutException,
from napalm.base.exceptions import (
CommandErrorException,
ConnectionClosedException,
ConnectionException,
MergeConfigException,
ReplaceConfigException,
)
from napalm.base.netmiko_helpers import netmiko_args
from napalm_device_types import (
ConfigLifecycleMixin,
FingerprintRule,
@@ -35,24 +35,20 @@ from napalm_device_types import (
run_fix_steps,
)
from napalm_device_types.models import InterfaceConfigDict, VlanConfigDict
from napalm.base import helpers as napalm_helpers
from napalm.base.exceptions import (
CommandErrorException,
ConnectionException,
ConnectionClosedException,
MergeConfigException,
ReplaceConfigException,
)
from napalm.base.netmiko_helpers import netmiko_args
from napalm_device_types.ssh_host_keys import (
KNOWN_KEYS_ARG,
OBSERVER_ARG,
HostKeyCheck,
SshHostKeyMismatch,
)
from netmiko import ConnectHandler
from netmiko.exceptions import (
NetmikoAuthenticationException,
NetmikoTimeoutException,
)
from napalm_procurve.api_client import ProcurveApiClient
from napalm_procurve import parsers
from napalm_procurve.api_client import ProcurveApiClient
logger = logging.getLogger("napalm_procurve")
@@ -118,7 +114,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
#: Where the switch publishes its health values over SNMP
#: (napalm-device-types ``SnmpHealthProfile``); the application polls.
#: Memory is NETSWITCH-MIB ``hpLocalMemEntry``: .5 total, .6 free, .7 allocated.
SNMP_HEALTH_PROFILE: ClassVar[Dict[str, Any]] = {
SNMP_HEALTH_PROFILE: ClassVar[dict[str, Any]] = {
"uptime": {"oids": ["1.3.6.1.4.1.11.2.14.11.1.2.4.1.5.1"]},
"cpu": {"oids": ["1.3.6.1.4.1.11.2.14.11.5.1.9.6.1.0", "1.3.6.1.4.1.11.2.3.7.8.6.1.0"]},
"memory": {
@@ -134,7 +130,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
username: str,
password: str,
timeout: int = 60,
optional_args: Optional[Dict] = None,
optional_args: dict | None = None,
) -> None:
self.hostname = hostname
self.username = username
@@ -147,11 +143,11 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# --- optional_args ---
self.port: int = optional_args.get("port", 22)
self.telnet_port: int = optional_args.get("telnet_port", 23)
self.force_transport: Optional[str] = optional_args.get("transport")
self.force_transport: str | None = optional_args.get("transport")
self.ssl_verify: bool = optional_args.get(
"ssl_verify", optional_args.get("verify_ssl", optional_args.get("verify", False))
)
self.api_version_hint: Optional[str] = optional_args.get("api_version")
self.api_version_hint: str | None = optional_args.get("api_version")
# Enable password for CLI (defaults to login password)
self._secret: str = optional_args.get("secret", password)
@@ -160,24 +156,24 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
self.netmiko_optional_args = netmiko_args(optional_args)
# The host keys to expect and where the one seen is reported (netork#1167).
self._host_key_args: Dict[str, Any] = {
self._host_key_args: dict[str, Any] = {
k: optional_args[k] for k in (KNOWN_KEYS_ARG, OBSERVER_ARG) if k in optional_args
}
# --- runtime state ---
# Active transport: "api" | "ssh" | "ssh_legacy" | "telnet"
self._transport: Optional[str] = None
self._transport: str | None = None
# CLI backend (SSH or Telnet)
self._device: Optional[ConnectHandler] = None
self._device: ConnectHandler | None = None
# REST API backend
self._api: Optional[ProcurveApiClient] = None
self._api: ProcurveApiClient | None = None
# Why each transport failed during the current open(), as "<transport>: <reason>"
self._attempts: List[str] = []
self._attempts: list[str] = []
# Config management state (CLI only)
self._candidate_config: Optional[str] = None
self._candidate_mode: Optional[str] = None # "merge" | "replace"
self._backup_config: Optional[str] = None
self._candidate_config: str | None = None
self._candidate_mode: str | None = None # "merge" | "replace"
self._backup_config: str | None = None
# ------------------------------------------------------------------
# Connection management
@@ -246,14 +242,14 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
self._device = None
self._transport = None
def is_alive(self) -> Dict[str, bool]:
def is_alive(self) -> dict[str, bool]:
"""Return transport-level liveness without writing to the channel."""
if self._api:
return {"is_alive": self._api.is_alive()}
if self._device:
try:
return {"is_alive": self._device.remote_conn.transport.is_active()}
except (AttributeError, socket.error, EOFError):
except (OSError, AttributeError, EOFError):
return {"is_alive": False}
return {"is_alive": False}
@@ -267,9 +263,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
logger.info("Probing REST API for %s (timeout=%ds)", self.hostname, probe_timeout)
# Probe without SSL verification — no credentials are sent during probing,
# so this is safe and avoids failing on self-signed certificates.
ver, proto = ProcurveApiClient.probe(
self.hostname, timeout=probe_timeout, ssl_verify=False
)
ver, proto = ProcurveApiClient.probe(self.hostname, timeout=probe_timeout, ssl_verify=False)
# Allow hint override (e.g. user knows the API version)
if self.api_version_hint and proto:
ver = self.api_version_hint
@@ -281,8 +275,8 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# Try connecting with the requested SSL setting first; if it fails due to a
# self-signed certificate (ssl_verify=True), transparently retry unverified.
error: Optional[Exception] = None
for ssl_verify in ([self.ssl_verify] if not self.ssl_verify else [True, False]):
error: Exception | None = None
for ssl_verify in [self.ssl_verify] if not self.ssl_verify else [True, False]:
client = ProcurveApiClient(
hostname=self.hostname,
username=self.username,
@@ -300,8 +294,13 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
continue
self._api = client
self._transport = "api"
logger.info("Connected to %s via REST API (%s %s, ssl_verify=%s)",
self.hostname, proto, ver, ssl_verify)
logger.info(
"Connected to %s via REST API (%s %s, ssl_verify=%s)",
self.hostname,
proto,
ver,
ssl_verify,
)
return True
logger.warning("REST API connect failed for %s — falling back to CLI", self.hostname)
@@ -314,8 +313,11 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
Explicit fields (port, timeout, disabled_algorithms) take precedence and are
excluded from the spread to avoid 'multiple values for keyword argument' errors.
"""
extra = {k: v for k, v in self.netmiko_optional_args.items()
if k not in ("port", "timeout", "disabled_algorithms")}
extra = {
k: v
for k, v in self.netmiko_optional_args.items()
if k not in ("port", "timeout", "disabled_algorithms")
}
return dict(
device_type=self.NETMIKO_DEVICE_TYPE,
host=self.hostname,
@@ -328,7 +330,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
**extra,
)
def _ssh_connect(self, legacy: bool = False, port: Optional[int] = None) -> ConnectHandler:
def _ssh_connect(self, legacy: bool = False, port: int | None = None) -> ConnectHandler:
"""A netmiko SSH session, its host key verified and reported (netork#1167).
Every SSH session of this driver opens here. A refused key raises
@@ -397,7 +399,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# CLI helpers
# ------------------------------------------------------------------
def _send_command(self, command: Union[str, List[str]]) -> str:
def _send_command(self, command: str | list[str]) -> str:
"""Send a CLI command and return stripped output.
When a list is supplied, commands are tried in order and the first
@@ -421,11 +423,15 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
for cmd in command:
last = _do(cmd)
# ProCurve says "Invalid input: …"; other firmware "% Invalid …"
if "Invalid input" not in last and "% Invalid" not in last and "Error" not in last:
if (
"Invalid input" not in last
and "% Invalid" not in last
and "Error" not in last
):
return last
return last
return _do(command)
except (socket.error, EOFError) as exc:
except (OSError, EOFError) as exc:
raise ConnectionClosedException(str(exc)) from exc
def _exec_prompt(self) -> str:
@@ -462,13 +468,13 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
read_timeout=self.timeout,
)
def _apply_config_lines(self, config_text: str) -> List[str]:
def _apply_config_lines(self, config_text: str) -> list[str]:
"""Push config lines to the device while in config mode.
Returns a list of error messages for rejected lines.
"""
ep = self._any_prompt()
errors: List[str] = []
errors: list[str] = []
for line in config_text.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith(("!", "#", ";")):
@@ -486,7 +492,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_facts
# ------------------------------------------------------------------
def get_facts(self) -> Dict:
def get_facts(self) -> dict:
"""Return general device information."""
if self._transport == "api":
return self._api.get_facts()
@@ -520,22 +526,20 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
"interface_list": iface_list,
}
def _get_interface_list(self) -> List[str]:
def _get_interface_list(self) -> list[str]:
"""Return sorted list of interface names from ``show interfaces brief``."""
out = self._send_command("show interfaces brief")
ifaces = parsers.parse_interfaces_brief(out)
return sorted(
ifaces.keys(),
key=lambda s: [
int(x) if x.isdigit() else x for x in re.split(r"(\d+)", s)
],
key=lambda s: [int(x) if x.isdigit() else x for x in re.split(r"(\d+)", s)],
)
# ------------------------------------------------------------------
# NAPALM: get_interfaces
# ------------------------------------------------------------------
def get_interfaces(self) -> Dict[str, Dict]:
def get_interfaces(self) -> dict[str, dict]:
"""Return a dictionary of interface details."""
if self._transport == "api":
return self._api.get_interfaces()
@@ -561,7 +565,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_interfaces_ip
# ------------------------------------------------------------------
def get_interfaces_ip(self) -> Dict[str, Dict]:
def get_interfaces_ip(self) -> dict[str, dict]:
"""Return IP addresses assigned to interfaces."""
if self._transport == "api":
return self._api.get_interfaces_ip()
@@ -573,7 +577,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_arp_table
# ------------------------------------------------------------------
def get_arp_table(self, vrf: str = "") -> List[Dict]:
def get_arp_table(self, vrf: str = "") -> list[dict]:
"""Return the ARP table."""
if self._transport == "api":
return self._api.get_arp_table(vrf)
@@ -585,7 +589,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_mac_address_table
# ------------------------------------------------------------------
def get_mac_address_table(self) -> List[Dict]:
def get_mac_address_table(self) -> list[dict]:
"""Return the MAC address table."""
if self._transport == "api":
return self._api.get_mac_address_table()
@@ -597,21 +601,19 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_lldp_neighbors
# ------------------------------------------------------------------
def get_lldp_neighbors(self) -> Dict[str, List[Dict]]:
def get_lldp_neighbors(self) -> dict[str, list[dict]]:
"""Return LLDP neighbor information."""
if self._transport == "api":
return self._api.get_lldp_neighbors()
out = self._send_command(
["show lldp info remote-device", "show lldp neighbors"]
)
out = self._send_command(["show lldp info remote-device", "show lldp neighbors"])
return parsers.parse_lldp_neighbors(out)
# ------------------------------------------------------------------
# NAPALM: get_lldp_neighbors_detail
# ------------------------------------------------------------------
def get_lldp_neighbors_detail(self, interface: str = "") -> Dict[str, List[Dict]]:
def get_lldp_neighbors_detail(self, interface: str = "") -> dict[str, list[dict]]:
"""Return detailed LLDP neighbor information."""
if self._transport == "api":
return self._api.get_lldp_neighbors_detail()
@@ -629,7 +631,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
def get_config(
self, retrieve: str = "all", full: bool = False, sanitized: bool = False
) -> Dict[str, str]:
) -> dict[str, str]:
"""Return device configuration."""
if self._transport == "api":
result = self._api.get_config()
@@ -652,9 +654,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
if retrieve in ("all", "running"):
running = self._send_command("show running-config")
if retrieve in ("all", "startup"):
startup = self._send_command(
["show startup-config", "show config"]
)
startup = self._send_command(["show startup-config", "show config"])
return {"running": running, "startup": startup, "candidate": candidate}
@@ -680,14 +680,12 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_environment
# ------------------------------------------------------------------
def get_environment(self) -> Dict:
def get_environment(self) -> dict:
"""Return environment information (temperature, fans, CPU, memory)."""
if self._transport == "api":
raise NotImplementedError("Environment data not available via REST API")
sysinfo = self._send_command(
["show system-information", "show system information"]
)
sysinfo = self._send_command(["show system-information", "show system information"])
env_out = self._send_command(["show environment", "show system"])
combined = sysinfo + "\n" + env_out
return parsers.parse_environment(combined)
@@ -696,25 +694,21 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_ntp_servers
# ------------------------------------------------------------------
def get_ntp_servers(self) -> Dict[str, Dict]:
def get_ntp_servers(self) -> dict[str, dict]:
"""Return configured NTP servers."""
if self._transport == "api":
return self._api.get_ntp_servers()
out = self._send_command(
["show ntp associations", "show ntp status", "show timesync"]
)
out = self._send_command(["show ntp associations", "show ntp status", "show timesync"])
return parsers.parse_ntp_servers(out)
# ------------------------------------------------------------------
# NAPALM: get_ntp_stats
# ------------------------------------------------------------------
def get_ntp_stats(self) -> List[Dict]:
def get_ntp_stats(self) -> list[dict]:
"""Return NTP peer statistics."""
out = self._send_command(
["show ntp associations", "show ntp status"]
)
out = self._send_command(["show ntp associations", "show ntp status"])
stats = []
for line in out.splitlines():
m = re.match(
@@ -746,7 +740,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_users
# ------------------------------------------------------------------
def get_users(self) -> Dict[str, Dict]:
def get_users(self) -> dict[str, dict]:
"""Return local users."""
out = self._send_command(["show local-users", "show users"])
return parsers.parse_users(out)
@@ -755,7 +749,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_snmp_information
# ------------------------------------------------------------------
def get_snmp_information(self) -> Dict:
def get_snmp_information(self) -> dict:
"""Return SNMP configuration."""
out = self._send_command(["show snmp-server", "show snmp"])
return parsers.parse_snmp_info(out)
@@ -764,7 +758,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: get_vlans (extended: tagged + untagged per VLAN)
# ------------------------------------------------------------------
def get_vlans(self) -> Dict[int, Dict]:
def get_vlans(self) -> dict[int, dict]:
"""Return VLAN information with tagged/untagged port separation.
This method extends the standard NAPALM ``get_vlans()`` return with
@@ -811,18 +805,18 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# Fallback: show vlans (name discovery) + show vlans <id> per VLAN
return self._get_vlans_via_show_vlans()
def _get_vlans_via_show_vlans(self) -> Dict[int, Dict]:
def _get_vlans_via_show_vlans(self) -> dict[int, dict]:
"""Fallback VLAN discovery via ``show vlans`` + per-VLAN detail queries."""
brief_out = self._send_command("show vlans")
vlan_names = parsers.parse_vlans_brief(brief_out)
result: Dict[int, Dict] = {}
result: dict[int, dict] = {}
for vid, name in vlan_names.items():
detail_out = self._send_command(f"show vlans {vid}")
ports = parsers.parse_vlan_ports_detail(detail_out)
tagged = ports["tagged"]
untagged = ports["untagged"]
interfaces: List[str] = []
interfaces: list[str] = []
for p in untagged + tagged:
if p not in interfaces:
interfaces.append(p)
@@ -839,7 +833,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# Custom: get_port_pvids (PVID per port, derived from get_vlans)
# ------------------------------------------------------------------
def get_port_pvids(self) -> Dict[str, int]:
def get_port_pvids(self) -> dict[str, int]:
"""Return the PVID (native / access VLAN) for every switch port.
On ProCurve, a port is untagged in exactly **one** VLAN — that VLAN's
@@ -906,9 +900,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
lines.append("exit")
errors = self._apply_config_lines("\n".join(lines))
if errors:
raise CommandErrorException(
f"set_vlan({vlan_id}) errors: {errors}"
)
raise CommandErrorException(f"set_vlan({vlan_id}) errors: {errors}")
finally:
self._exit_config_mode()
self._save_config()
@@ -917,7 +909,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# LAG / trunk membership
# ------------------------------------------------------------------
def set_lag_members(self, lag_name: str, members: List[str]) -> None:
def set_lag_members(self, lag_name: str, members: list[str]) -> None:
"""Set the full member-port list of a LAG/trunk group.
Diffs ``members`` against the trunk's current members (as reported
@@ -939,7 +931,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
mode = current.get("lag_mode") or "trunk"
trk_name = lag_name.lower()
commands: List[str] = []
commands: list[str] = []
if to_remove:
commands.append(f"no trunk {','.join(to_remove)}")
if to_add:
@@ -1024,7 +1016,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
mode = config.get("mode")
self._enter_config_mode()
try:
lines: List[str] = []
lines: list[str] = []
if "enabled" in config or "description" in config:
lines.append(f"interface {interface}")
if "enabled" in config:
@@ -1049,9 +1041,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
if lines:
errors = self._apply_config_lines("\n".join(lines))
if errors:
raise CommandErrorException(
f"set_interface({interface}) errors: {errors}"
)
raise CommandErrorException(f"set_interface({interface}) errors: {errors}")
finally:
self._exit_config_mode()
self._save_config()
@@ -1068,9 +1058,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
try:
errors = self._apply_config_lines(f"no vlan {vlan_id}")
if errors:
raise CommandErrorException(
f"delete_vlan({vlan_id}) errors: {errors}"
)
raise CommandErrorException(f"delete_vlan({vlan_id}) errors: {errors}")
finally:
self._exit_config_mode()
self._save_config()
@@ -1079,7 +1067,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# PoE
# ------------------------------------------------------------------
def get_poe_status(self) -> Dict[str, Dict]:
def get_poe_status(self) -> dict[str, dict]:
"""Return PoE configuration for all ports, keyed by interface name.
Only available via the REST API transport.
@@ -1088,7 +1076,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
return self._api.get_poe_ports()
raise NotImplementedError("PoE status is only available via the REST API")
def set_poe(self, interface: str, config: Dict) -> None:
def set_poe(self, interface: str, config: dict) -> None:
"""Update PoE configuration for a single interface."""
if self._transport != "api":
raise NotImplementedError("PoE configuration is only available via the REST API")
@@ -1125,12 +1113,17 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
size: int = 100,
count: int = 5,
vrf: str = "",
) -> Dict:
) -> dict:
"""Execute a ping from the device."""
if self._transport == "api":
return self._api.ping(
destination, source=source, ttl=ttl,
timeout=timeout, size=size, count=count, vrf=vrf,
destination,
source=source,
ttl=ttl,
timeout=timeout,
size=size,
count=count,
vrf=vrf,
)
# ProCurve CLI ping: "ping <ip> repetitions <n>"
@@ -1144,11 +1137,11 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# NAPALM: cli
# ------------------------------------------------------------------
def cli(self, commands: List[str], encoding: str = "text") -> Dict[str, str]:
def cli(self, commands: list[str], encoding: str = "text") -> dict[str, str]:
"""Execute raw CLI commands and return output dict."""
if self._transport == "api":
# ArubaOS REST API supports CLI via POST /rest/<ver>/cli
results: Dict[str, str] = {}
results: dict[str, str] = {}
for cmd in commands:
resp = self._api.post("cli", json={"cmd": cmd})
if resp.ok:
@@ -1166,9 +1159,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
def _get_running_config(self) -> str:
return self._send_command("show running-config")
def load_merge_candidate(
self, filename: Optional[str] = None, config: Optional[str] = None
) -> None:
def load_merge_candidate(self, filename: str | None = None, config: str | None = None) -> None:
if self._transport == "api":
raise MergeConfigException(
"Merge config is not supported via REST API transport. Use SSH."
@@ -1176,7 +1167,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
super().load_merge_candidate(filename, config)
def load_replace_candidate(
self, filename: Optional[str] = None, config: Optional[str] = None
self, filename: str | None = None, config: str | None = None
) -> None:
if self._transport == "api":
raise ReplaceConfigException(
@@ -1184,7 +1175,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
)
super().load_replace_candidate(filename, config)
def commit_config(self, message: str = "", revert_in: Optional[int] = None) -> None:
def commit_config(self, message: str = "", revert_in: int | None = None) -> None:
"""Apply the candidate configuration to the device."""
if self._candidate_config is None:
raise MergeConfigException("No candidate config loaded.")
@@ -1201,9 +1192,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
raise MergeConfigException(f"Error applying config: {exc}") from exc
if errors:
raise MergeConfigException(
"The following lines were rejected:\n" + "\n".join(errors)
)
raise MergeConfigException("The following lines were rejected:\n" + "\n".join(errors))
self._save_config()
self._candidate_config = None
@@ -1224,11 +1213,15 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# Use a single command with short read_timeout to avoid blocking the poll.
try:
out = self._device.send_command(
"show snmp-server",
expect_string=rf"{re.escape(self._device.base_prompt)}[>#]",
read_timeout=10,
).strip() if self._device else ""
out = (
self._device.send_command(
"show snmp-server",
expect_string=rf"{re.escape(self._device.base_prompt)}[>#]",
read_timeout=10,
).strip()
if self._device
else ""
)
except Exception:
return None
if not out:
@@ -1256,7 +1249,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# SNMP agent (napalm-device-types SnmpAgentMixin): the caller's values
# ------------------------------------------------------------------
def configure_snmp_agent(self, settings: Dict[str, Any]) -> Dict[str, Any]:
def configure_snmp_agent(self, settings: dict[str, Any]) -> dict[str, Any]:
"""The agent is built in; configure community, contact and location, then save.
The REST API has no writable SNMP endpoint, so over REST a temporary SSH
@@ -1296,7 +1289,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
pass
self._api, self._device, self._transport = old_api, old_device, old_transport
def _snmp_configure_cli(self, settings: Dict[str, Any]) -> Dict[str, Any]:
def _snmp_configure_cli(self, settings: dict[str, Any]) -> dict[str, Any]:
community = settings["community"]
if not _CLI_WORD.match(community):
raise ValueError("the community must be one word without quotes or #")
@@ -1317,12 +1310,12 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
return {"ok": True, "output": "community (read-only), contact and location set"}
@staticmethod
def _snmp_sources_note(settings: Dict[str, Any]) -> Dict[str, Any]:
def _snmp_sources_note(settings: dict[str, Any]) -> dict[str, Any]:
sources = ", ".join(settings.get("allowed_sources", [])) or "none given"
note = f"no host firewall on the switch; sources not enforced ({sources})"
return {"ok": True, "output": note}
def _snmp_save_and_verify(self, settings: Dict[str, Any]) -> Dict[str, Any]:
def _snmp_save_and_verify(self, settings: dict[str, Any]) -> dict[str, Any]:
self._save_config()
shown = self._send_command("show snmp-server")
if re.search(rf"^\s*{re.escape(settings['community'])}\s", shown, re.M):
@@ -1334,6 +1327,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
# Helpers
# ---------------------------------------------------------------------------
def _is_float(s: str) -> bool:
try:
float(s)
@@ -1342,7 +1336,7 @@ def _is_float(s: str) -> bool:
return False
def _parse_ping_output(output: str, count: int) -> Dict:
def _parse_ping_output(output: str, count: int) -> dict:
"""Parse ProCurve CLI ping output into NAPALM ping result dict.
ProCurve ping output::
@@ -1356,7 +1350,8 @@ def _parse_ping_output(output: str, count: int) -> Dict:
# Packet statistics line
m = re.search(
r"(\d+)\s+packets\s+transmitted,\s+(\d+)\s+packets\s+received",
output, re.I,
output,
re.I,
)
if not m:
# Check for total failure
@@ -1399,4 +1394,3 @@ def _parse_ping_output(output: str, count: int) -> Dict:
"results": results,
}
}