package main import ( "context" "strings" "testing" ) // §16, §31 convergence table: a property at its value runs nothing; a set one is set and read back and // said; `default` is ZFS's default for a pool property and an inherit for a dataset's; a read-back that // differs is a failure. func TestPropertiesConvergeFromWhatZFSSays(t *testing.T) { cfg := func(j string) *Config { c, ref := ParseConfig([]byte(j), 16000) if len(ref) != 0 { t.Fatal(ref) } return c } type step struct{ cmd, out string } cases := []struct { name string conf string reads []step // read answers writes []string after map[string]string // a read that changes once the write ran events int fails bool }{ {"pool property at its value", `{"pool":"tank","pool-properties":{"autotrim":"on"}}`, []step{{"zpool get -Hp -o value autotrim -- tank", "on\n"}}, nil, nil, 0, false}, {"pool property set", `{"pool":"tank","pool-properties":{"autotrim":"on"}}`, []step{{"zpool get -Hp -o value autotrim -- tank", "off\n"}}, []string{"zpool set autotrim=on -- tank"}, map[string]string{"zpool get -Hp -o value autotrim -- tank": "on\n"}, 1, false}, {"pool property to ZFS's default", `{"pool":"tank","pool-properties":{"failmode":"default"}}`, []step{{"zpool get -Hp -o value failmode -- tank", "continue\n"}}, []string{"zpool set failmode=wait -- tank"}, map[string]string{"zpool get -Hp -o value failmode -- tank": "wait\n"}, 1, false}, {"pool property read back differs", `{"pool":"tank","pool-properties":{"autotrim":"on"}}`, []step{{"zpool get -Hp -o value autotrim -- tank", "off\n"}}, []string{"zpool set autotrim=on -- tank"}, nil, 0, true}, {"dataset size at its value in bytes", `{"pool":"tank","dataset-properties":{"tank/a":{"recordsize":"1M"}}}`, []step{{"zfs get -Hp -o value,source recordsize -- tank/a", "1048576\tlocal\n"}}, nil, nil, 0, false}, {"dataset compression set", `{"pool":"tank","dataset-properties":{"tank":{"compression":"zstd-3"}}}`, []step{{"zfs get -Hp -o value,source compression -- tank", "lz4\tlocal\n"}}, []string{"zfs set compression=zstd-3 -- tank"}, map[string]string{"zfs get -Hp -o value,source compression -- tank": "zstd-3\tlocal\n"}, 1, false}, {"dataset default of an inherited one", `{"pool":"tank","dataset-properties":{"tank/a":{"atime":"default"}}}`, []step{{"zfs get -Hp -o value,source atime -- tank/a", "on\tinherited from tank\n"}}, nil, nil, 0, false}, {"dataset default of a local one", `{"pool":"tank","dataset-properties":{"tank/a":{"atime":"default"}}}`, []step{{"zfs get -Hp -o value,source atime -- tank/a", "off\tlocal\n"}}, []string{"zfs inherit atime -- tank/a"}, map[string]string{"zfs get -Hp -o value,source atime -- tank/a": "on\tdefault\n"}, 1, false}, {"quota none at zero", `{"pool":"tank","dataset-properties":{"tank/a":{"quota":"none"}}}`, []step{{"zfs get -Hp -o value,source quota -- tank/a", "0\tdefault\n"}}, nil, nil, 0, false}, } for _, c := range cases { f := newFake(t) f.uid = 0 for _, r := range c.reads { f.on(r.cmd, r.out) } wrote := map[string]bool{} for _, w := range c.writes { w := w f.on(w, "") f.after[w] = func(f *fake) { wrote[w] = true for k, v := range c.after { f.on(k, v) } } } m := f.machine() m.States = memStates{} err := m.ApplyProperties(context.Background(), cfg(c.conf)) if (err != nil) != c.fails { t.Errorf("%s: error %v", c.name, err) } for _, w := range c.writes { if !wrote[w] { t.Errorf("%s: %q not run; ran %v", c.name, w, f.calls) } } for _, call := range f.calls { if (strings.Contains(call, " set ") || strings.Contains(call, " inherit ")) && !contains(c.writes, call) { t.Errorf("%s: ran %q", c.name, call) } } if len(f.emitted) != c.events { t.Errorf("%s: %d events, want %d: %v", c.name, len(f.emitted), c.events, f.emitted) } } } // §10: a dataset's sharenfs and sharesmb are never converged, never run: the parse refuses them, so the // apply has no Config to run. func TestSharenfsAndSharesmbAreNeverASetProperty(t *testing.T) { for _, p := range []string{"sharenfs", "sharesmb"} { if c, ref := ParseConfig([]byte(`{"pool":"tank","dataset-properties":{"tank":{"`+p+`":"on"}}}`), 16000); c != nil || len(ref) == 0 { t.Errorf("%s accepted", p) } } }