ZFS: dedup toggle per pool and dedup option for new datasets
build / build (push) Successful in 1m4s
build / build (push) Successful in 1m4s
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>
This commit is contained in:
@@ -68,12 +68,12 @@ func newTestServer(t *testing.T) (*Server, *sched.Scheduler, *store.Store) {
|
||||
},
|
||||
Pools: []collect.Pool{
|
||||
{Name: "tank", Health: "DEGRADED", Size: 8 << 40, Alloc: 3 << 40, Free: 5 << 40, CapPct: 37, FragPct: 5, Scan: "scrub repaired 0B", LastScrub: &old,
|
||||
Status: "One or more devices could not be opened.", Action: "Attach the missing device", Errors: "No known data errors",
|
||||
Status: "One or more devices could not be opened.", Action: "Attach the missing device", Errors: "No known data errors", DedupRatio: 1.42,
|
||||
Vdevs: []collect.Vdev{{Name: "tank", State: "DEGRADED"}, {Name: "mirror-0", Depth: 1, State: "DEGRADED"}, {Name: "sda", Depth: 2, State: "ONLINE", Cksum: 2}, {Name: "123", Depth: 2, State: "UNAVAIL", Note: "was /dev/sdb1"}}},
|
||||
{Name: "tmp_pool", Health: "ONLINE", Size: 1 << 40, Alloc: 900 << 30, Free: 124 << 30, CapPct: 88, Scrubbing: true, Scan: "scrub in progress"},
|
||||
},
|
||||
Datasets: []collect.Dataset{
|
||||
{Name: "tank", Used: 3 << 40, Avail: 5 << 40, Refer: 1 << 20, Mountpoint: "/tank"},
|
||||
{Name: "tank", Used: 3 << 40, Avail: 5 << 40, Refer: 1 << 20, Mountpoint: "/tank", Dedup: "on"},
|
||||
{Name: "tank/empty", Used: 96 << 10, Avail: 5 << 40, Refer: 96 << 10, Mountpoint: "/tank/empty"},
|
||||
{Name: "tank/data", Used: 3 << 40, Avail: 5 << 40, Refer: 3 << 40, Mountpoint: "/tank/data"},
|
||||
{Name: "tank/parent", Used: 96 << 10, Avail: 5 << 40, Refer: 96 << 10, Mountpoint: "/tank/parent"},
|
||||
@@ -245,7 +245,7 @@ func TestZfsPageControls(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/zfs?msg=hello", nil))
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{"New dataset in tank", "New dataset in tmp_pool", "hello", `value="1000:1000"`, "type tank/empty to confirm"} {
|
||||
for _, want := range []string{"New dataset in tank", "New dataset in tmp_pool", "hello", `value="1000:1000"`, "type tank/empty to confirm", `action="/zfs/dedup"`, "Dedup for tmp_pool", "ratio 1.42x"} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("zfs page missing %q", want)
|
||||
}
|
||||
@@ -274,6 +274,7 @@ func TestZfsCreate(t *testing.T) {
|
||||
{"pool": {"tank"}, "name": {"x"}, "quota": {"lots"}}, // bad quota
|
||||
{"pool": {"tank"}, "name": {"x"}, "owner": {"ansible"}}, // bad owner
|
||||
{"pool": {"tank"}, "name": {"x"}, "compression": {"gzip"}},
|
||||
{"pool": {"tank"}, "name": {"x"}, "dedup": {"verify"}},
|
||||
}
|
||||
for _, form := range bad {
|
||||
f.calls = nil
|
||||
@@ -289,11 +290,11 @@ func TestZfsCreate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
rec := postForm(srv, "/zfs/create", url.Values{"pool": {"tank"}, "name": {"/media/video/"}, "owner": {"1000:1000"}, "compression": {"lz4"}, "quota": {"100g"}})
|
||||
rec := postForm(srv, "/zfs/create", url.Values{"pool": {"tank"}, "name": {"/media/video/"}, "owner": {"1000:1000"}, "compression": {"lz4"}, "dedup": {"on"}, "quota": {"100g"}})
|
||||
if rec.Code != http.StatusSeeOther || !strings.Contains(rec.Header().Get("Location"), "created") {
|
||||
t.Fatalf("status %d location %q body %s", rec.Code, rec.Header().Get("Location"), rec.Body.String())
|
||||
}
|
||||
want := "zfs create -p -o compression=lz4 -o quota=100G tank/media/video"
|
||||
want := "zfs create -p -o compression=lz4 -o dedup=on -o quota=100G tank/media/video"
|
||||
if len(f.calls) != 3 || f.calls[0] != want || f.calls[2] != "chown 1000:1000 /tank/media/video" {
|
||||
t.Errorf("calls = %v", f.calls)
|
||||
}
|
||||
@@ -307,6 +308,44 @@ func TestZfsCreate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestZfsDedup(t *testing.T) {
|
||||
srv, _, _ := newTestServer(t)
|
||||
f := &fakeRunner{}
|
||||
srv.admin = zfsadm.New(f.run)
|
||||
|
||||
bad := []url.Values{
|
||||
{"pool": {"nope"}, "dedup": {"on"}}, // unknown pool
|
||||
{"pool": {"tank/data"}, "dedup": {"on"}}, // not a pool
|
||||
{"pool": {"tank"}, "dedup": {"verify"}}, // unsupported value
|
||||
{"pool": {"tank"}}, // missing value
|
||||
}
|
||||
for _, form := range bad {
|
||||
f.calls = nil
|
||||
rec := postForm(srv, "/zfs/dedup", form)
|
||||
if rec.Code != 200 || !strings.Contains(rec.Body.String(), "Dedup not changed") {
|
||||
t.Errorf("%v: status %d, no error shown", form, rec.Code)
|
||||
}
|
||||
if len(f.calls) != 0 {
|
||||
t.Errorf("%v: commands ran: %v", form, f.calls)
|
||||
}
|
||||
}
|
||||
|
||||
rec := postForm(srv, "/zfs/dedup", url.Values{"pool": {"tmp_pool"}, "dedup": {"on"}})
|
||||
if rec.Code != http.StatusSeeOther || !strings.Contains(rec.Header().Get("Location"), "#tmp_pool") {
|
||||
t.Fatalf("status %d location %q", rec.Code, rec.Header().Get("Location"))
|
||||
}
|
||||
if len(f.calls) != 1 || f.calls[0] != "zfs set dedup=on tmp_pool" {
|
||||
t.Errorf("calls = %v", f.calls)
|
||||
}
|
||||
|
||||
f = &fakeRunner{fail: "zfs set"}
|
||||
srv.admin = zfsadm.New(f.run)
|
||||
rec = postForm(srv, "/zfs/dedup", url.Values{"pool": {"tank"}, "dedup": {"off"}})
|
||||
if rec.Code != 200 || !strings.Contains(rec.Body.String(), "cannot do that") {
|
||||
t.Errorf("failure not reported: %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZfsDestroy(t *testing.T) {
|
||||
srv, _, _ := newTestServer(t)
|
||||
f := &fakeRunner{}
|
||||
|
||||
Reference in New Issue
Block a user