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
+117
-123
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user