mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Allow SSH SMART fallback without host-agent SMART
This commit is contained in:
@@ -328,6 +328,12 @@ canonical physical-disk model and the Proxmox polling runtime in
|
||||
`internal/monitoring/monitor_pve.go` must evaluate disk alerts only after that
|
||||
merged disk view exists, so controller-backed disks do not lose health and
|
||||
endurance coverage between collection and alerting.
|
||||
That same host-agent temperature boundary must not suppress SSH SMART disk
|
||||
collection just because the agent already reported CPU package or NVMe
|
||||
temperatures. `internal/monitoring/monitor_polling_node_helpers.go` may skip
|
||||
SSH only once the host-agent temperature payload already has SMART disk data,
|
||||
so nodes keep their disk-temperature and SMART augmentation when the host agent
|
||||
is present but lacks SMART support.
|
||||
That same Proxmox monitoring boundary also owns checked response parsing for
|
||||
polymorphic numeric fields. Shared client parsers such as
|
||||
`pkg/proxmox/replication.go` must use the package's checked integer conversion
|
||||
|
||||
@@ -220,6 +220,31 @@ func TestProxmoxGuestDiskCarryForwardUsesCanonicalStabilityHelper(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitoringTemperatureFallbackUsesSMARTAwareSSHSkipRule(t *testing.T) {
|
||||
requiredSnippets := map[string][]string{
|
||||
"host_agent_temps.go": {
|
||||
"func shouldSkipTemperatureSSHCollection(hostAgentTemp *models.Temperature) bool {",
|
||||
"return hostAgentTemp.HasSMART",
|
||||
},
|
||||
"monitor_polling_node_helpers.go": {
|
||||
"skipSSHCollection := shouldSkipTemperatureSSHCollection(hostAgentTemp)",
|
||||
},
|
||||
}
|
||||
|
||||
for file, snippets := range requiredSnippets {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read %s: %v", file, err)
|
||||
}
|
||||
source := string(data)
|
||||
for _, snippet := range snippets {
|
||||
if !strings.Contains(source, snippet) {
|
||||
t.Fatalf("%s must contain %q", file, snippet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitoringRuntimeAvoidsLegacyMockPartialHelpers(t *testing.T) {
|
||||
forbiddenSnippets := []string{
|
||||
"mock.GetMockState(",
|
||||
|
||||
@@ -22,6 +22,16 @@ func (m *Monitor) getHostAgentTemperature(nodeName string) *models.Temperature {
|
||||
return m.getHostAgentTemperatureByID("", nodeName)
|
||||
}
|
||||
|
||||
func shouldSkipTemperatureSSHCollection(hostAgentTemp *models.Temperature) bool {
|
||||
if hostAgentTemp == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Host-agent CPU or NVMe readings are preferred, but SSH should still augment
|
||||
// the node with SMART disk temperatures until the agent has reported SMART.
|
||||
return hostAgentTemp.HasSMART
|
||||
}
|
||||
|
||||
// getHostAgentTemperatureByID looks for a matching host agent by node ID first,
|
||||
// then falls back to hostname matching. This correctly handles clusters where
|
||||
// multiple nodes may have the same hostname (e.g., "px1" on different IPs).
|
||||
|
||||
@@ -152,6 +152,37 @@ func TestConvertHostSensorsToTemperature_NoPackageUsesMaxCore(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldSkipTemperatureSSHCollection(t *testing.T) {
|
||||
t.Run("nil host agent temp does not skip", func(t *testing.T) {
|
||||
if shouldSkipTemperatureSSHCollection(nil) {
|
||||
t.Fatal("expected nil host agent temp not to skip SSH collection")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cpu only host agent temp does not skip", func(t *testing.T) {
|
||||
host := &models.Temperature{
|
||||
Available: true,
|
||||
HasCPU: true,
|
||||
CPUPackage: 55,
|
||||
}
|
||||
if shouldSkipTemperatureSSHCollection(host) {
|
||||
t.Fatal("expected CPU-only host agent temp not to skip SSH collection")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("host agent smart data skips", func(t *testing.T) {
|
||||
host := &models.Temperature{
|
||||
Available: true,
|
||||
HasCPU: true,
|
||||
HasSMART: true,
|
||||
SMART: []models.DiskTemp{{Device: "/dev/sda", Temperature: 35}},
|
||||
}
|
||||
if !shouldSkipTemperatureSSHCollection(host) {
|
||||
t.Fatal("expected SMART-capable host agent temp to skip SSH collection")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsHostAgentTemperatureRecent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -159,8 +159,10 @@ func (m *Monitor) collectNodeTemperatureData(
|
||||
sshHost = node.Node
|
||||
}
|
||||
|
||||
// Skip SSH collection if we already have host agent data.
|
||||
skipSSHCollection := hostAgentTemp != nil
|
||||
// Skip SSH only when the host agent already has SMART data too.
|
||||
// If the host agent only has CPU/NVMe readings, SSH can still
|
||||
// augment the node with SMART disk temperatures.
|
||||
skipSSHCollection := shouldSkipTemperatureSSHCollection(hostAgentTemp)
|
||||
|
||||
if !skipSSHCollection {
|
||||
sshTemp, err = m.tempCollector.CollectTemperature(tempCtx, sshHost, node.Node)
|
||||
|
||||
Reference in New Issue
Block a user