Ensure sensor proxy wrapper delivers SMART temps locally

This commit is contained in:
rcourtman 2025-11-21 10:07:42 +00:00
parent 30a069dfa6
commit 335795d354
2 changed files with 50 additions and 5 deletions

View file

@ -25,6 +25,19 @@ const (
tempWrapperScript = `#!/bin/sh
set -eu
WRAPPER_PATH="${PULSE_SENSOR_WRAPPER:-/opt/pulse/sensor-proxy/bin/pulse-sensor-wrapper.sh}"
# Prefer the full wrapper when available so SMART disk temperatures are included
if [ -x "$WRAPPER_PATH" ]; then
exec "$WRAPPER_PATH"
fi
# Legacy locations (for older installations)
if [ -x /usr/local/bin/pulse-sensor-wrapper.sh ]; then
exec /usr/local/bin/pulse-sensor-wrapper.sh
fi
# Fallback to basic lm-sensors output (no SMART data)
if command -v sensors >/dev/null 2>&1; then
OUTPUT="$(sensors -j 2>/dev/null || true)"
if [ -n "$OUTPUT" ]; then
@ -575,12 +588,10 @@ func (p *Proxy) testSSHConnection(nodeHost string) error {
func (p *Proxy) getTemperatureViaSSH(ctx context.Context, nodeHost string) (string, error) {
startTime := time.Now()
nodeLabel := sanitizeNodeLabel(nodeHost)
localNode := isLocalNode(nodeHost)
// Check if we're requesting the local node (self-monitoring)
// For standalone nodes, avoid SSH and run sensors directly
if isLocalNode(nodeHost) {
log.Debug().Str("node", nodeHost).Msg("Self-monitoring detected, collecting temperatures locally")
return p.getTemperatureLocal(ctx)
if localNode {
log.Debug().Str("node", nodeHost).Msg("Self-monitoring detected, using SSH wrapper for SMART temperatures")
}
privKeyPath := filepath.Join(p.sshKeyPath, "id_ed25519")
@ -617,6 +628,19 @@ func (p *Proxy) getTemperatureViaSSH(ctx context.Context, nodeHost string) (stri
}
if err != nil {
if localNode {
log.Warn().
Str("node", nodeHost).
Err(err).
Msg("SSH temperature collection failed on local node, falling back to direct sensors")
if fallback, localErr := p.getTemperatureLocal(ctx); localErr == nil && strings.TrimSpace(fallback) != "" {
p.metrics.sshRequests.WithLabelValues(nodeLabel, "success").Inc()
p.metrics.sshLatency.WithLabelValues(nodeLabel).Observe(time.Since(startTime).Seconds())
return fallback, nil
}
}
p.metrics.sshRequests.WithLabelValues(nodeLabel, "error").Inc()
p.metrics.sshLatency.WithLabelValues(nodeLabel).Observe(time.Since(startTime).Seconds())
stderrMsg := strings.TrimSpace(stderr)

View file

@ -138,6 +138,27 @@ func TestTempWrapperPrefersSensorsOutput(t *testing.T) {
}
}
func TestTempWrapperPrefersOverrideWrapper(t *testing.T) {
scriptPath, _, binDir, baseDir := setupTempWrapper(t)
overrideDir := filepath.Join(baseDir, "override")
if err := os.MkdirAll(overrideDir, 0o755); err != nil {
t.Fatalf("failed to create override directory: %v", err)
}
overridePath := filepath.Join(overrideDir, "pulse-sensor-wrapper.sh")
expectedOutput := `{"smart":[{"device":"/dev/test","temperature":42}]}`
overrideScript := fmt.Sprintf("#!/bin/sh\nprintf '%s'\n", expectedOutput)
if err := os.WriteFile(overridePath, []byte(overrideScript), 0o755); err != nil {
t.Fatalf("failed to write override wrapper: %v", err)
}
output := runTempWrapper(t, scriptPath, binDir, "PULSE_SENSOR_WRAPPER="+overridePath)
if strings.TrimSpace(string(output)) != expectedOutput {
t.Fatalf("expected override wrapper output %s, got %s", expectedOutput, strings.TrimSpace(string(output)))
}
}
func TestReadAllWithLimit(t *testing.T) {
reader := bytes.NewBufferString("abcdefg")
data, exceeded, err := readAllWithLimit(reader, 4)