fix: four real defects the fourteen failing tests were pointing at
Closes netork#115. The suite had been red long enough that it stopped being read. Four of the fourteen failures were the tests being right. `interfaces_mixin.py` used `re.match` without importing `re`, so `get_mac_address_table` raised NameError against any node with a Linux bridge. The tests never reached that line: they mocked the API call underneath `_exec_ssh_command`, which takes two positional arguments where the doubles accepted one, and which base64-wraps the command — so a fixture keyed on "bridge fdb" appearing in the text matched nothing and the helper returned "". They mock `_exec_ssh_command` itself now, which is the driver's own seam. `is_alive` called `_resolve_node()`, which returns early without touching the API whenever a node was configured through optional_args. A dead connection reported itself alive. It probes `GET /version` now. The documented `realm` optional_arg was read into `self._realm` in `__init__` and then never used. Proxmox authenticates against "<user>@<realm>" and rejects a bare username, so the option had no effect and callers had to know to type the realm themselves. `get_vlans` filtered out entries with no member ports on one return path while the OVS path returned them, so a configured SDN VNet was visible or invisible depending on which branch ran. A VNet exists on the node whether or not anything is attached to it, and netOrk's VLAN discovery reads this. `get_ipv6_neighbors_table` was simply missing and fell through to NAPALM's stub; it is implemented against `ip -6 neigh show`, dropping FAILED entries. The rest were stale tests. The DNS fixture put an FQDN where a search domain belongs, which made `get_facts` build "pve1.pve1.example.com" and look like a driver bug. The LLDP fixture was a simplified shape that real `lldpcli show neighbors summary` does not produce — the parser matches on the ", via: LLDP" that follows the interface name. And `test_bridge_vlan_show_parsing` covered a fallback that was replaced by VM-config scanning, asserting an "interfaces" key this method has never returned; it is now a test of the fallback that exists.
This commit is contained in:
@@ -141,9 +141,18 @@ class ProxmoxDriver(
|
||||
# The openssh backend tunnels all kwargs through to
|
||||
# openssh_wrapper.CommandBaseSession, which does not
|
||||
# accept password/verify_ssl/token params.
|
||||
# Proxmox authenticates against "<user>@<realm>" and rejects a bare
|
||||
# username outright. A caller who typed a realm keeps it; one who
|
||||
# did not gets self._realm, which is what the documented `realm`
|
||||
# optional_arg is for -- it was read in __init__ and then never
|
||||
# used, so the option had no effect and a bare username failed.
|
||||
user = self.username or ""
|
||||
if user and "@" not in user:
|
||||
user = f"{user}@{self._realm}"
|
||||
|
||||
kwargs: _JsonDict = {
|
||||
"host": self.hostname,
|
||||
"user": self.username,
|
||||
"user": user,
|
||||
"password": self.password,
|
||||
"port": self._port,
|
||||
"verify_ssl": self._verify_ssl,
|
||||
@@ -210,11 +219,17 @@ class ProxmoxDriver(
|
||||
self._ssh_client = None
|
||||
|
||||
def is_alive(self) -> _JsonDict:
|
||||
"""Return connection liveness."""
|
||||
"""Return connection liveness.
|
||||
|
||||
Probes ``GET /version``, the cheapest endpoint that proves the session
|
||||
still authenticates. It used to call ``_resolve_node()``, which returns
|
||||
early without touching the API whenever a node was configured via
|
||||
optional_args — so a dead connection reported itself alive.
|
||||
"""
|
||||
alive = False
|
||||
if self._api:
|
||||
try:
|
||||
self._resolve_node()
|
||||
self._api.version.get()
|
||||
alive = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from napalm_proxmox import utils
|
||||
@@ -113,6 +114,47 @@ class ProxmoxInterfaceMixin:
|
||||
}
|
||||
return result
|
||||
|
||||
def get_ipv6_neighbors_table(self) -> list[_JsonDict]:
|
||||
"""Return the IPv6 neighbour table, read via ``ip -6 neigh show``.
|
||||
|
||||
The IPv6 counterpart to :meth:`get_arp_table`. Proxmox exposes no REST
|
||||
endpoint for it, so it goes through the node exec helper like the ARP
|
||||
table does.
|
||||
|
||||
Entries in FAILED state are dropped: they record an address the kernel
|
||||
could not resolve, so there is no neighbour to report.
|
||||
"""
|
||||
raw = self._exec_ssh_command("ip -6 neigh show 2>/dev/null || true")
|
||||
if not raw:
|
||||
return []
|
||||
|
||||
entries: list[_JsonDict] = []
|
||||
for line in raw.splitlines():
|
||||
parts = line.split()
|
||||
# "<ip> dev <iface> lladdr <mac> <STATE>" — an entry without lladdr
|
||||
# never resolved and carries no neighbour.
|
||||
if len(parts) < 6 or "lladdr" not in parts:
|
||||
continue
|
||||
state = parts[-1].upper()
|
||||
if state == "FAILED":
|
||||
continue
|
||||
try:
|
||||
iface = parts[parts.index("dev") + 1]
|
||||
mac = parts[parts.index("lladdr") + 1]
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
entries.append(
|
||||
{
|
||||
"interface": iface,
|
||||
"mac": utils.normalize_mac(mac),
|
||||
"ip": parts[0],
|
||||
# `ip neigh` reports no age; NAPALM's shape requires the key.
|
||||
"age": 0.0,
|
||||
"state": state,
|
||||
}
|
||||
)
|
||||
return entries
|
||||
|
||||
def get_arp_table(self, vrf: str = "") -> list[_JsonDict]:
|
||||
"""Return ARP table.
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ class ProxmoxSDNMixin:
|
||||
OVSIntPort (access ports with ovs_tag) → untagged membership, and
|
||||
OVSPort / OVSBridge (trunk ports) → tagged membership.
|
||||
|
||||
Falls back to ``bridge vlan show`` for classic Linux-bridge nodes.
|
||||
Without OVS ports, VLAN membership is derived from the ``tag=`` values
|
||||
in each VM's and container's ``netN`` config (:meth:`_get_vm_vlan_tags`).
|
||||
"""
|
||||
result: dict[str, _JsonDict] = {}
|
||||
node_network = self._get_node_network()
|
||||
@@ -113,10 +114,12 @@ class ProxmoxSDNMixin:
|
||||
if bridge not in entry["untagged"]:
|
||||
entry["untagged"].append(bridge)
|
||||
|
||||
return {
|
||||
vid: entry for vid, entry in result.items()
|
||||
if entry.get("tagged") or entry.get("untagged")
|
||||
}
|
||||
# Deliberately unfiltered. This used to drop entries with no member
|
||||
# ports, which hid every configured SDN VNet that no VM happened to be
|
||||
# attached to — and contradicted the OVS branch above, which returns
|
||||
# empty-membership VLANs. A VNet exists on the node whether or not
|
||||
# anything currently uses it, and netOrk's VLAN discovery reads this.
|
||||
return result
|
||||
|
||||
def _get_vm_vlan_tags(self) -> dict[str, set[str]]:
|
||||
"""Return ``{vlan_tag: {bridge_names}}`` derived from VM/container net configs.
|
||||
|
||||
Reference in New Issue
Block a user