From c9b4f7e88bea393070ae17036e568ae3f7cade8f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 14 Nov 2025 23:38:44 +0000 Subject: [PATCH] Fix incorrect temperature data during cluster initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During cluster startup, nodes were temporarily using the primary cluster endpoint for temperature collection before cluster metadata validation completed. This caused all nodes to show the same (incorrect) temperature values for ~4 minutes until validation finished and per-node endpoints were established. Example: minipc would show delly's temperature (90°C) instead of its own (50°C) from startup until cluster validation completed. Root cause: - Temperature collection started immediately at startup - Cluster endpoint validation happened asynchronously - Code fell back to primary endpoint when ClusterEndpoints was empty - All nodes used same endpoint, got same temperature data Fix: Skip temperature collection for cluster nodes until: 1. ClusterEndpoints array is populated (validation complete) 2. Node's specific endpoint is found in the cluster metadata This ensures correct temperature data from the very first collection, maintaining data integrity during startup. When persisted config exists, endpoints are available immediately so no delay occurs. For new clusters, temperature collection begins once validation completes (~30s). Preserves Pulse's correctness guarantee: users can trust metrics immediately after restart without waiting for "warm-up" period. --- internal/monitoring/monitor.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index 8b3af49..3b95747 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -5796,17 +5796,42 @@ func (m *Monitor) pollPVEInstance(ctx context.Context, instanceName string, clie // Determine SSH hostname to use (most robust approach): // Prefer the resolved host for this node, with cluster overrides when available. sshHost := modelNode.Host + foundNodeEndpoint := false if modelNode.IsClusterMember && instanceCfg.IsCluster { + // For cluster members, wait until we have validated endpoints + // This prevents collecting wrong temperature data during initialization + if len(instanceCfg.ClusterEndpoints) == 0 { + tempCancel() + log.Debug(). + Str("node", node.Node). + Str("instance", instanceCfg.Name). + Msg("Skipping temperature collection - cluster endpoints not yet validated") + continue + } + hasFingerprint := instanceCfg.Fingerprint != "" for _, ep := range instanceCfg.ClusterEndpoints { if strings.EqualFold(ep.NodeName, node.Node) { if effective := clusterEndpointEffectiveURL(ep, instanceCfg.VerifySSL, hasFingerprint); effective != "" { sshHost = effective + foundNodeEndpoint = true } break } } + + // If this node is a cluster member but we didn't find its specific endpoint, + // skip temperature collection to avoid using wrong endpoint + if !foundNodeEndpoint { + tempCancel() + log.Debug(). + Str("node", node.Node). + Str("instance", instanceCfg.Name). + Int("endpointCount", len(instanceCfg.ClusterEndpoints)). + Msg("Skipping temperature collection - node endpoint not found in cluster metadata") + continue + } } if strings.TrimSpace(sshHost) == "" {