package main import ( "strings" "testing" ) // Design 42 zfs-owns-zfs §11: each value of a fixed form, not managed until set and not empty; a value of // another form is refused before anything is written or run (§31, one table per value). func TestEachValueHasItsFormAndABlankOneIsNotManaged(t *testing.T) { bad := func(key, val string) string { return `{"pool":"tank","` + key + `":` + val + `}` } refused := []struct{ name, conf, why string }{ {"pool with a space", `{"pool":"ta nk"}`, "pool's name"}, {"pool with a newline", `{"pool":"tank\nx"}`, "pool's name"}, {"pool not a string", `{"pool":3}`, "not a string"}, {"arc-max below 64", bad("arc-max", "63"), "below 64"}, {"arc-max at memory", bad("arc-max", "16000"), "not below the node's memory"}, {"arc-max fraction", bad("arc-max", "100.5"), "whole number"}, {"arc-max string", bad("arc-max", `"4096"`), "whole number"}, {"arc-min above arc-max", `{"arc-max":1000,"arc-min":2000}`, "above arc-max"}, {"module option of arc", bad("module-options", `["zfs_arc_max=1"]`), "arc-max and arc-min"}, {"module option unknown", bad("module-options", `["zfs_foo=1"]`), "not a parameter"}, {"module option not digits", bad("module-options", `["zfs_txg_timeout=5;x"]`), "not name=digits"}, {"module option twice", bad("module-options", `["zfs_txg_timeout=5","zfs_txg_timeout=6"]`), "twice"}, {"hostid short", bad("hostid", `"abc"`), "eight hexadecimal"}, {"hostid zero", bad("hostid", `"00000000"`), "all zero"}, {"hostid backtick", bad("hostid", "\"0123456`\""), "eight hexadecimal"}, {"zed value", bad("zed", `"yes"`), "on, off"}, {"zed handler", bad("zed-handlers", `["notify"]`), "not all-syslog"}, {"trim schedule", bad("trim-schedule", `"daily"`), "none, weekly, monthly"}, {"trim day", bad("trim-day", `"Monday"`), "Mon"}, {"trim time", bad("trim-time", `"24:00"`), "HH:MM"}, {"trim schedule without day or time", `{"trim-schedule":"weekly","trim-time":"03:00"}`, "needs trim-day"}, {"trim schedule without time", `{"trim-schedule":"monthly","trim-day":"Sun"}`, "needs trim-time"}, {"cache device with a slash", bad("cache-devices", `["a/b"]`), "under /dev/disk/by-id"}, {"cache device with a dollar", bad("cache-devices", `["a$b"]`), "under /dev/disk/by-id"}, {"cache device with a quote", bad("cache-devices", `["a\"b"]`), "under /dev/disk/by-id"}, {"log device with a semicolon", bad("log-devices", `["a;b"]`), "under /dev/disk/by-id"}, {"log mirror of one", `{"log-mirror":true,"log-devices":["a"]}`, "exactly two"}, {"log mirror not a boolean", bad("log-mirror", `"yes"`), "true or false"}, {"pool property", bad("pool-properties", `{"ashift":"12"}`), "not a pool property"}, {"pool property value", bad("pool-properties", `{"failmode":"loud"}`), "failmode=loud"}, {"dataset of another pool", bad("dataset-properties", `{"other/x":{"atime":"off"}}`), "not the pool's name"}, {"dataset property sharenfs", bad("dataset-properties", `{"tank":{"sharenfs":"on"}}`), "not a dataset property"}, {"dataset property sharesmb", bad("dataset-properties", `{"tank":{"sharesmb":"on"}}`), "not a dataset property"}, {"dataset recordsize", bad("dataset-properties", `{"tank/a":{"recordsize":"100K"}}`), "power of two"}, {"dataset compression", bad("dataset-properties", `{"tank/a":{"compression":"zstd-20"}}`), "compression"}, {"dataset quota", bad("dataset-properties", `{"tank/a":{"quota":"5X"}}`), "quota"}, {"dataset name with a snapshot mark", bad("dataset-properties", `{"tank/a@x":{"atime":"on"}}`), "dataset's name"}, {"snapshots above the bound", bad("snapshots", `{"hourly":169}`), "0 to 168"}, {"snapshots unknown kind", bad("snapshots", `{"yearly":1}`), "not none, hourly"}, {"snapshots none with counts", bad("snapshots", `{"none":true,"daily":1}`), "none is given"}, {"scrub cadence", bad("scrub-cadence", `"daily"`), "weekly, monthly"}, {"not an object", `[1]`, "not a JSON object"}, } for _, c := range refused { cfg, ref := ParseConfig([]byte(c.conf), 16000) if cfg != nil || len(ref) == 0 || !strings.Contains(ref[0].Reason, c.why) { t.Errorf("%s: %+v %+v, want a refusal saying %q", c.name, cfg, ref, c.why) } } // Without the node's memory arc-max cannot be checked. if _, ref := ParseConfig([]byte(`{"arc-max":1000}`), 0); len(ref) != 1 || ref[0].Key != "arc-max" { t.Errorf("arc-max with no memory read: %v", ref) } // Absent, empty and null are not managed: no refusal, nothing set. for _, conf := range []string{`{}`, `{"pool":"tank","cache-devices":[],"log-devices":[],"module-options":[],"zed-handlers":[], "pool-properties":{},"dataset-properties":{},"snapshots":{},"hostid":"","zed":"","trim-schedule":"","arc-max":null}`} { c, ref := ParseConfig([]byte(conf), 16000) if len(ref) != 0 || c.ArcMax != nil || c.HostID != "" || c.Zed != "" || c.TrimSchedule != "" || c.Snapshots != nil || len(c.CacheDevices)+len(c.LogDevices)+len(c.ModuleOptions)+len(c.ZedHandlers)+len(c.PoolProperties)+len(c.DatasetProperties) != 0 { t.Errorf("%s: %+v %v", conf, c, ref) } } } func TestAWholeConfigIsReadValueByValue(t *testing.T) { c, ref := ParseConfig([]byte(`{"pool":"tank","scrub-cadence":"weekly","scrub-day":"Mon","scrub-time":"00:00", "arc-max":8192,"arc-min":1024,"module-options":["zfs_txg_timeout=10"],"hostid":"0A1B2C3D","zed":"on", "zed-handlers":["all-syslog"],"trim-schedule":"weekly","trim-day":"Sun","trim-time":"03:00", "cache-devices":["nvme-a","nvme-b"],"log-devices":["nvme-c","nvme-d"],"log-mirror":true, "pool-properties":{"autotrim":"on","failmode":"default"}, "dataset-properties":{"tank":{"compression":"zstd-3"},"tank/media":{"recordsize":"1M","quota":"5T"}}, "snapshots":{"hourly":24,"daily":7}}`), 16000) if len(ref) != 0 { t.Fatal(ref) } if c.Pool != "tank" || *c.ArcMax != 8192 || *c.ArcMin != 1024 || c.HostID != "0a1b2c3d" || c.Zed != "on" || c.ModuleOptions[0] != (ModuleOption{"zfs_txg_timeout", "10"}) || len(c.CacheDevices) != 2 || !*c.LogMirror || c.PoolProperties["autotrim"] != "on" || c.DatasetProperties["tank/media"]["quota"] != "5T" || c.Snapshots.Of("hourly") != 24 || c.Snapshots.Of("monthly") != 0 || c.Snapshots.None { t.Errorf("%+v", c) } if c, _ := ParseConfig([]byte(`{"snapshots":{"none":true}}`), 0); !c.Snapshots.None { t.Errorf("none: %+v", c.Snapshots) } }