aa4dfb75ea
Add a host-wide, token-authed GET /host/metrics local-API endpoint that re-serves the slice-4 collector's host + per-storage view to the customer (the de-privileged controller can't read the host itself). Add the one new collector — CPU/chassis temperature via sysfs hwmon/thermal-zones, graceful- null — to the shared HostMetrics struct, so the hub report carries cpu_temp_c too. Cross-repo host-report golden updated byte-identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
3.6 KiB
Go
90 lines
3.6 KiB
Go
package hub
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// writeSysfs creates path under root with the given content (sysfs files are tiny text files).
|
|
func writeSysfs(t *testing.T, path, content string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("write %s: %v", path, err)
|
|
}
|
|
}
|
|
|
|
// A coretemp hwmon with a "Package id 0" label must win over a per-core sensor.
|
|
func TestSysfsTempReader_HwmonPackagePreferred(t *testing.T) {
|
|
root := t.TempDir()
|
|
dir := filepath.Join(root, "class", "hwmon", "hwmon0")
|
|
writeSysfs(t, filepath.Join(dir, "name"), "coretemp\n")
|
|
// temp1 = Package id 0 = 47°C; temp2 = Core 0 = 52°C. The package must be chosen.
|
|
writeSysfs(t, filepath.Join(dir, "temp1_input"), "47000\n")
|
|
writeSysfs(t, filepath.Join(dir, "temp1_label"), "Package id 0\n")
|
|
writeSysfs(t, filepath.Join(dir, "temp2_input"), "52000\n")
|
|
writeSysfs(t, filepath.Join(dir, "temp2_label"), "Core 0\n")
|
|
|
|
got := SysfsTempReader{Root: root}.CPUTempC(context.Background())
|
|
if got == nil || *got != 47 {
|
|
t.Fatalf("CPUTempC = %v, want 47", got)
|
|
}
|
|
}
|
|
|
|
// With no package label, the first readable hwmon input is used.
|
|
func TestSysfsTempReader_HwmonFirstInputFallback(t *testing.T) {
|
|
root := t.TempDir()
|
|
dir := filepath.Join(root, "class", "hwmon", "hwmon0")
|
|
writeSysfs(t, filepath.Join(dir, "name"), "k10temp\n")
|
|
writeSysfs(t, filepath.Join(dir, "temp1_input"), "39000\n") // Tctl, no label
|
|
got := SysfsTempReader{Root: root}.CPUTempC(context.Background())
|
|
if got == nil || *got != 39 {
|
|
t.Fatalf("CPUTempC = %v, want 39", got)
|
|
}
|
|
}
|
|
|
|
// A non-CPU hwmon (e.g. a NIC) must be ignored; the thermal-zone CPU sensor is used instead.
|
|
func TestSysfsTempReader_ThermalZoneByType(t *testing.T) {
|
|
root := t.TempDir()
|
|
// hwmon is a non-CPU driver → skipped.
|
|
nic := filepath.Join(root, "class", "hwmon", "hwmon0")
|
|
writeSysfs(t, filepath.Join(nic, "name"), "iwlwifi\n")
|
|
writeSysfs(t, filepath.Join(nic, "temp1_input"), "60000\n")
|
|
// thermal zones: acpitz (40°C) + x86_pkg_temp (55°C). The CPU package type must win.
|
|
z0 := filepath.Join(root, "class", "thermal", "thermal_zone0")
|
|
writeSysfs(t, filepath.Join(z0, "type"), "acpitz\n")
|
|
writeSysfs(t, filepath.Join(z0, "temp"), "40000\n")
|
|
z1 := filepath.Join(root, "class", "thermal", "thermal_zone1")
|
|
writeSysfs(t, filepath.Join(z1, "type"), "x86_pkg_temp\n")
|
|
writeSysfs(t, filepath.Join(z1, "temp"), "55000\n")
|
|
|
|
got := SysfsTempReader{Root: root}.CPUTempC(context.Background())
|
|
if got == nil || *got != 55 {
|
|
t.Fatalf("CPUTempC = %v, want 55 (x86_pkg_temp), got %v", got, got)
|
|
}
|
|
}
|
|
|
|
// The headline graceful-null case: a host that exposes NO sensor (empty /sys) returns nil, and
|
|
// no error propagates (CPUTempC has no error return — a missing sensor is "n/a", never a failure).
|
|
func TestSysfsTempReader_GracefulNullWhenAbsent(t *testing.T) {
|
|
root := t.TempDir() // empty: no hwmon, no thermal zones
|
|
if got := (SysfsTempReader{Root: root}).CPUTempC(context.Background()); got != nil {
|
|
t.Fatalf("CPUTempC on a sensorless host = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
// Out-of-range / garbage readings degrade to nil rather than reporting a bogus temperature.
|
|
func TestSysfsTempReader_RejectsImplausibleValues(t *testing.T) {
|
|
root := t.TempDir()
|
|
z := filepath.Join(root, "class", "thermal", "thermal_zone0")
|
|
writeSysfs(t, filepath.Join(z, "type"), "x86_pkg_temp\n")
|
|
writeSysfs(t, filepath.Join(z, "temp"), "0\n") // 0 m°C → implausible → ignored
|
|
if got := (SysfsTempReader{Root: root}).CPUTempC(context.Background()); got != nil {
|
|
t.Fatalf("CPUTempC on a 0°C reading = %v, want nil", got)
|
|
}
|
|
}
|