Initial import: nasmon health monitor, split out of home_nas
Go service (nasmon/) plus nfpm packaging (packaging/), carried over from the home_nas monorepo. Adds rpm packaging alongside the existing deb build (Fedora/RHEL/Rocky dependency names via nfpm overrides, per-format systemd unit path, dpkg+rpm-compatible maintainer scripts) and a Gitea Actions workflow that builds both formats on every push. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
package collect
|
||||
|
||||
import "testing"
|
||||
|
||||
const smartATA = `{
|
||||
"smartctl": {"version": [7, 4], "exit_status": 0, "messages": []},
|
||||
"device": {"name": "/dev/sda", "type": "sat", "protocol": "ATA"},
|
||||
"model_name": "WDC WD80EFZZ-68BTXN0",
|
||||
"serial_number": "WD-AAA",
|
||||
"firmware_version": "81.00A81",
|
||||
"user_capacity": {"blocks": 15628053168, "bytes": 8001563222016},
|
||||
"rotation_rate": 5640,
|
||||
"smart_support": {"available": true, "enabled": true},
|
||||
"smart_status": {"passed": true},
|
||||
"ata_smart_data": {
|
||||
"self_test": {"status": {"value": 0, "string": "completed without error", "passed": true}}
|
||||
},
|
||||
"ata_smart_attributes": {"table": [
|
||||
{"id": 5, "name": "Reallocated_Sector_Ct", "value": 200, "worst": 200, "thresh": 140, "when_failed": "", "raw": {"value": 0, "string": "0"}},
|
||||
{"id": 9, "name": "Power_On_Hours", "value": 90, "worst": 90, "thresh": 0, "when_failed": "", "raw": {"value": 7541, "string": "7541"}},
|
||||
{"id": 194, "name": "Temperature_Celsius", "value": 116, "worst": 100, "thresh": 0, "when_failed": "", "raw": {"value": 34, "string": "34"}},
|
||||
{"id": 197, "name": "Current_Pending_Sector", "value": 200, "worst": 200, "thresh": 0, "when_failed": "", "raw": {"value": 8, "string": "8"}},
|
||||
{"id": 199, "name": "UDMA_CRC_Error_Count", "value": 200, "worst": 200, "thresh": 0, "when_failed": "", "raw": {"value": 2, "string": "2"}},
|
||||
{"id": 188, "name": "Command_Timeout", "value": 100, "worst": 100, "thresh": 0, "when_failed": "", "raw": {"value": 196611, "string": "3 3 3"}}
|
||||
]},
|
||||
"power_on_time": {"hours": 7541},
|
||||
"power_cycle_count": 21,
|
||||
"temperature": {"current": 34},
|
||||
"ata_smart_self_test_log": {"standard": {"revision": 1, "table": [
|
||||
{"type": {"value": 2, "string": "Extended offline"}, "status": {"value": 0, "string": "Completed without error", "passed": true}, "lifetime_hours": 7500},
|
||||
{"type": {"value": 1, "string": "Short offline"}, "status": {"value": 0, "string": "Completed without error", "passed": true}, "lifetime_hours": 7400}
|
||||
]}}
|
||||
}`
|
||||
|
||||
const smartNVMe = `{
|
||||
"smartctl": {"version": [7, 4], "exit_status": 0},
|
||||
"device": {"name": "/dev/nvme0", "type": "nvme", "protocol": "NVMe"},
|
||||
"model_name": "Samsung SSD 980 500GB",
|
||||
"serial_number": "S64ANL0T",
|
||||
"firmware_version": "2B4QFXO7",
|
||||
"user_capacity": {"blocks": 976773168, "bytes": 500107862016},
|
||||
"smart_support": {"available": true, "enabled": true},
|
||||
"smart_status": {"passed": true, "nvme": {"value": 0}},
|
||||
"nvme_smart_health_information_log": {
|
||||
"critical_warning": 0, "temperature": 41, "available_spare": 100, "available_spare_threshold": 10,
|
||||
"percentage_used": 3, "media_errors": 0, "num_err_log_entries": 12, "unsafe_shutdowns": 30,
|
||||
"power_cycles": 300, "power_on_hours": 1234
|
||||
},
|
||||
"temperature": {"current": 41},
|
||||
"power_cycle_count": 300,
|
||||
"power_on_time": {"hours": 1234},
|
||||
"nvme_self_test_log": {"current_self_test_operation": {"value": 0, "string": "No self-test in progress"},
|
||||
"table": [{"self_test_code": {"value": 1, "string": "Short"}, "self_test_result": {"value": 0, "string": "Completed without error"}, "power_on_hours": 1200}]}
|
||||
}`
|
||||
|
||||
const smartStandby = `{
|
||||
"smartctl": {"version": [7, 4], "exit_status": 2,
|
||||
"messages": [{"string": "Device is in STANDBY mode, exit(2)", "severity": "information"}]},
|
||||
"device": {"name": "/dev/sdb", "type": "sat", "protocol": "ATA"}
|
||||
}`
|
||||
|
||||
const smartOpenFail = `{
|
||||
"smartctl": {"version": [7, 4], "exit_status": 2,
|
||||
"messages": [{"string": "Smartctl open device: /dev/sdz failed: No such device", "severity": "error"}]}
|
||||
}`
|
||||
|
||||
const smartInProgress = `{
|
||||
"smartctl": {"version": [7, 4], "exit_status": 0},
|
||||
"device": {"protocol": "ATA"},
|
||||
"serial_number": "X",
|
||||
"smart_support": {"available": true, "enabled": true},
|
||||
"smart_status": {"passed": true},
|
||||
"ata_smart_data": {"self_test": {"status": {"value": 249, "string": "in progress, 90% remaining", "remaining_percent": 90}}},
|
||||
"ata_smart_self_test_log": {"standard": {"table": [
|
||||
{"type": {"string": "Short offline"}, "status": {"string": "Completed: read failure", "passed": false}, "lifetime_hours": 10}]}}
|
||||
}`
|
||||
|
||||
func TestParseSmartATA(t *testing.T) {
|
||||
s := parseSmart([]byte(smartATA))
|
||||
if s.Error != "" || s.Standby {
|
||||
t.Fatalf("unexpected error/standby: %+v", s)
|
||||
}
|
||||
if s.Protocol != "ATA" || s.Serial != "WD-AAA" || !s.Supported || !s.Healthy {
|
||||
t.Errorf("basic fields: %+v", s)
|
||||
}
|
||||
if !s.HasTemp || s.TempC != 34 || s.PowerOnHrs != 7541 || s.PowerCycles != 21 {
|
||||
t.Errorf("temp/hours: %+v", s)
|
||||
}
|
||||
if a, ok := s.Attr(197); !ok || a.Raw != 8 {
|
||||
t.Errorf("attr 197: %+v %v", a, ok)
|
||||
}
|
||||
if a, ok := s.Attr(188); !ok || a.Raw != 3 {
|
||||
t.Errorf("attr 188 must be unpacked to the low word: %+v", a)
|
||||
}
|
||||
if s.LastTest == nil || s.LastTest.InProgress || !s.LastTest.Passed || s.LastTest.Type != "Extended offline" || s.LastTest.Hours != 7500 {
|
||||
t.Errorf("last test: %+v", s.LastTest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSmartNVMe(t *testing.T) {
|
||||
s := parseSmart([]byte(smartNVMe))
|
||||
if s.NVMe == nil {
|
||||
t.Fatalf("nvme log missing: %+v", s)
|
||||
}
|
||||
if s.NVMe.PercentageUsed != 3 || s.NVMe.AvailableSpare != 100 || s.NVMe.ErrLogEntries != 12 {
|
||||
t.Errorf("nvme: %+v", s.NVMe)
|
||||
}
|
||||
if s.TempC != 41 || s.PowerOnHrs != 1234 || !s.Healthy {
|
||||
t.Errorf("fields: %+v", s)
|
||||
}
|
||||
if s.LastTest == nil || s.LastTest.Type != "Short" || !s.LastTest.Passed {
|
||||
t.Errorf("last test: %+v", s.LastTest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSmartStandby(t *testing.T) {
|
||||
s := parseSmart([]byte(smartStandby))
|
||||
if !s.Standby || s.Error != "" {
|
||||
t.Errorf("standby not detected: %+v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSmartOpenFail(t *testing.T) {
|
||||
s := parseSmart([]byte(smartOpenFail))
|
||||
if s.Standby || s.Error == "" || s.Supported {
|
||||
t.Errorf("open failure not reported: %+v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSmartInProgress(t *testing.T) {
|
||||
s := parseSmart([]byte(smartInProgress))
|
||||
if s.LastTest == nil || !s.LastTest.InProgress || s.LastTest.Remaining != 90 {
|
||||
t.Errorf("in-progress test: %+v", s.LastTest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSmartGarbage(t *testing.T) {
|
||||
s := parseSmart([]byte("not json"))
|
||||
if s.Error == "" {
|
||||
t.Errorf("expected parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLsblk(t *testing.T) {
|
||||
in := `{"blockdevices": [
|
||||
{"name":"sda","path":"/dev/sda","type":"disk","model":"WDC WD80EFZZ-68BTXN0","serial":"WD-AAA","size":8001563222016,"rota":true,"tran":"sata"},
|
||||
{"name":"nvme0n1","path":"/dev/nvme0n1","type":"disk","model":"Samsung SSD 980 500GB","serial":"S64ANL0T","size":500107862016,"rota":false,"tran":"nvme"},
|
||||
{"name":"loop0","path":"/dev/loop0","type":"loop","model":null,"serial":null,"size":4096,"rota":false,"tran":null},
|
||||
{"name":"zd0","path":"/dev/zd0","type":"disk","model":null,"serial":null,"size":4096,"rota":false,"tran":null},
|
||||
{"name":"sr0","path":"/dev/sr0","type":"rom","model":"DVD","serial":null,"size":1024,"rota":true,"tran":"sata"}
|
||||
]}`
|
||||
disks, err := parseLsblk([]byte(in))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(disks) != 2 || disks[0].Name != "sda" || !disks[0].Rota || disks[1].Tran != "nvme" || disks[1].Rota {
|
||||
t.Errorf("disks = %+v", disks)
|
||||
}
|
||||
// older util-linux prints rota as a string
|
||||
old := `{"blockdevices": [{"name":"sda","type":"disk","size":"1000","rota":"1","tran":"sata"}]}`
|
||||
disks, err = parseLsblk([]byte(old))
|
||||
if err != nil || len(disks) != 1 || !disks[0].Rota || disks[0].Path != "/dev/sda" {
|
||||
t.Errorf("old lsblk: %+v %v", disks, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user