mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 18:45:53 +00:00
Prefer live monitor connection state in diagnostics
Back-port v5 fix d310c257a to v6, adapted to v6's connection-status key
format. computeDiagnostics now merges a failed PVE/PBS diagnostics probe
with the monitor's live connection state: if the long-running poller still
reports the instance connected, a transient probe failure (network blip,
TLS re-check) no longer flips it to 'disconnected' in the UI. Uses v6's
'pve-<name>'/'pbs-<name>' status keys (v5 used a bare node name, which
would not match in v6). Adds a merge-logic regression test.
This commit is contained in:
@@ -795,6 +795,33 @@ func writeDiagnosticsResponse(w http.ResponseWriter, diag DiagnosticsInfo, cache
|
||||
}
|
||||
}
|
||||
|
||||
// diagnosticsMonitorConnectionStatus looks up the live monitor connection state
|
||||
// for a node by its connection-status key (e.g. "pve-<name>"/"pbs-<name>").
|
||||
func diagnosticsMonitorConnectionStatus(m *monitoring.Monitor, key string) (bool, bool) {
|
||||
if m == nil || strings.TrimSpace(key) == "" {
|
||||
return false, false
|
||||
}
|
||||
statuses := m.GetConnectionStatuses()
|
||||
connected, ok := statuses[key]
|
||||
return connected, ok
|
||||
}
|
||||
|
||||
// mergeDiagnosticsConnection prevents a transient diagnostics-probe failure from
|
||||
// reporting an instance as disconnected when the long-running monitor connection
|
||||
// still reports it connected.
|
||||
func mergeDiagnosticsConnection(probeConnected bool, probeError string, monitorConnected bool, hasMonitorStatus bool) (bool, string) {
|
||||
if probeConnected {
|
||||
return true, probeError
|
||||
}
|
||||
if !hasMonitorStatus || !monitorConnected {
|
||||
return false, probeError
|
||||
}
|
||||
if strings.TrimSpace(probeError) == "" {
|
||||
return true, ""
|
||||
}
|
||||
return true, "Live diagnostics probe failed, but the monitor still reports this instance connected: " + probeError
|
||||
}
|
||||
|
||||
func (r *Router) computeDiagnostics(ctx context.Context) DiagnosticsInfo {
|
||||
diag := EmptyDiagnosticsInfo()
|
||||
|
||||
@@ -833,6 +860,7 @@ func (r *Router) computeDiagnostics(ctx context.Context) DiagnosticsInfo {
|
||||
Host: node.Host,
|
||||
Type: "pve",
|
||||
}
|
||||
monitorConnected, hasMonitorStatus := diagnosticsMonitorConnectionStatus(r.monitor, "pve-"+node.Name)
|
||||
|
||||
// Determine auth method (sanitized - don't expose actual values)
|
||||
if node.TokenName != "" && node.TokenValue != "" {
|
||||
@@ -860,12 +888,14 @@ func (r *Router) computeDiagnostics(ctx context.Context) DiagnosticsInfo {
|
||||
nodeDiag.Connected = false
|
||||
nodeDiag.Error = "Failed to initialize connection"
|
||||
log.Error().Err(err).Str("node", node.Name).Msg("Diagnostics: Proxmox client init failed")
|
||||
nodeDiag.Connected, nodeDiag.Error = mergeDiagnosticsConnection(nodeDiag.Connected, nodeDiag.Error, monitorConnected, hasMonitorStatus)
|
||||
} else {
|
||||
nodes, err := client.GetNodes(ctx)
|
||||
if err != nil {
|
||||
nodeDiag.Connected = false
|
||||
nodeDiag.Error = "Failed to connect to Proxmox API"
|
||||
log.Error().Err(err).Str("node", node.Name).Msg("Diagnostics: Proxmox API connection failed")
|
||||
nodeDiag.Connected, nodeDiag.Error = mergeDiagnosticsConnection(nodeDiag.Connected, nodeDiag.Error, monitorConnected, hasMonitorStatus)
|
||||
} else {
|
||||
nodeDiag.Connected = true
|
||||
|
||||
@@ -903,6 +933,7 @@ func (r *Router) computeDiagnostics(ctx context.Context) DiagnosticsInfo {
|
||||
Name: pbsNode.Name,
|
||||
Host: pbsNode.Host,
|
||||
}
|
||||
monitorConnected, hasMonitorStatus := diagnosticsMonitorConnectionStatus(r.monitor, "pbs-"+pbsNode.Name)
|
||||
|
||||
testCfg := pbs.ClientConfig{
|
||||
Host: pbsNode.Host,
|
||||
@@ -919,11 +950,13 @@ func (r *Router) computeDiagnostics(ctx context.Context) DiagnosticsInfo {
|
||||
pbsDiag.Connected = false
|
||||
pbsDiag.Error = "Failed to initialize connection"
|
||||
log.Error().Err(err).Str("pbs", pbsNode.Name).Msg("Diagnostics: PBS client init failed")
|
||||
pbsDiag.Connected, pbsDiag.Error = mergeDiagnosticsConnection(pbsDiag.Connected, pbsDiag.Error, monitorConnected, hasMonitorStatus)
|
||||
} else {
|
||||
if version, err := client.GetVersion(ctx); err != nil {
|
||||
pbsDiag.Connected = false
|
||||
pbsDiag.Error = "Connection established but version check failed"
|
||||
log.Error().Err(err).Str("pbs", pbsNode.Name).Msg("Diagnostics: PBS version check failed")
|
||||
pbsDiag.Connected, pbsDiag.Error = mergeDiagnosticsConnection(pbsDiag.Connected, pbsDiag.Error, monitorConnected, hasMonitorStatus)
|
||||
} else {
|
||||
pbsDiag.Connected = true
|
||||
pbsDiag.Details = &PBSDetails{Version: version.Version}
|
||||
|
||||
@@ -765,3 +765,36 @@ func TestResolveGroupName(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDiagnosticsConnection(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
probeConnected bool
|
||||
probeError string
|
||||
monitorConnected bool
|
||||
hasMonitorStatus bool
|
||||
wantConnected bool
|
||||
wantErrContains string
|
||||
}{
|
||||
{"probe connected stays connected", true, "", false, false, true, ""},
|
||||
{"probe failed but monitor connected", false, "timeout", true, true, true, "monitor still reports"},
|
||||
{"probe failed and no monitor status", false, "timeout", false, false, false, "timeout"},
|
||||
{"probe failed and monitor disconnected", false, "timeout", false, true, false, "timeout"},
|
||||
{"probe failed empty error, monitor connected", false, "", true, true, true, ""},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotConnected, gotErr := mergeDiagnosticsConnection(tc.probeConnected, tc.probeError, tc.monitorConnected, tc.hasMonitorStatus)
|
||||
if gotConnected != tc.wantConnected {
|
||||
t.Fatalf("connected = %v, want %v", gotConnected, tc.wantConnected)
|
||||
}
|
||||
if tc.wantErrContains == "" {
|
||||
if gotErr != "" {
|
||||
t.Fatalf("expected empty error, got %q", gotErr)
|
||||
}
|
||||
} else if !strings.Contains(gotErr, tc.wantErrContains) {
|
||||
t.Fatalf("error %q does not contain %q", gotErr, tc.wantErrContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user