fix: resolve the node the connection landed on, not the first cluster member
_resolve_node() took the first entry of GET /nodes. In a cluster that lists every member, so a node polled without an explicit `node` driver argument talked to whichever member came first: pve-dual reported pve-02's name and VMs, and netOrk's VM sync moved pve-02's VM devices over to it. Resolve through GET /cluster/status instead: the entry marked local, then a match by IP or (short) name, then the sole node of a standalone host, and otherwise raise rather than guess. The lookup no longer swallows API errors either. A TLS verification failure used to leave the IP as the node name, so open() succeeded and every getter failed quietly while the poll reported success with empty data. It now surfaces as a ConnectionException from open(). Refs NetOrk/netork#417, NetOrk/netork#418
This commit is contained in:
+33
-19
@@ -183,30 +183,44 @@ class ProxmoxDriver(
|
||||
) from exc
|
||||
|
||||
def _resolve_node(self) -> str:
|
||||
"""Resolve the node name from the hostname or optional_args."""
|
||||
"""Resolve the PVE node this connection talks to.
|
||||
|
||||
In a cluster, ``GET /nodes`` lists every member, so its first entry is
|
||||
just some node, not necessarily the one at ``self.hostname``. That used
|
||||
to be taken blindly, and a device then reported another node's name and
|
||||
VMs. ``GET /cluster/status`` marks the node the session landed on with
|
||||
``local: 1``; failing that, the node is matched by IP or name.
|
||||
|
||||
API errors propagate so that ``open()`` fails with the real cause (e.g.
|
||||
a TLS verification error) rather than succeeding on a guessed name.
|
||||
"""
|
||||
if self._node:
|
||||
self._node_name = self._node
|
||||
return self._node_name
|
||||
|
||||
# Try the node's hostname via API cluster/resources
|
||||
try:
|
||||
nodes = self._api.nodes.get()
|
||||
# Match by hostname or IP
|
||||
for n in nodes:
|
||||
n_node = n.get("node", "")
|
||||
if n_node:
|
||||
# First match: the node exists in the cluster
|
||||
self._node_name = n_node
|
||||
return self._node_name
|
||||
except Exception:
|
||||
pass
|
||||
members = [
|
||||
s for s in (self._api.cluster.status.get() or []) if s.get("type") == "node"
|
||||
]
|
||||
short_host = self.hostname.split(".")[0]
|
||||
for pick in (
|
||||
lambda s: s.get("local"),
|
||||
lambda s: s.get("ip") == self.hostname,
|
||||
lambda s: s.get("name") in (self.hostname, short_host),
|
||||
):
|
||||
match = next((s for s in members if pick(s)), None)
|
||||
if match:
|
||||
self._node_name = match["name"]
|
||||
return self._node_name
|
||||
|
||||
# Fallback: use the configured hostname as node name
|
||||
# (may not match the PVE node name — SSH-based methods will fail,
|
||||
# but API methods that target a specific node name require correct
|
||||
# resolution)
|
||||
self._node_name = self.hostname
|
||||
return self._node_name
|
||||
names = [n.get("node") for n in (self._api.nodes.get() or []) if n.get("node")]
|
||||
if len(names) == 1:
|
||||
self._node_name = names[0]
|
||||
return self._node_name
|
||||
|
||||
raise ConnectionException(
|
||||
f"Cannot tell which PVE node {self.hostname} is among {names}; "
|
||||
f"set the 'node' driver argument"
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the connection."""
|
||||
|
||||
Reference in New Issue
Block a user