feat(tpm): add read-only TPM validation
This commit is contained in:
@@ -138,6 +138,7 @@ type satRunner interface {
|
|||||||
ResetNvidiaGPU(index int) (string, error)
|
ResetNvidiaGPU(index int) (string, error)
|
||||||
RunMemoryAcceptancePack(ctx context.Context, baseDir string, sizeMB, passes int, logFunc func(string)) (string, error)
|
RunMemoryAcceptancePack(ctx context.Context, baseDir string, sizeMB, passes int, logFunc func(string)) (string, error)
|
||||||
RunStorageAcceptancePack(ctx context.Context, baseDir string, extended bool, logFunc func(string)) (string, error)
|
RunStorageAcceptancePack(ctx context.Context, baseDir string, extended bool, logFunc func(string)) (string, error)
|
||||||
|
RunTPMValidationPack(ctx context.Context, baseDir string, logFunc func(string)) (string, error)
|
||||||
RunNvidiaConfigCheckPack(ctx context.Context, baseDir string, logFunc func(string)) (string, error)
|
RunNvidiaConfigCheckPack(ctx context.Context, baseDir string, logFunc func(string)) (string, error)
|
||||||
RunPCIeLinkCheckPack(ctx context.Context, baseDir string, logFunc func(string)) (string, error)
|
RunPCIeLinkCheckPack(ctx context.Context, baseDir string, logFunc func(string)) (string, error)
|
||||||
RunNvidiaPCIeBandwidthPack(ctx context.Context, baseDir string, gpuIndices []int, logFunc func(string)) (string, error)
|
RunNvidiaPCIeBandwidthPack(ctx context.Context, baseDir string, gpuIndices []int, logFunc func(string)) (string, error)
|
||||||
|
|||||||
@@ -258,6 +258,13 @@ func (a *App) RunStorageAcceptancePackResult(baseDir string) (ActionResult, erro
|
|||||||
return ActionResult{Title: "Storage SAT", Body: satResultBody(path)}, err
|
return ActionResult{Title: "Storage SAT", Body: satResultBody(path)}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) RunTPMValidationPackCtx(ctx context.Context, baseDir string, logFunc func(string)) (string, error) {
|
||||||
|
if strings.TrimSpace(baseDir) == "" {
|
||||||
|
baseDir = DefaultSATBaseDir
|
||||||
|
}
|
||||||
|
return a.sat.RunTPMValidationPack(ctx, baseDir, logFunc)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) RunNvidiaConfigCheckPackCtx(ctx context.Context, baseDir string, logFunc func(string)) (string, error) {
|
func (a *App) RunNvidiaConfigCheckPackCtx(ctx context.Context, baseDir string, logFunc func(string)) (string, error) {
|
||||||
if strings.TrimSpace(baseDir) == "" {
|
if strings.TrimSpace(baseDir) == "" {
|
||||||
baseDir = DefaultSATBaseDir
|
baseDir = DefaultSATBaseDir
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ func (f fakeSAT) RunStorageAcceptancePack(_ context.Context, baseDir string, _ b
|
|||||||
return f.runStorageFn(baseDir)
|
return f.runStorageFn(baseDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f fakeSAT) RunTPMValidationPack(_ context.Context, _ string, _ func(string)) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f fakeSAT) RunNvidiaConfigCheckPack(_ context.Context, baseDir string, _ func(string)) (string, error) {
|
func (f fakeSAT) RunNvidiaConfigCheckPack(_ context.Context, baseDir string, _ func(string)) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ reading here can be normal driver power management, not a fault),
|
|||||||
speed here, the idle Gen1 reading was power saving — if it stays at Gen1
|
speed here, the idle Gen1 reading was power saving — if it stays at Gen1
|
||||||
under load, that's a real link/riser/slot degradation), `pcie-aer-sysfs.txt`, `kernel-aer-nvidia.txt` (AER/NVRM/Xid-filtered dmesg), `lspci-video-vv.txt`, `systemctl-nvidia-units.txt`. AMD systems get `rocm-smi*.txt` here instead. |
|
under load, that's a real link/riser/slot degradation), `pcie-aer-sysfs.txt`, `kernel-aer-nvidia.txt` (AER/NVRM/Xid-filtered dmesg), `lspci-video-vv.txt`, `systemctl-nvidia-units.txt`. AMD systems get `rocm-smi*.txt` here instead. |
|
||||||
| `network/` | `ethtool-{info,link,module}.txt` (per-NIC), `mstflint-query.txt` (Mellanox/NVIDIA NICs). |
|
| `network/` | `ethtool-{info,link,module}.txt` (per-NIC), `mstflint-query.txt` (Mellanox/NVIDIA NICs). |
|
||||||
| `platform/` | `dmidecode-type{0,1,2}.txt` (BIOS/System/Baseboard), `ipmitool-{fru,sdr,sensor,sel,sel-time}.txt` (BMC), `sensors.json`, `lspci-{nn,vmm,vvv}.txt`. |
|
| `platform/` | `dmidecode-type{0,1,2}.txt` (BIOS/System/Baseboard), `ipmitool-{fru,sdr,sensor,sel,sel-time}.txt` (BMC), `tpm-{properties-fixed,pcr-banks,pcr-values,test-result}.txt` (read-only TPM state), `sensors.json`, `lspci-{nn,vmm,vvv}.txt`. |
|
||||||
|
|
||||||
## `status/` in detail
|
## `status/` in detail
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ func Run(_ runtimeenv.Mode) schema.HardwareIngestRequest {
|
|||||||
snap.PowerSupplies = enrichPSUsWithTelemetry(snap.PowerSupplies, sensorDoc)
|
snap.PowerSupplies = enrichPSUsWithTelemetry(snap.PowerSupplies, sensorDoc)
|
||||||
snap.Sensors = mergeIPMISensors(buildSensorsFromDoc(sensorDoc), collectIPMISensors())
|
snap.Sensors = mergeIPMISensors(buildSensorsFromDoc(sensorDoc), collectIPMISensors())
|
||||||
snap.EventLogs = append(collectIPMISEL(), collectDmesgErrors()...)
|
snap.EventLogs = append(collectIPMISEL(), collectDmesgErrors()...)
|
||||||
|
snap.PlatformConfig = collectTPMPlatformConfig()
|
||||||
finalizeSnapshot(&snap, collectedAt)
|
finalizeSnapshot(&snap, collectedAt)
|
||||||
|
|
||||||
// remaining collectors added in steps 1.8 – 1.10
|
// remaining collectors added in steps 1.8 – 1.10
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tpmGlob = filepath.Glob
|
||||||
|
tpmStat = os.Stat
|
||||||
|
tpmRun = func(name string, args ...string) ([]byte, error) {
|
||||||
|
return exec.Command(name, args...).Output()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// collectTPMPlatformConfig records TPM presence and read-only identity data.
|
||||||
|
// It never provisions the TPM, changes ownership, writes NV storage, or runs
|
||||||
|
// TPM2_SelfTest.
|
||||||
|
func collectTPMPlatformConfig() *json.RawMessage {
|
||||||
|
config := map[string]any{"TpmPresent": false, "TpmEnabled": false}
|
||||||
|
devices, _ := tpmGlob("/sys/class/tpm/tpm*")
|
||||||
|
if len(devices) == 0 {
|
||||||
|
return rawPlatformConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["TpmPresent"] = true
|
||||||
|
config["TpmEnabled"] = true
|
||||||
|
config["TpmDevice"] = filepath.Base(devices[0])
|
||||||
|
for _, path := range []string{"/dev/tpmrm0", "/dev/tpm0"} {
|
||||||
|
if _, err := tpmStat(path); err == nil {
|
||||||
|
config["TpmInterface"] = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := tpmRun("tpm2_getcap", "properties-fixed")
|
||||||
|
if err != nil {
|
||||||
|
return rawPlatformConfig(config)
|
||||||
|
}
|
||||||
|
properties := parseTPMProperties(string(out))
|
||||||
|
if value := tpmPropertyValue(properties, "TPM2_PT_FAMILY_INDICATOR"); value != "" {
|
||||||
|
config["TpmVersion"] = value
|
||||||
|
}
|
||||||
|
if value := tpmManufacturer(properties["TPM2_PT_MANUFACTURER"]); value != "" {
|
||||||
|
config["TpmManufacturer"] = value
|
||||||
|
}
|
||||||
|
firmware1 := tpmPropertyRaw(properties, "TPM2_PT_FIRMWARE_VERSION_1")
|
||||||
|
firmware2 := tpmPropertyRaw(properties, "TPM2_PT_FIRMWARE_VERSION_2")
|
||||||
|
if firmware1 != "" || firmware2 != "" {
|
||||||
|
config["TpmFirmwareVersion"] = strings.Trim(strings.Join([]string{firmware1, firmware2}, "/"), "/")
|
||||||
|
}
|
||||||
|
return rawPlatformConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tpmProperty struct {
|
||||||
|
raw string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTPMProperties(input string) map[string]tpmProperty {
|
||||||
|
properties := make(map[string]tpmProperty)
|
||||||
|
current := ""
|
||||||
|
for _, line := range strings.Split(input, "\n") {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(trimmed, "TPM2_PT_") && strings.HasSuffix(trimmed, ":") {
|
||||||
|
current = strings.TrimSuffix(trimmed, ":")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if current == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
property := properties[current]
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(trimmed, "raw:"):
|
||||||
|
property.raw = strings.TrimSpace(strings.TrimPrefix(trimmed, "raw:"))
|
||||||
|
case strings.HasPrefix(trimmed, "value:"):
|
||||||
|
property.value = strings.Trim(strings.TrimSpace(strings.TrimPrefix(trimmed, "value:")), `"`)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
properties[current] = property
|
||||||
|
}
|
||||||
|
return properties
|
||||||
|
}
|
||||||
|
|
||||||
|
func tpmPropertyValue(properties map[string]tpmProperty, name string) string {
|
||||||
|
property := properties[name]
|
||||||
|
if property.value != "" {
|
||||||
|
return property.value
|
||||||
|
}
|
||||||
|
return property.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func tpmPropertyRaw(properties map[string]tpmProperty, name string) string {
|
||||||
|
return properties[name].raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func tpmManufacturer(property tpmProperty) string {
|
||||||
|
if property.value != "" {
|
||||||
|
return property.value
|
||||||
|
}
|
||||||
|
raw := strings.TrimPrefix(property.raw, "0x")
|
||||||
|
value, err := strconv.ParseUint(raw, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
bytes := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(bytes, uint32(value))
|
||||||
|
for _, b := range bytes {
|
||||||
|
if b != 0 && (b < 0x20 || b > 0x7e) {
|
||||||
|
return property.raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimRight(string(bytes), "\x00 ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawPlatformConfig(config map[string]any) *json.RawMessage {
|
||||||
|
raw, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
message := json.RawMessage(raw)
|
||||||
|
return &message
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseTPMProperties(t *testing.T) {
|
||||||
|
properties := parseTPMProperties(`TPM2_PT_FAMILY_INDICATOR:
|
||||||
|
raw: 0x322E3000
|
||||||
|
value: "2.0"
|
||||||
|
TPM2_PT_MANUFACTURER:
|
||||||
|
raw: 0x49465800
|
||||||
|
value: "IFX"
|
||||||
|
TPM2_PT_FIRMWARE_VERSION_1:
|
||||||
|
raw: 0x0007003F
|
||||||
|
TPM2_PT_FIRMWARE_VERSION_2:
|
||||||
|
raw: 0x00100023
|
||||||
|
`)
|
||||||
|
|
||||||
|
if got := tpmPropertyValue(properties, "TPM2_PT_FAMILY_INDICATOR"); got != "2.0" {
|
||||||
|
t.Fatalf("family indicator = %q, want 2.0", got)
|
||||||
|
}
|
||||||
|
if got := tpmManufacturer(properties["TPM2_PT_MANUFACTURER"]); got != "IFX" {
|
||||||
|
t.Fatalf("manufacturer = %q, want IFX", got)
|
||||||
|
}
|
||||||
|
if got := tpmPropertyRaw(properties, "TPM2_PT_FIRMWARE_VERSION_1"); got != "0x0007003F" {
|
||||||
|
t.Fatalf("firmware version 1 = %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTPMManufacturerDecodesRawVendorID(t *testing.T) {
|
||||||
|
if got := tpmManufacturer(tpmProperty{raw: "0x49465800"}); got != "IFX" {
|
||||||
|
t.Fatalf("manufacturer = %q, want IFX", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectTPMPlatformConfig(t *testing.T) {
|
||||||
|
originalGlob, originalStat, originalRun := tpmGlob, tpmStat, tpmRun
|
||||||
|
t.Cleanup(func() {
|
||||||
|
tpmGlob, tpmStat, tpmRun = originalGlob, originalStat, originalRun
|
||||||
|
})
|
||||||
|
|
||||||
|
tpmGlob = func(string) ([]string, error) { return []string{"/sys/class/tpm/tpm0"}, nil }
|
||||||
|
tpmStat = func(path string) (os.FileInfo, error) {
|
||||||
|
if path == "/dev/tpmrm0" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
tpmRun = func(name string, args ...string) ([]byte, error) {
|
||||||
|
if name != "tpm2_getcap" || !reflect.DeepEqual(args, []string{"properties-fixed"}) {
|
||||||
|
t.Fatalf("unexpected command: %s %v", name, args)
|
||||||
|
}
|
||||||
|
return []byte(`TPM2_PT_FAMILY_INDICATOR:
|
||||||
|
value: "2.0"
|
||||||
|
TPM2_PT_MANUFACTURER:
|
||||||
|
raw: 0x49465800
|
||||||
|
TPM2_PT_FIRMWARE_VERSION_1:
|
||||||
|
raw: 0x0007003F
|
||||||
|
TPM2_PT_FIRMWARE_VERSION_2:
|
||||||
|
raw: 0x00100023
|
||||||
|
`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
got := decodeTPMConfig(t, collectTPMPlatformConfig())
|
||||||
|
want := map[string]any{
|
||||||
|
"TpmPresent": true,
|
||||||
|
"TpmEnabled": true,
|
||||||
|
"TpmDevice": "tpm0",
|
||||||
|
"TpmInterface": "/dev/tpmrm0",
|
||||||
|
"TpmVersion": "2.0",
|
||||||
|
"TpmManufacturer": "IFX",
|
||||||
|
"TpmFirmwareVersion": "0x0007003F/0x00100023",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("config = %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectTPMPlatformConfigAbsent(t *testing.T) {
|
||||||
|
originalGlob := tpmGlob
|
||||||
|
t.Cleanup(func() { tpmGlob = originalGlob })
|
||||||
|
tpmGlob = func(string) ([]string, error) { return nil, errors.New("not found") }
|
||||||
|
|
||||||
|
got := decodeTPMConfig(t, collectTPMPlatformConfig())
|
||||||
|
want := map[string]any{"TpmPresent": false, "TpmEnabled": false}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("config = %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeTPMConfig(t *testing.T, raw *json.RawMessage) map[string]any {
|
||||||
|
t.Helper()
|
||||||
|
if raw == nil {
|
||||||
|
t.Fatal("platform config is nil")
|
||||||
|
}
|
||||||
|
var config map[string]any
|
||||||
|
if err := json.Unmarshal(*raw, &config); err != nil {
|
||||||
|
t.Fatalf("unmarshal config: %v", err)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ var runtimeRequiredTools = []string{
|
|||||||
"smartctl",
|
"smartctl",
|
||||||
"nvme",
|
"nvme",
|
||||||
"ipmitool",
|
"ipmitool",
|
||||||
|
"tpm2_getcap",
|
||||||
|
"tpm2_pcrread",
|
||||||
"dhclient",
|
"dhclient",
|
||||||
"mount",
|
"mount",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ const (
|
|||||||
// RAM: memtester 512 MB / 1 pass (extrapolated from validate timing, linear with size).
|
// RAM: memtester 512 MB / 1 pass (extrapolated from validate timing, linear with size).
|
||||||
SATEstimatedMemoryStressSec = 140
|
SATEstimatedMemoryStressSec = 140
|
||||||
|
|
||||||
|
// TPM capabilities, PCR values, and existing self-test result queries.
|
||||||
|
SATEstimatedTPMValidateSec = 5
|
||||||
|
|
||||||
// NVIDIA dcgmi diag Level 2 (medium), all GPUs simultaneously.
|
// NVIDIA dcgmi diag Level 2 (medium), all GPUs simultaneously.
|
||||||
SATEstimatedNvidiaGPUValidateSec = 85
|
SATEstimatedNvidiaGPUValidateSec = 85
|
||||||
// NVIDIA dcgmi diag Level 3 (targeted stress), all GPUs simultaneously.
|
// NVIDIA dcgmi diag Level 3 (targeted stress), all GPUs simultaneously.
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ var techDumpFixedCommands = []struct {
|
|||||||
{Name: "ipmitool", Args: []string{"sensor"}, File: "ipmitool-sensor.txt"},
|
{Name: "ipmitool", Args: []string{"sensor"}, File: "ipmitool-sensor.txt"},
|
||||||
{Name: "ipmitool", Args: []string{"sel", "list"}, File: "ipmitool-sel.txt"},
|
{Name: "ipmitool", Args: []string{"sel", "list"}, File: "ipmitool-sel.txt"},
|
||||||
{Name: "ipmitool", Args: []string{"sel", "time", "get"}, File: "ipmitool-sel-time.txt"},
|
{Name: "ipmitool", Args: []string{"sel", "time", "get"}, File: "ipmitool-sel-time.txt"},
|
||||||
|
{Name: "tpm2_getcap", Args: []string{"properties-fixed"}, File: "tpm-properties-fixed.txt"},
|
||||||
|
{Name: "tpm2_getcap", Args: []string{"pcrs"}, File: "tpm-pcr-banks.txt"},
|
||||||
|
{Name: "tpm2_pcrread", File: "tpm-pcr-values.txt"},
|
||||||
|
{Name: "tpm2_gettestresult", File: "tpm-test-result.txt"},
|
||||||
{Name: "nvme", Args: []string{"list", "-o", "json"}, File: "nvme-list.json"},
|
{Name: "nvme", Args: []string{"list", "-o", "json"}, File: "nvme-list.json"},
|
||||||
{Name: "storcli64", Args: []string{"/call/eall/sall", "show", "all", "J"}, File: "storcli64-drives.json"},
|
{Name: "storcli64", Args: []string{"/call/eall/sall", "show", "all", "J"}, File: "storcli64-drives.json"},
|
||||||
// storcli2 (Tri-Mode controllers, e.g. SAS3808-iMR/9500 series) needs an
|
// storcli2 (Tri-Mode controllers, e.g. SAS3808-iMR/9500 series) needs an
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package platform
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// RunTPMValidationPack verifies TPM 2.0 communication using read-only
|
||||||
|
// commands. It deliberately excludes SelfTest, provisioning, NV writes, PCR
|
||||||
|
// changes, key creation, and ownership operations.
|
||||||
|
func (s *System) RunTPMValidationPack(ctx context.Context, baseDir string, logFunc func(string)) (string, error) {
|
||||||
|
return runAcceptancePackCtx(ctx, baseDir, "tpm", tpmValidationJobs(), logFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tpmValidationJobs() []satJob {
|
||||||
|
return []satJob{
|
||||||
|
{name: "01-properties-fixed.log", cmd: []string{"tpm2_getcap", "properties-fixed"}},
|
||||||
|
{name: "02-pcr-banks.log", cmd: []string{"tpm2_getcap", "pcrs"}},
|
||||||
|
{name: "03-pcr-values.log", cmd: []string{"tpm2_pcrread"}},
|
||||||
|
{name: "04-test-result.log", cmd: []string{"tpm2_gettestresult"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package platform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTPMValidationJobsAreReadOnly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
jobs := tpmValidationJobs()
|
||||||
|
want := [][]string{
|
||||||
|
{"tpm2_getcap", "properties-fixed"},
|
||||||
|
{"tpm2_getcap", "pcrs"},
|
||||||
|
{"tpm2_pcrread"},
|
||||||
|
{"tpm2_gettestresult"},
|
||||||
|
}
|
||||||
|
if len(jobs) != len(want) {
|
||||||
|
t.Fatalf("jobs=%d want %d", len(jobs), len(want))
|
||||||
|
}
|
||||||
|
for index, job := range jobs {
|
||||||
|
if !reflect.DeepEqual(job.cmd, want[index]) {
|
||||||
|
t.Fatalf("jobs[%d].cmd=%v want %v", index, job.cmd, want[index])
|
||||||
|
}
|
||||||
|
joined := strings.ToLower(strings.Join(job.cmd, " "))
|
||||||
|
for _, forbidden := range []string{"selftest", "clear", "changeauth", "nvwrite", "pcrextend", "create"} {
|
||||||
|
if strings.Contains(joined, forbidden) {
|
||||||
|
t.Fatalf("job %q contains state-changing command %q", joined, forbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1542,6 +1542,7 @@ func (h *handler) handleAPISystemTimeSync(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
var standardTools = []string{
|
var standardTools = []string{
|
||||||
"dmidecode", "smartctl", "nvme", "lspci", "ipmitool",
|
"dmidecode", "smartctl", "nvme", "lspci", "ipmitool",
|
||||||
|
"tpm2_getcap", "tpm2_pcrread", "tpm2_gettestresult",
|
||||||
"nvidia-smi", "dcgmi", "nv-hostengine", "memtester", "stress-ng", "nvtop",
|
"nvidia-smi", "dcgmi", "nv-hostengine", "memtester", "stress-ng", "nvtop",
|
||||||
"mstflint", "saa",
|
"mstflint", "saa",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type validateInventory struct {
|
|||||||
CPU string
|
CPU string
|
||||||
Memory string
|
Memory string
|
||||||
Storage string
|
Storage string
|
||||||
|
TPM string
|
||||||
NVIDIA string
|
NVIDIA string
|
||||||
AMD string
|
AMD string
|
||||||
NvidiaGPUCount int
|
NvidiaGPUCount int
|
||||||
@@ -42,6 +43,7 @@ func validateTotalValidateSec(n int) int {
|
|||||||
}
|
}
|
||||||
total := platform.SATEstimatedCPUValidateSec +
|
total := platform.SATEstimatedCPUValidateSec +
|
||||||
platform.SATEstimatedMemoryValidateSec +
|
platform.SATEstimatedMemoryValidateSec +
|
||||||
|
platform.SATEstimatedTPMValidateSec +
|
||||||
platform.SATEstimatedNvidiaInterconnectSec +
|
platform.SATEstimatedNvidiaInterconnectSec +
|
||||||
platform.SATEstimatedNvidiaBandwidthSec
|
platform.SATEstimatedNvidiaBandwidthSec
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
@@ -147,6 +149,12 @@ func renderValidateMode(opts HandlerOptions, stressDefault bool) string {
|
|||||||
`<code>lsblk</code>; NVMe: <code>nvme id-ctrl</code>, <code>nvme smart-log</code>, <code>nvme device-self-test -s 1</code>; SATA/SAS: <code>smartctl -H -A</code>, <code>smartctl -t short</code>`,
|
`<code>lsblk</code>; NVMe: <code>nvme id-ctrl</code>, <code>nvme smart-log</code>, <code>nvme device-self-test -s 1</code>; SATA/SAS: <code>smartctl -H -A</code>, <code>smartctl -t short</code>`,
|
||||||
`~2 min per device (NVMe short self-test; SATA/SAS short self-test — duration device-dependent).`,
|
`~2 min per device (NVMe short self-test; SATA/SAS short self-test — duration device-dependent).`,
|
||||||
)) +
|
)) +
|
||||||
|
renderSATCard("tpm", "TPM", "runSAT('tpm')", "", renderValidateCardBody(
|
||||||
|
inv.TPM,
|
||||||
|
`Checks TPM 2.0 communication and reports its fixed properties, allocated PCR banks, current PCR values, and the result of self-tests already performed by the TPM. It does not start a new self-test or change TPM state.`,
|
||||||
|
`<code>tpm2_getcap properties-fixed</code>, <code>tpm2_getcap pcrs</code>, <code>tpm2_pcrread</code>, <code>tpm2_gettestresult</code>`,
|
||||||
|
`Seconds - read-only queries; no ownership, NV, PCR, or key changes.`,
|
||||||
|
)) +
|
||||||
`</div>
|
`</div>
|
||||||
<div style="height:1px;background:var(--border);margin:16px 0"></div>
|
<div style="height:1px;background:var(--border);margin:16px 0"></div>
|
||||||
<div class="card" style="margin-bottom:16px">
|
<div class="card" style="margin-bottom:16px">
|
||||||
@@ -211,7 +219,7 @@ func renderValidateMode(opts HandlerOptions, stressDefault bool) string {
|
|||||||
let satES = null;
|
let satES = null;
|
||||||
` + satStressModeJS + `
|
` + satStressModeJS + `
|
||||||
function satLabels() {
|
function satLabels() {
|
||||||
return {nvidia:'Validate GPU', 'nvidia-targeted-stress':'NVIDIA Targeted Stress (dcgmi diag targeted_stress)', 'nvidia-targeted-power':'NVIDIA Targeted Power (dcgmi diag targeted_power)', 'nvidia-pulse':'NVIDIA PSU Pulse Test (dcgmi diag pulse_test)', 'nvidia-interconnect':'NVIDIA Interconnect (NCCL all_reduce_perf)', 'nvidia-bandwidth':'NVIDIA Bandwidth (NVBandwidth)', memory:'Validate Memory', storage:'Validate Storage', cpu:'Validate CPU', amd:'Validate AMD GPU', 'amd-mem':'AMD GPU MEM Integrity', 'amd-bandwidth':'AMD GPU MEM Bandwidth'};
|
return {nvidia:'Validate GPU', 'nvidia-targeted-stress':'NVIDIA Targeted Stress (dcgmi diag targeted_stress)', 'nvidia-targeted-power':'NVIDIA Targeted Power (dcgmi diag targeted_power)', 'nvidia-pulse':'NVIDIA PSU Pulse Test (dcgmi diag pulse_test)', 'nvidia-interconnect':'NVIDIA Interconnect (NCCL all_reduce_perf)', 'nvidia-bandwidth':'NVIDIA Bandwidth (NVBandwidth)', memory:'Validate Memory', storage:'Validate Storage', tpm:'Validate TPM (read-only)', cpu:'Validate CPU', amd:'Validate AMD GPU', 'amd-mem':'AMD GPU MEM Integrity', 'amd-bandwidth':'AMD GPU MEM Bandwidth'};
|
||||||
}
|
}
|
||||||
let satNvidiaGPUsPromise = null;
|
let satNvidiaGPUsPromise = null;
|
||||||
function loadSatNvidiaGPUs() {
|
function loadSatNvidiaGPUs() {
|
||||||
@@ -416,7 +424,7 @@ function runAllSAT() {
|
|||||||
const status = document.getElementById('sat-all-status');
|
const status = document.getElementById('sat-all-status');
|
||||||
status.textContent = 'Enqueuing...';
|
status.textContent = 'Enqueuing...';
|
||||||
const stressOnlyTargets = ['nvidia-targeted-stress', 'nvidia-targeted-power', 'nvidia-pulse'];
|
const stressOnlyTargets = ['nvidia-targeted-stress', 'nvidia-targeted-power', 'nvidia-pulse'];
|
||||||
const baseTargets = ['nvidia','nvidia-targeted-stress','nvidia-targeted-power','nvidia-pulse','nvidia-interconnect','nvidia-bandwidth','memory','storage','cpu'].concat(selectedAMDValidateTargets());
|
const baseTargets = ['nvidia','nvidia-targeted-stress','nvidia-targeted-power','nvidia-pulse','nvidia-interconnect','nvidia-bandwidth','memory','storage','tpm','cpu'].concat(selectedAMDValidateTargets());
|
||||||
const activeTargets = baseTargets.filter(target => {
|
const activeTargets = baseTargets.filter(target => {
|
||||||
if (stressOnlyTargets.indexOf(target) >= 0 && !satStressMode()) return false;
|
if (stressOnlyTargets.indexOf(target) >= 0 && !satStressMode()) return false;
|
||||||
const btn = document.getElementById('sat-btn-' + target);
|
const btn = document.getElementById('sat-btn-' + target);
|
||||||
@@ -498,6 +506,7 @@ func loadValidateInventory(opts HandlerOptions) validateInventory {
|
|||||||
CPU: unknown,
|
CPU: unknown,
|
||||||
Memory: unknown,
|
Memory: unknown,
|
||||||
Storage: unknown,
|
Storage: unknown,
|
||||||
|
TPM: unknown,
|
||||||
NVIDIA: unknown,
|
NVIDIA: unknown,
|
||||||
AMD: unknown,
|
AMD: unknown,
|
||||||
}
|
}
|
||||||
@@ -561,6 +570,7 @@ func loadValidateInventory(opts HandlerOptions) validateInventory {
|
|||||||
out.CPU = formatValidateDeviceSummary(cpuTotal, cpuCounts, "CPU")
|
out.CPU = formatValidateDeviceSummary(cpuTotal, cpuCounts, "CPU")
|
||||||
out.Memory = formatValidateDeviceSummary(memTotal, memCounts, "module")
|
out.Memory = formatValidateDeviceSummary(memTotal, memCounts, "module")
|
||||||
out.Storage = formatValidateDeviceSummary(storageTotal, storageCounts, "device")
|
out.Storage = formatValidateDeviceSummary(storageTotal, storageCounts, "device")
|
||||||
|
out.TPM = formatValidateTPMSummary(snap.Hardware.PlatformConfig)
|
||||||
out.NVIDIA = formatValidateDeviceSummary(nvidiaTotal, nvidiaCounts, "GPU")
|
out.NVIDIA = formatValidateDeviceSummary(nvidiaTotal, nvidiaCounts, "GPU")
|
||||||
out.AMD = formatValidateDeviceSummary(amdTotal, amdCounts, "GPU")
|
out.AMD = formatValidateDeviceSummary(amdTotal, amdCounts, "GPU")
|
||||||
out.NvidiaGPUCount = nvidiaTotal
|
out.NvidiaGPUCount = nvidiaTotal
|
||||||
@@ -568,6 +578,30 @@ func loadValidateInventory(opts HandlerOptions) validateInventory {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatValidateTPMSummary(raw *json.RawMessage) string {
|
||||||
|
if raw == nil {
|
||||||
|
return "TPM presence was not collected."
|
||||||
|
}
|
||||||
|
var config map[string]any
|
||||||
|
if err := json.Unmarshal(*raw, &config); err != nil {
|
||||||
|
return "TPM presence was not collected."
|
||||||
|
}
|
||||||
|
present, ok := config["TpmPresent"].(bool)
|
||||||
|
if !ok {
|
||||||
|
return "TPM presence was not collected."
|
||||||
|
}
|
||||||
|
if !present {
|
||||||
|
return "No TPM detected."
|
||||||
|
}
|
||||||
|
parts := []string{"TPM detected"}
|
||||||
|
for _, key := range []string{"TpmVersion", "TpmManufacturer", "TpmFirmwareVersion", "TpmInterface"} {
|
||||||
|
if value, ok := config[key].(string); ok && strings.TrimSpace(value) != "" {
|
||||||
|
parts = append(parts, html.EscapeString(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(parts, " / ")
|
||||||
|
}
|
||||||
|
|
||||||
func renderValidateCardBody(devices, description, commands, settings string) string {
|
func renderValidateCardBody(devices, description, commands, settings string) string {
|
||||||
return `<div class="validate-card-section"><div style="font-size:13px;color:var(--muted)">` + devices + `</div></div>` +
|
return `<div class="validate-card-section"><div style="font-size:13px;color:var(--muted)">` + devices + `</div></div>` +
|
||||||
`<div class="validate-card-section"><div style="font-size:13px">` + description + `</div></div>` +
|
`<div class="validate-card-section"><div style="font-size:13px">` + description + `</div></div>` +
|
||||||
@@ -665,6 +699,12 @@ func renderCheck(opts HandlerOptions) string {
|
|||||||
`<code>lsblk</code>; NVMe: <code>nvme id-ctrl</code>, <code>nvme smart-log</code>; SATA/SAS: <code>smartctl -H -A</code>`,
|
`<code>lsblk</code>; NVMe: <code>nvme id-ctrl</code>, <code>nvme smart-log</code>; SATA/SAS: <code>smartctl -H -A</code>`,
|
||||||
`Seconds — instantaneous device query, no wear counters incremented.`,
|
`Seconds — instantaneous device query, no wear counters incremented.`,
|
||||||
)) +
|
)) +
|
||||||
|
renderSATCard("tpm", "TPM", "runSAT('tpm')", "", renderValidateCardBody(
|
||||||
|
inv.TPM,
|
||||||
|
`Checks TPM 2.0 communication and reads fixed properties, allocated PCR banks, current PCR values, and the result of self-tests already performed by the TPM. It never starts a new self-test.`,
|
||||||
|
`<code>tpm2_getcap properties-fixed</code>, <code>tpm2_getcap pcrs</code>, <code>tpm2_pcrread</code>, <code>tpm2_gettestresult</code>`,
|
||||||
|
`Seconds - read-only queries; no ownership, NV, PCR, or key changes.`,
|
||||||
|
)) +
|
||||||
renderSATCard("nvidia-config", "GPU Config & NVLink", "runSAT('nvidia-config')", "", renderValidateCardBody(
|
renderSATCard("nvidia-config", "GPU Config & NVLink", "runSAT('nvidia-config')", "", renderValidateCardBody(
|
||||||
inv.NVIDIA,
|
inv.NVIDIA,
|
||||||
`Checks GPU configuration and NVLink topology that DCGM diag does not cover: ECC/MIG/power-limit drift from factory default, NVLink-bonded pair link count and error counters, and (informational) NVIDIA Confidential Computing readiness (CPU TEE support + GPU firmware CC capability). Read-only — changes nothing.`,
|
`Checks GPU configuration and NVLink topology that DCGM diag does not cover: ECC/MIG/power-limit drift from factory default, NVLink-bonded pair link count and error counters, and (informational) NVIDIA Confidential Computing readiness (CPU TEE support + GPU firmware CC capability). Read-only — changes nothing.`,
|
||||||
@@ -744,7 +784,7 @@ func renderCheck(opts HandlerOptions) string {
|
|||||||
<script>
|
<script>
|
||||||
let satES = null;
|
let satES = null;
|
||||||
function satLabels() {
|
function satLabels() {
|
||||||
return {nvidia:'Check GPU (DCGM L2)', 'nvidia-interconnect':'NVIDIA Interconnect (NCCL)', 'nvidia-bandwidth':'NVIDIA Bandwidth (NVBandwidth)', memory:'Check Memory', storage:'Check Storage', cpu:'Check CPU', amd:'Check AMD GPU', 'amd-mem':'AMD GPU MEM Integrity', 'amd-bandwidth':'AMD GPU MEM Bandwidth', 'nvidia-config':'Check GPU Config & NVLink', 'pcie-link':'PCIe Link Check', 'nvidia-pcie-bandwidth':'GPU PCIe Bandwidth Check'};
|
return {nvidia:'Check GPU (DCGM L2)', 'nvidia-interconnect':'NVIDIA Interconnect (NCCL)', 'nvidia-bandwidth':'NVIDIA Bandwidth (NVBandwidth)', memory:'Check Memory', storage:'Check Storage', tpm:'Check TPM (read-only)', cpu:'Check CPU', amd:'Check AMD GPU', 'amd-mem':'AMD GPU MEM Integrity', 'amd-bandwidth':'AMD GPU MEM Bandwidth', 'nvidia-config':'Check GPU Config & NVLink', 'pcie-link':'PCIe Link Check', 'nvidia-pcie-bandwidth':'GPU PCIe Bandwidth Check'};
|
||||||
}
|
}
|
||||||
let satNvidiaGPUsPromise = null;
|
let satNvidiaGPUsPromise = null;
|
||||||
function loadSatNvidiaGPUs() {
|
function loadSatNvidiaGPUs() {
|
||||||
@@ -880,7 +920,7 @@ function runAllCheckSAT() {
|
|||||||
status.textContent = 'Enqueuing...';
|
status.textContent = 'Enqueuing...';
|
||||||
const nvidiaIndices = satSelectedGPUIndices();
|
const nvidiaIndices = satSelectedGPUIndices();
|
||||||
const nvidiaAllTargets = ['nvidia', 'nvidia-interconnect', 'nvidia-bandwidth', 'nvidia-pcie-bandwidth'];
|
const nvidiaAllTargets = ['nvidia', 'nvidia-interconnect', 'nvidia-bandwidth', 'nvidia-pcie-bandwidth'];
|
||||||
const baseTargets = ['cpu', 'memory', 'storage', 'nvidia-config', 'pcie-link'];
|
const baseTargets = ['cpu', 'memory', 'storage', 'tpm', 'nvidia-config', 'pcie-link'];
|
||||||
const amdTargets = selectedAMDValidateTargets();
|
const amdTargets = selectedAMDValidateTargets();
|
||||||
const expanded = [];
|
const expanded = [];
|
||||||
baseTargets.forEach(t => expanded.push({target: t}));
|
baseTargets.forEach(t => expanded.push({target: t}));
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package webui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatValidateTPMSummary(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
raw string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "absent", raw: `{"TpmPresent":false}`, want: "No TPM detected."},
|
||||||
|
{name: "present", raw: `{"TpmPresent":true,"TpmVersion":"2.0","TpmManufacturer":"IFX","TpmFirmwareVersion":"7.63","TpmInterface":"/dev/tpmrm0"}`, want: "TPM detected / 2.0 / IFX / 7.63 / /dev/tpmrm0"},
|
||||||
|
{name: "legacy snapshot", raw: `{}`, want: "TPM presence was not collected."},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
raw := json.RawMessage(tt.raw)
|
||||||
|
if got := formatValidateTPMSummary(&raw); got != tt.want {
|
||||||
|
t.Fatalf("summary=%q want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderCheckIncludesReadOnlyTPMValidation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
page := renderCheck(HandlerOptions{})
|
||||||
|
for _, want := range []string{
|
||||||
|
`id="sat-btn-tpm"`,
|
||||||
|
`tpm2_getcap properties-fixed`,
|
||||||
|
`tpm2_pcrread`,
|
||||||
|
`tpm2_gettestresult`,
|
||||||
|
`'storage', 'tpm', 'nvidia-config'`,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(page, want) {
|
||||||
|
t.Fatalf("check page does not contain %q", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(page, "tpm2_selftest") {
|
||||||
|
t.Fatal("check page must not offer tpm2_selftest")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -264,6 +264,7 @@ func NewHandler(opts HandlerOptions) http.Handler {
|
|||||||
mux.HandleFunc("POST /api/sat/nvidia-stress/run", h.handleAPISATRun("nvidia-stress"))
|
mux.HandleFunc("POST /api/sat/nvidia-stress/run", h.handleAPISATRun("nvidia-stress"))
|
||||||
mux.HandleFunc("POST /api/sat/memory/run", h.handleAPISATRun("memory"))
|
mux.HandleFunc("POST /api/sat/memory/run", h.handleAPISATRun("memory"))
|
||||||
mux.HandleFunc("POST /api/sat/storage/run", h.handleAPISATRun("storage"))
|
mux.HandleFunc("POST /api/sat/storage/run", h.handleAPISATRun("storage"))
|
||||||
|
mux.HandleFunc("POST /api/sat/tpm/run", h.handleAPISATRun("tpm"))
|
||||||
mux.HandleFunc("POST /api/sat/nvidia-config/run", h.handleAPISATRun("nvidia-config"))
|
mux.HandleFunc("POST /api/sat/nvidia-config/run", h.handleAPISATRun("nvidia-config"))
|
||||||
mux.HandleFunc("POST /api/sat/pcie-link/run", h.handleAPISATRun("pcie-link"))
|
mux.HandleFunc("POST /api/sat/pcie-link/run", h.handleAPISATRun("pcie-link"))
|
||||||
mux.HandleFunc("POST /api/sat/nvidia-pcie-bandwidth/run", h.handleAPISATRun("nvidia-pcie-bandwidth"))
|
mux.HandleFunc("POST /api/sat/nvidia-pcie-bandwidth/run", h.handleAPISATRun("nvidia-pcie-bandwidth"))
|
||||||
|
|||||||
@@ -288,6 +288,12 @@ func executeTaskWithOptions(opts *HandlerOptions, t *Task, j *jobState, ctx cont
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
archive, err = runStorageAcceptancePackCtx(a, ctx, "", t.params.StressMode, j.append)
|
archive, err = runStorageAcceptancePackCtx(a, ctx, "", t.params.StressMode, j.append)
|
||||||
|
case "tpm":
|
||||||
|
if a == nil {
|
||||||
|
err = fmt.Errorf("app not configured")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
archive, err = runTPMValidationPackCtx(a, ctx, "", j.append)
|
||||||
case "nvidia-config":
|
case "nvidia-config":
|
||||||
if a == nil {
|
if a == nil {
|
||||||
err = fmt.Errorf("app not configured")
|
err = fmt.Errorf("app not configured")
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ var taskNames = map[string]string{
|
|||||||
"nvidia-stress": "NVIDIA GPU Stress",
|
"nvidia-stress": "NVIDIA GPU Stress",
|
||||||
"memory": "Memory SAT",
|
"memory": "Memory SAT",
|
||||||
"storage": "Storage SAT",
|
"storage": "Storage SAT",
|
||||||
|
"tpm": "TPM Read-only Check",
|
||||||
"nvidia-config": "GPU Config & NVLink Check",
|
"nvidia-config": "GPU Config & NVLink Check",
|
||||||
"pcie-link": "PCIe Link Check (forced retrain)",
|
"pcie-link": "PCIe Link Check (forced retrain)",
|
||||||
"nvidia-pcie-bandwidth": "NVIDIA GPU PCIe Bandwidth Check",
|
"nvidia-pcie-bandwidth": "NVIDIA GPU PCIe Bandwidth Check",
|
||||||
@@ -316,6 +317,9 @@ var (
|
|||||||
runStorageAcceptancePackCtx = func(a *app.App, ctx context.Context, baseDir string, extended bool, logFunc func(string)) (string, error) {
|
runStorageAcceptancePackCtx = func(a *app.App, ctx context.Context, baseDir string, extended bool, logFunc func(string)) (string, error) {
|
||||||
return a.RunStorageAcceptancePackCtx(ctx, baseDir, extended, logFunc)
|
return a.RunStorageAcceptancePackCtx(ctx, baseDir, extended, logFunc)
|
||||||
}
|
}
|
||||||
|
runTPMValidationPackCtx = func(a *app.App, ctx context.Context, baseDir string, logFunc func(string)) (string, error) {
|
||||||
|
return a.RunTPMValidationPackCtx(ctx, baseDir, logFunc)
|
||||||
|
}
|
||||||
runNvidiaConfigCheckPackCtx = func(a *app.App, ctx context.Context, baseDir string, logFunc func(string)) (string, error) {
|
runNvidiaConfigCheckPackCtx = func(a *app.App, ctx context.Context, baseDir string, logFunc func(string)) (string, error) {
|
||||||
return a.RunNvidiaConfigCheckPackCtx(ctx, baseDir, logFunc)
|
return a.RunNvidiaConfigCheckPackCtx(ctx, baseDir, logFunc)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule bible updated: 1977730d93...d2600f1279
@@ -23,6 +23,11 @@ Generic engineering rules live in `bible/rules/patterns/`.
|
|||||||
- `lscpu`
|
- `lscpu`
|
||||||
- `sensors`
|
- `sensors`
|
||||||
- `stress-ng`
|
- `stress-ng`
|
||||||
|
- TPM check (read-only)
|
||||||
|
- `tpm2_getcap properties-fixed`
|
||||||
|
- `tpm2_getcap pcrs`
|
||||||
|
- `tpm2_pcrread`
|
||||||
|
- `tpm2_gettestresult` (reads the existing result; does not start `TPM2_SelfTest`)
|
||||||
- Memory check
|
- Memory check
|
||||||
- `free`
|
- `free`
|
||||||
- `timeout <timeout_sec> memtester`
|
- `timeout <timeout_sec> memtester`
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ Current validation state:
|
|||||||
5. pcie collector (lspci -vmm -D, /sys/bus/pci/devices/)
|
5. pcie collector (lspci -vmm -D, /sys/bus/pci/devices/)
|
||||||
6. psu collector (ipmitool fru + sdr — silent if no /dev/ipmi0)
|
6. psu collector (ipmitool fru + sdr — silent if no /dev/ipmi0)
|
||||||
7. nvidia enrichment (nvidia-smi — skipped if binary absent or driver not loaded)
|
7. nvidia enrichment (nvidia-smi — skipped if binary absent or driver not loaded)
|
||||||
8. output JSON → /var/log/bee-audit.json
|
8. TPM inventory (sysfs presence + `tpm2_getcap properties-fixed`; no state changes)
|
||||||
|
9. output JSON → /var/log/bee-audit.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Every collector returns `nil, nil` on tool-not-found. Errors are logged, never fatal.
|
Every collector returns `nil, nil` on tool-not-found. Errors are logged, never fatal.
|
||||||
@@ -159,6 +160,7 @@ Acceptance flows:
|
|||||||
- NVIDIA GPU burn-in can use either `bee-gpu-burn` or `bee-john-gpu-stress` (John the Ripper jumbo via OpenCL)
|
- NVIDIA GPU burn-in can use either `bee-gpu-burn` or `bee-john-gpu-stress` (John the Ripper jumbo via OpenCL)
|
||||||
- `bee sat memory` → `memtester` archive
|
- `bee sat memory` → `memtester` archive
|
||||||
- `bee sat storage` → SMART/NVMe diagnostic archive and short self-test trigger where supported
|
- `bee sat storage` → SMART/NVMe diagnostic archive and short self-test trigger where supported
|
||||||
|
- `bee` TPM Validate → read-only capabilities, PCR values, and existing self-test result; never starts `TPM2_SelfTest`
|
||||||
- SAT `summary.txt` now includes `overall_status` and per-job `*_status` values (`OK`, `FAILED`, `UNSUPPORTED`)
|
- SAT `summary.txt` now includes `overall_status` and per-job `*_status` values (`OK`, `FAILED`, `UNSUPPORTED`)
|
||||||
- `bee-gpu-burn` should prefer cuBLASLt GEMM load over the old integer/PTX burn path:
|
- `bee-gpu-burn` should prefer cuBLASLt GEMM load over the old integer/PTX burn path:
|
||||||
- Ampere: `fp16` + `fp32`/TF32 tensor-core load
|
- Ampere: `fp16` + `fp32`/TF32 tensor-core load
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Fills gaps where Redfish/logpile is blind:
|
|||||||
|
|
||||||
## In scope
|
## In scope
|
||||||
|
|
||||||
- Read-only hardware inventory: board, CPU, memory, storage, PCIe, PSU, GPU, NIC, RAID
|
- Read-only hardware inventory: board, CPU, memory, storage, PCIe, PSU, GPU, NIC, RAID, TPM
|
||||||
- Machine-readable health summary derived from collector verdicts
|
- Machine-readable health summary derived from collector verdicts
|
||||||
- Operator-triggered acceptance tests for NVIDIA, memory, and storage
|
- Operator-triggered acceptance tests for NVIDIA, memory, and storage
|
||||||
- NVIDIA SAT includes diagnostic collection plus a lightweight in-image GPU stress step via `bee-gpu-burn`
|
- NVIDIA SAT includes diagnostic collection plus a lightweight in-image GPU stress step via `bee-gpu-burn`
|
||||||
|
|||||||
@@ -763,6 +763,11 @@ PSU без `serial_number` игнорируется.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`bee` emits the following TPM keys in this object: `TpmPresent`, `TpmEnabled`,
|
||||||
|
`TpmDevice`, `TpmInterface`, `TpmVersion`, `TpmManufacturer`, and
|
||||||
|
`TpmFirmwareVersion`. Detection and identity collection are read-only; absent
|
||||||
|
optional identity fields are omitted.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Обработка статусов компонентов
|
## Обработка статусов компонентов
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Tests on the **Validate** page are purely diagnostic. They:
|
|||||||
| Storage | `smartctl -a`, `nvme smart-log` — reads SMART data only |
|
| Storage | `smartctl -a`, `nvme smart-log` — reads SMART data only |
|
||||||
| CPU | `stress-ng` for a bounded duration; CPU-only, no I/O |
|
| CPU | `stress-ng` for a bounded duration; CPU-only, no I/O |
|
||||||
| AMD GPU | `rocm-smi --showallinfo`, `dmidecode` — read-only queries |
|
| AMD GPU | `rocm-smi --showallinfo`, `dmidecode` — read-only queries |
|
||||||
|
| TPM | `tpm2_getcap`, `tpm2_pcrread`, `tpm2_gettestresult` — read-only queries; does not start `TPM2_SelfTest` |
|
||||||
|
|
||||||
## Burn Tests (hardware wear)
|
## Burn Tests (hardware wear)
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule internal/chart updated: 8105c7ec08...2a15bc87f1
@@ -2,6 +2,7 @@
|
|||||||
dmidecode
|
dmidecode
|
||||||
smartmontools
|
smartmontools
|
||||||
nvme-cli
|
nvme-cli
|
||||||
|
tpm2-tools
|
||||||
pciutils
|
pciutils
|
||||||
rsync
|
rsync
|
||||||
ipmitool
|
ipmitool
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ info "nvidia modules flavor: ${NVIDIA_MODULES_FLAVOR}"
|
|||||||
|
|
||||||
# --- PATH & binaries ---
|
# --- PATH & binaries ---
|
||||||
echo "-- PATH & binaries --"
|
echo "-- PATH & binaries --"
|
||||||
for tool in dmidecode smartctl nvme ipmitool lspci bee; do
|
for tool in dmidecode smartctl nvme ipmitool lspci tpm2_getcap tpm2_pcrread tpm2_gettestresult bee; do
|
||||||
if p=$(PATH="/usr/local/bin:/usr/sbin:/sbin:$PATH" command -v "$tool" 2>/dev/null); then
|
if p=$(PATH="/usr/local/bin:/usr/sbin:/sbin:$PATH" command -v "$tool" 2>/dev/null); then
|
||||||
ok "$tool found: $p"
|
ok "$tool found: $p"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user