Files
nasmon/internal/collect/zfs_test.go
T
a.gritsenkoandClaude Opus 5.5 0cdee29359
build / build (push) Successful in 1m4s
ZFS: dedup toggle per pool and dedup option for new datasets
Pool cards show the dedup ratio and can set dedup=on|off on the pool root
dataset (inherited by children); the new-dataset form gets a dedup option.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:30:38 +03:00

144 lines
4.7 KiB
Go

package collect
import (
"testing"
"time"
)
const zpoolStatusOK = ` pool: nas_pool_1_8_gb
state: ONLINE
scan: scrub repaired 0B in 03:12:41 with 0 errors on Sun Sep 7 03:36:42 2025
config:
NAME STATE READ WRITE CKSUM
nas_pool_1_8_gb ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
ata-WDC_WD80EFZZ-68BTXN0_WD-AAA ONLINE 0 0 0
ata-WDC_WD80EFZZ-68BTXN0_WD-BBB ONLINE 0 0 0
errors: No known data errors
`
const zpoolStatusDegraded = ` pool: tank
state: DEGRADED
status: One or more devices could not be opened. Sufficient replicas exist for
the pool to continue functioning in a degraded state.
action: Attach the missing device and online it using 'zpool online'.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q
scan: scrub in progress since Sat Sep 13 01:00:00 2025
1.20T / 7.10T scanned at 1.02G/s, 500G / 7.10T issued at 430M/s
0B repaired, 6.88% done, 04:28:12 to go
config:
NAME STATE READ WRITE CKSUM
tank DEGRADED 0 0 0
mirror-0 DEGRADED 0 0 0
ata-DISK_A ONLINE 0 0 3
1234567890123456789 UNAVAIL 0 0 0 was /dev/sdb1
logs
nvme0n1p2 ONLINE 0 0 0
errors: Permanent errors have been detected in the following files:
/tank/data/file.bin
`
func TestParseZpoolStatusOK(t *testing.T) {
p := Pool{Name: "nas_pool_1_8_gb"}
parseZpoolStatus([]byte(zpoolStatusOK), &p)
if p.Health != "ONLINE" {
t.Errorf("health = %q", p.Health)
}
if p.Errors != "No known data errors" {
t.Errorf("errors = %q", p.Errors)
}
if p.Scrubbing || p.ScanErrs != 0 {
t.Errorf("scan flags wrong: %+v", p)
}
if p.LastScrub == nil {
t.Fatalf("last scrub not parsed from %q", p.Scan)
}
want := time.Date(2025, 9, 7, 3, 36, 42, 0, time.Local)
if !p.LastScrub.Equal(want) {
t.Errorf("last scrub = %v, want %v", p.LastScrub, want)
}
if len(p.Vdevs) != 4 {
t.Fatalf("vdevs = %d: %+v", len(p.Vdevs), p.Vdevs)
}
if p.Vdevs[0].Depth != 0 || p.Vdevs[1].Depth != 1 || p.Vdevs[2].Depth != 2 {
t.Errorf("depths wrong: %+v", p.Vdevs)
}
if p.Vdevs[1].Name != "mirror-0" || p.Vdevs[1].State != "ONLINE" {
t.Errorf("mirror vdev wrong: %+v", p.Vdevs[1])
}
}
func TestParseZpoolStatusDegraded(t *testing.T) {
p := Pool{Name: "tank"}
parseZpoolStatus([]byte(zpoolStatusDegraded), &p)
if p.Health != "DEGRADED" {
t.Errorf("health = %q", p.Health)
}
if !p.Scrubbing {
t.Errorf("scrub in progress not detected: %q", p.Scan)
}
if p.LastScrub != nil {
t.Errorf("last scrub must be nil while scrubbing")
}
if p.Status == "" || p.Action == "" {
t.Errorf("status/action not captured: %q / %q", p.Status, p.Action)
}
if p.Errors == "" || p.Errors == "No known data errors" {
t.Errorf("errors = %q", p.Errors)
}
var unavail, cksum, logs bool
for _, v := range p.Vdevs {
switch {
case v.State == "UNAVAIL" && v.Note == "was /dev/sdb1":
unavail = true
case v.Name == "ata-DISK_A" && v.Cksum == 3:
cksum = true
case v.Name == "logs" && v.State == "":
logs = true
}
}
if !unavail || !cksum || !logs {
t.Errorf("vdev parse: unavail=%v cksum=%v logs=%v %+v", unavail, cksum, logs, p.Vdevs)
}
}
func TestParseZpoolList(t *testing.T) {
out := "nas_pool_1_8_gb\t7992048291840\t3500000000000\t4492048291840\t43\tONLINE\t7\ntmp_pool\t1000000000000\t900000000000\t100000000000\t90\tONLINE\t-\n"
pools := parseZpoolList([]byte(out))
if len(pools) != 2 {
t.Fatalf("pools = %d", len(pools))
}
if pools[0].CapPct != 43 || pools[0].FragPct != 7 || pools[0].Health != "ONLINE" {
t.Errorf("pool0 = %+v", pools[0])
}
if pools[1].CapPct != 90 || pools[1].FragPct != 0 {
t.Errorf("pool1 = %+v", pools[1])
}
if pools[0].DedupRatio != 0 {
t.Errorf("dedup ratio without the column = %v", pools[0].DedupRatio)
}
// with the dedup column; -p prints "1.35", without -p it is "1.35x"
pools = parseZpoolList([]byte("a\t1\t1\t0\t100\tONLINE\t0\t1.35\nb\t1\t1\t0\t100\tONLINE\t0\t2.00x\n"))
if len(pools) != 2 || pools[0].DedupRatio != 1.35 || pools[1].DedupRatio != 2 {
t.Errorf("dedup ratio = %+v", pools)
}
}
func TestParseZfsList(t *testing.T) {
out := "nas_pool_1_8_gb\t100\t200\t50\t/nas_pool_1_8_gb\nnas_pool_1_8_gb/appdata\t10\t200\t10\t/nas_pool_1_8_gb/appdata\n"
ds := parseZfsList([]byte(out))
if len(ds) != 2 || ds[1].Name != "nas_pool_1_8_gb/appdata" || ds[1].Avail != 200 {
t.Errorf("datasets = %+v", ds)
}
ds = parseZfsList([]byte("tank\t1\t2\t3\t/tank\ton\n"))
if len(ds) != 1 || ds[0].Dedup != "on" || ds[0].Mountpoint != "/tank" {
t.Errorf("dedup column: %+v", ds)
}
}