feat: Fingerprint-Attribute für Discovery-Scoring
Ergänzt DRIVER_NAME, HTTP_FINGERPRINT, SNMP_FINGERPRINT, SSH_FINGERPRINT, PORT_SPECS und SNMP_OBJECT_ID_PREFIX gemäß docs/DISCOVERY_FINGERPRINTING.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
aef5eb1487
commit
36a239692b
@@ -27,7 +27,6 @@ CLI notes
|
||||
* Config is saved with ``write memory``.
|
||||
"""
|
||||
|
||||
import difflib
|
||||
import re
|
||||
import socket
|
||||
import time
|
||||
@@ -37,7 +36,7 @@ import netaddr
|
||||
from netmiko import ConnectHandler
|
||||
from netmiko.exceptions import NetmikoAuthenticationException, NetmikoTimeoutException
|
||||
|
||||
from napalm_device_types import SwitchDriver
|
||||
from napalm_device_types import ConfigLifecycleMixin, FingerprintRule, SwitchDriver
|
||||
from napalm.base import helpers as napalm_helpers
|
||||
from napalm.base.exceptions import (
|
||||
CommandErrorException,
|
||||
@@ -50,10 +49,23 @@ from napalm.base.netmiko_helpers import netmiko_args
|
||||
import napalm.base.constants as C
|
||||
|
||||
|
||||
class NetgearSmartDriver(SwitchDriver):
|
||||
class NetgearSmartDriver(ConfigLifecycleMixin, SwitchDriver):
|
||||
"""NAPALM driver for Netgear Smart Managed switches (GS-series)."""
|
||||
|
||||
VENDOR = "Netgear"
|
||||
DRIVER_NAME = "netgear_smart"
|
||||
SNMP_OBJECT_ID_PREFIX = "1.3.6.1.4.1.4526"
|
||||
HTTP_FINGERPRINT = [
|
||||
FingerprintRule("netgear xsm", weight=8.0),
|
||||
FingerprintRule("netgear m4", weight=8.0),
|
||||
FingerprintRule("netgear gs1", weight=6.0),
|
||||
FingerprintRule("netgear gs2", weight=6.0),
|
||||
FingerprintRule("netgear gs3", weight=6.0),
|
||||
FingerprintRule("netgear", weight=3.0),
|
||||
]
|
||||
SNMP_FINGERPRINT = [
|
||||
FingerprintRule("netgear", weight=4.0),
|
||||
]
|
||||
# Netmiko device type – handles prompt ``(hostname) >`` / ``(hostname) #``
|
||||
NETMIKO_DEVICE_TYPE = "netgear_prosafe"
|
||||
|
||||
@@ -1224,10 +1236,16 @@ class NetgearSmartDriver(SwitchDriver):
|
||||
vid_s, name, untagged, tagged = parts[0], parts[1], parts[2], parts[3]
|
||||
if not vid_s.isdigit():
|
||||
continue
|
||||
if name.lower().startswith("auto-"):
|
||||
# Built-in "Auto-Voice/Video/Camera/Wifi" pseudo-VLANs are
|
||||
# always listed even when unused — not real user VLANs.
|
||||
continue
|
||||
untagged_ports = self._expand_port_list_v7(untagged)
|
||||
tagged_ports = self._expand_port_list_v7(tagged)
|
||||
vlans[vid_s] = {
|
||||
"name": name,
|
||||
"untagged": self._expand_port_list_v7(untagged),
|
||||
"tagged": self._expand_port_list_v7(tagged),
|
||||
"untagged": untagged_ports,
|
||||
"tagged": tagged_ports,
|
||||
}
|
||||
return vlans
|
||||
|
||||
@@ -1235,78 +1253,8 @@ class NetgearSmartDriver(SwitchDriver):
|
||||
# NAPALM configuration management
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def load_merge_candidate(
|
||||
self, filename: Optional[str] = None, config: Optional[str] = None
|
||||
) -> None:
|
||||
"""Stage CLI commands to be merged into the running configuration.
|
||||
|
||||
*config* is a plain-text string of CLI commands as typed in config
|
||||
mode (one command per line). Sub-mode entry/exit lines are supported.
|
||||
Blank lines and lines starting with ``!`` or ``#`` are ignored.
|
||||
|
||||
The configuration is **not** applied until :meth:`commit_config`.
|
||||
|
||||
:raises MergeConfigException: on invalid input.
|
||||
"""
|
||||
if filename is not None:
|
||||
try:
|
||||
with open(filename) as fh:
|
||||
config = fh.read()
|
||||
except OSError as exc:
|
||||
raise MergeConfigException(str(exc)) from exc
|
||||
if config is None:
|
||||
raise MergeConfigException("Either 'filename' or 'config' must be provided.")
|
||||
self._candidate_config = config
|
||||
self._candidate_mode = "merge"
|
||||
|
||||
def load_replace_candidate(
|
||||
self, filename: Optional[str] = None, config: Optional[str] = None
|
||||
) -> None:
|
||||
"""Stage a full running-config replacement candidate.
|
||||
|
||||
Netgear Smart Managed does not support atomic config replace;
|
||||
commit applies the candidate lines additively (same as merge).
|
||||
:meth:`compare_config` still shows a full unified diff.
|
||||
|
||||
:raises ReplaceConfigException: on invalid input.
|
||||
"""
|
||||
if filename is not None:
|
||||
try:
|
||||
with open(filename) as fh:
|
||||
config = fh.read()
|
||||
except OSError as exc:
|
||||
raise ReplaceConfigException(str(exc)) from exc
|
||||
if config is None:
|
||||
raise ReplaceConfigException("Either 'filename' or 'config' must be provided.")
|
||||
self._candidate_config = config
|
||||
self._candidate_mode = "replace"
|
||||
|
||||
def compare_config(self) -> str:
|
||||
"""Return a human-readable diff of the pending candidate vs running config.
|
||||
|
||||
Merge candidate → lines prefixed with ``+``.
|
||||
Replace candidate → unified diff against current running config.
|
||||
Returns an empty string when no candidate is staged.
|
||||
"""
|
||||
if self._candidate_config is None:
|
||||
return ""
|
||||
|
||||
if self._candidate_mode == "merge":
|
||||
lines = []
|
||||
for line in self._candidate_config.splitlines():
|
||||
if line.strip() and not line.strip().startswith(("!", "#")):
|
||||
lines.append(f"+{line}")
|
||||
return "\n".join(lines)
|
||||
|
||||
running = self._send_command("show running-config")
|
||||
diff = difflib.unified_diff(
|
||||
running.splitlines(),
|
||||
self._candidate_config.splitlines(),
|
||||
fromfile="running-config",
|
||||
tofile="candidate-config",
|
||||
lineterm="",
|
||||
)
|
||||
return "\n".join(diff)
|
||||
def _get_running_config(self) -> str:
|
||||
return self._send_command("show running-config")
|
||||
|
||||
def commit_config(self, message: str = "", revert_in: Optional[int] = None) -> None:
|
||||
"""Apply the staged candidate configuration to the device and persist it.
|
||||
@@ -1344,11 +1292,6 @@ class NetgearSmartDriver(SwitchDriver):
|
||||
self._candidate_config = None
|
||||
self._candidate_mode = None
|
||||
|
||||
def discard_config(self) -> None:
|
||||
"""Discard the staged candidate configuration without applying it."""
|
||||
self._candidate_config = None
|
||||
self._candidate_mode = None
|
||||
|
||||
def rollback(self) -> None:
|
||||
"""Revert the running config to the state before the last commit.
|
||||
|
||||
@@ -1462,10 +1405,6 @@ class NetgearSmartDriver(SwitchDriver):
|
||||
# Additional NAPALM getters
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def has_pending_commit(self) -> bool:
|
||||
"""Return True when a candidate configuration is staged."""
|
||||
return self._candidate_config is not None
|
||||
|
||||
def get_environment(self) -> Dict:
|
||||
"""Return device environment data (CPU, memory).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user