Z1 Z2 Z3 zfs-manifest: packages and values, files and units, the apply and the run in the declared order
The manifest declares zfs-utils and zfs-dkms, renames scrub-pool to pool, and declares every new value not managed until set; its config file is ${config-json}, the options file, ZED's folder, rc, functions, handlers and drop-in each under their condition, the three ZFS units, the apply (run-once, restarted by its config file) and the run before the distribution's scrub and trim timers. One commit: the three subtasks change the one file module.json.
Builds: design 42 zfs-owns-zfs §6, §7, §8, §9, §11, §12, §13, §14, §15, §18, §20, A-1, A-2, A-3, A-7, A-14, A-15, A-16, A-23
This commit is contained in:
@@ -2,13 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type manifest struct {
|
||||
@@ -84,15 +84,203 @@ func TestTheManifestNamesTheToolsTheBundleServesAndWhatEachReplaces(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
// ADR 0240: zfs_health on the watch, which runs as the operator account; the follower judged by its
|
||||
// unit. The scheduled run is not long-running and declares none. The module installs nothing, so it never
|
||||
// puts ZFS on a machine.
|
||||
func TestTheHealthChecksAndWhoRunsWhat(t *testing.T) {
|
||||
m, raw := readManifest(t)
|
||||
byID := map[string]map[string]any{}
|
||||
for _, r := range m.Resources {
|
||||
byID[r["id"].(string)] = r
|
||||
// manifestOf is the whole manifest as a tree, for the tests that read a field the typed view does not.
|
||||
func manifestOf(t *testing.T) (map[string]any, map[string]map[string]any, map[string]int) {
|
||||
t.Helper()
|
||||
_, raw := readManifest(t)
|
||||
var tree map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &tree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
byID, order := map[string]map[string]any{}, map[string]int{}
|
||||
for i, r := range tree["resources"].([]any) {
|
||||
res := r.(map[string]any)
|
||||
byID[res["id"].(string)] = res
|
||||
order[res["id"].(string)] = i
|
||||
}
|
||||
return tree, byID, order
|
||||
}
|
||||
|
||||
// conditions are the `if-config` of a resource, as written.
|
||||
func conditions(r map[string]any) string {
|
||||
var out []string
|
||||
l, _ := r["if-config"].([]any)
|
||||
for _, c := range l {
|
||||
out = append(out, c.(string))
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
// §7, §8 (A-1), §11 (A-14): the two packages are declared and the debug one is not; `pool` renames `scrub-pool`; each new value is
|
||||
// not managed until set (no default, not required), of the form and trust §11 lists.
|
||||
func TestTheManifestDeclaresThePackagesAndEveryValueAsSections7To11Say(t *testing.T) {
|
||||
tree, byID, _ := manifestOf(t)
|
||||
for id, pkg := range map[string]string{"utils": "zfs-utils", "dkms": "zfs-dkms"} {
|
||||
if r := byID[id]; r == nil || r["type"] != "package" || r["package"] != pkg || r["state"] != nil {
|
||||
t.Errorf("%s: %v", id, r)
|
||||
}
|
||||
}
|
||||
for _, r := range byID {
|
||||
if r["package"] == "zfs-utils-debug" {
|
||||
t.Errorf("the debug package is not the module's: %v", r)
|
||||
}
|
||||
}
|
||||
cfg := tree["config"].(map[string]any)
|
||||
if _, old := cfg["scrub-pool"]; old {
|
||||
t.Errorf("scrub-pool is declared; it is only in renamed-from")
|
||||
}
|
||||
if p := cfg["pool"].(map[string]any); p["renamed-from"] != "scrub-pool" || p["type"] != "string" || p["trusted"] != true || p["required"] != true || p["default"] != nil {
|
||||
t.Errorf("pool: %v", p)
|
||||
}
|
||||
for k, want := range map[string]string{"scrub-cadence": "string/false", "scrub-day": "string/false", "scrub-time": "string/false"} {
|
||||
if v := cfg[k].(map[string]any); v["default"] == nil || fmt.Sprint(v["type"]) != strings.Split(want, "/")[0] {
|
||||
t.Errorf("%s keeps its default: %v", k, v)
|
||||
}
|
||||
}
|
||||
new := map[string]string{"arc-max": "number/false", "arc-min": "number/false", "module-options": "list/true", "hostid": "string/true", "zed": "string/false",
|
||||
"zed-handlers": "list/true", "trim-schedule": "string/false", "trim-day": "string/false", "trim-time": "string/false", "cache-devices": "list/true",
|
||||
"log-devices": "list/true", "log-mirror": "boolean/true", "pool-properties": "object/true", "dataset-properties": "object/true", "snapshots": "object/true"}
|
||||
for k, want := range new {
|
||||
v, ok := cfg[k].(map[string]any)
|
||||
if !ok {
|
||||
t.Errorf("%s is not declared", k)
|
||||
continue
|
||||
}
|
||||
if got := fmt.Sprint(v["type"], "/", v["trusted"]); got != want || v["default"] != nil || v["required"] != nil {
|
||||
t.Errorf("%s: %s with default %v required %v, want %s not managed until set", k, got, v["default"], v["required"], want)
|
||||
}
|
||||
}
|
||||
if len(cfg) != len(new)+4 {
|
||||
t.Errorf("%d values declared, want %d", len(cfg), len(new)+4)
|
||||
}
|
||||
if d := fmt.Sprint(cfg["hostid"].(map[string]any)["description"]); !strings.Contains(d, "check of the pool's import at the next boot") {
|
||||
t.Errorf("hostid's description does not say the change needs a check of the pool's import: %s", d)
|
||||
}
|
||||
}
|
||||
|
||||
// §9 (A-15, A-23), §12, §14 (A-2, A-7), §15: the config file is `${config-json}` and not trusted; the options file, ZED's folder, rc,
|
||||
// functions, handlers and drop-in each under their condition; no link and no resource or template for /etc/hostid.
|
||||
func TestTheManifestsFilesAreAsSections9To15Say(t *testing.T) {
|
||||
_, byID, _ := manifestOf(t)
|
||||
c := byID["config"]
|
||||
if c["path"] != ConfigFile || c["mode"] != "0644" || c["trusted"] != false || c["owner"] != nil || strings.TrimSpace(templateText(t, c)) != "${config-json}" {
|
||||
t.Errorf("the config file: %v", c)
|
||||
}
|
||||
if byID["scrub-config"] != nil {
|
||||
t.Errorf("scrub-config is gone: the node-engine removes the old file as a resource that left")
|
||||
}
|
||||
zedFiles := map[string]string{"zed-rc": "/etc/zfs/mesh-zed.d/zed.rc", "zed-functions": "/etc/zfs/mesh-zed.d/zed-functions.sh",
|
||||
"zed-handler-all-syslog": "/etc/zfs/mesh-zed.d/all-syslog.sh", "zed-handler-scrub-after-resilver": "/etc/zfs/mesh-zed.d/resilver_finish-start-scrub.sh",
|
||||
"zed-drop-in": "/etc/systemd/system/zfs-zed.service.d/mesh.conf"}
|
||||
for id, path := range zedFiles {
|
||||
r := byID[id]
|
||||
want := "zed=on"
|
||||
mode := "0644"
|
||||
switch id {
|
||||
case "zed-handler-all-syslog":
|
||||
want, mode = "zed-handlers=all-syslog", "0755"
|
||||
case "zed-handler-scrub-after-resilver":
|
||||
want, mode = "zed-handlers=resilver_finish-start-scrub", "0755"
|
||||
}
|
||||
if r == nil || r["type"] != "file" || r["path"] != path || conditions(r) != want || r["mode"] != mode {
|
||||
t.Errorf("%s: %v", id, r)
|
||||
}
|
||||
}
|
||||
if d := byID["zed-dir"]; d["type"] != "directory" || d["path"] != "/etc/zfs/mesh-zed.d" || d["unknown"] != "module" || d["mode"] != "0755" || conditions(d) != "zed=on" {
|
||||
t.Errorf("the folder: %v", d)
|
||||
}
|
||||
if o := byID["options"]; o["path"] != "/etc/modprobe.d/zfs.conf" || o["mode"] != "0644" || conditions(o) != "arc-max,arc-min,module-options" {
|
||||
t.Errorf("the options file: %v", o)
|
||||
}
|
||||
if d := byID["run-dir"]; d["path"] != "/run/zfs-tools" || d["unknown"] != "program" || filepath.Dir(ApplyLock) != d["path"] {
|
||||
t.Errorf("the lock's folder: %v", d)
|
||||
}
|
||||
for id, r := range byID {
|
||||
if r["type"] == "symlink" || r["type"] == "link" || r["target"] != nil {
|
||||
t.Errorf("%s is a link: the mesh creates none", id)
|
||||
}
|
||||
if r["path"] == "/etc/hostid" {
|
||||
t.Errorf("%s places /etc/hostid: the apply writes it with ZFS's own writer", id)
|
||||
}
|
||||
if r["type"] == "file" && strings.Contains(templateText(t, r), "/etc/hostid") {
|
||||
t.Errorf("%s's template names /etc/hostid", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// §6, §13 (A-3), §18 (A-16): the run's resource precedes the resources that stop the scrub timers, with its schedule;
|
||||
// each unit has the state and condition §13 lists; the trim timers are held stopped under none, weekly and monthly; the apply is
|
||||
// run-once and starts again on the config file's change.
|
||||
func TestTheManifestsUnitsAndTheRunAreAsSections6And13Say(t *testing.T) {
|
||||
_, byID, order := manifestOf(t)
|
||||
run := byID["run"]
|
||||
if run["type"] != "process" || run["name"] != "zfs-mesh-run" || run["schedule"] != "0,15,30,45 * * * *" || run["user"] != nil || strings.Contains(fmt.Sprint(run["schedule"]), "/") {
|
||||
t.Errorf("the scheduled run: %v", run)
|
||||
}
|
||||
if args, _ := run["run"].([]any); len(args) != 2 || args[1] != "run" {
|
||||
t.Errorf("the run's command: %v", run["run"])
|
||||
}
|
||||
if RunTimer != "zfs-mesh-run.timer" || RunService != "zfs-mesh-run.service" {
|
||||
t.Errorf("the tools read %s and %s", RunTimer, RunService)
|
||||
}
|
||||
for _, id := range []string{"distribution-weekly", "distribution-monthly", "distribution-trim-weekly", "distribution-trim-monthly"} {
|
||||
if r := byID[id]; r["state"] != "stopped" || r["boot"] != "disabled" || order["run"] > order[id] {
|
||||
t.Errorf("%s: %v (the run is at %d, it at %d)", id, r, order["run"], order[id])
|
||||
}
|
||||
}
|
||||
for c, unit := range map[string]string{"distribution-weekly": "zfs-scrub-weekly@${config:pool}.timer", "distribution-monthly": "zfs-scrub-monthly@${config:pool}.timer",
|
||||
"distribution-trim-weekly": "zfs-trim-weekly@${config:pool}.timer", "distribution-trim-monthly": "zfs-trim-monthly@${config:pool}.timer"} {
|
||||
want := ""
|
||||
if strings.Contains(c, "trim") {
|
||||
want = "trim-schedule=none,trim-schedule=weekly,trim-schedule=monthly"
|
||||
}
|
||||
if byID[c]["unit"] != unit || conditions(byID[c]) != want {
|
||||
t.Errorf("%s: %v", c, byID[c])
|
||||
}
|
||||
}
|
||||
units := map[string][3]string{"import-cache": {"zfs-import-cache.service", "running", "enabled"}, "mount": {"zfs-mount.service", "running", "enabled"},
|
||||
"target": {"zfs.target", "running", "enabled"}, "import-scan": {"zfs-import-scan.service", "", ""}, "share": {"zfs-share.service", "", ""},
|
||||
"volume-wait": {"zfs-volume-wait.service", "", ""}, "import-target": {"zfs-import.target", "", ""}}
|
||||
for id, u := range units {
|
||||
r := byID[id]
|
||||
got := [3]string{fmt.Sprint(r["unit"]), fmt.Sprint(orNone(r["state"])), fmt.Sprint(orNone(r["boot"]))}
|
||||
if r == nil || got != u || conditions(r) != "" {
|
||||
t.Errorf("%s: %v, want %v", id, got, u)
|
||||
}
|
||||
}
|
||||
on, off := byID["zed-on"], byID["zed-off"]
|
||||
if on["unit"] != "zfs-zed.service" || on["state"] != "running" || on["boot"] != "enabled" || conditions(on) != "zed=on" ||
|
||||
off["unit"] != "zfs-zed.service" || off["state"] != "stopped" || off["boot"] != "disabled" || conditions(off) != "zed=off" {
|
||||
t.Errorf("zed: %v %v", on, off)
|
||||
}
|
||||
if got := fmt.Sprint(on["restart-on"]); !strings.Contains(got, "zed-rc") || !strings.Contains(got, "zed-drop-in") || !strings.Contains(got, "zed-handler-all-syslog") {
|
||||
t.Errorf("zed restarts on %v", got)
|
||||
}
|
||||
a := byID["apply"]
|
||||
if a["type"] != "process" || a["name"] != "zfs-config-apply" || a["run-once"] != true || fmt.Sprint(a["restart-on"]) != "[config]" || a["schedule"] != nil || a["user"] != nil {
|
||||
t.Errorf("the apply is run-once and starts on the config file's change: %v", a)
|
||||
}
|
||||
if order["config"] > order["apply"] || order["utils"] > order["config"] || order["options"] > order["apply"] || order["apply"] > order["run"] {
|
||||
t.Errorf("the order: packages, config file and options before the apply, the apply before the run: %v", order)
|
||||
}
|
||||
for _, r := range byID {
|
||||
if p, _ := r["path"].(string); strings.HasPrefix(p, "/etc/systemd/system/") && !strings.HasPrefix(p, "/etc/systemd/system/zfs-zed.service.d") {
|
||||
t.Errorf("the module writes a unit file of its own: %s", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func orNone(v any) any {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ADR 0240: zfs_health on the watch, which runs as the operator account; the follower judged by its unit.
|
||||
// The scheduled run is not long-running and declares none.
|
||||
func TestTheHealthChecksAndWhoRunsWhat(t *testing.T) {
|
||||
_, byID, _ := manifestOf(t)
|
||||
w := byID["watch"]
|
||||
if h, _ := w["health"].(map[string]any); h["kind"] != "tool" || h["tool"] != "zfs_health" || w["user"] != "${machine:account}" {
|
||||
t.Errorf("the watch runs as the operator account and carries zfs_health: %v", w)
|
||||
@@ -101,119 +289,13 @@ func TestTheHealthChecksAndWhoRunsWhat(t *testing.T) {
|
||||
if h, _ := e["health"].(map[string]any); h["kind"] != "unit" || e["user"] != nil {
|
||||
t.Errorf("the follower runs as root, judged by its unit: %v", e)
|
||||
}
|
||||
if d := byID["state-dir"]; d["path"] != "/var/lib/zfs-tools" || d["owner"] != "${machine:account}" ||
|
||||
filepath.Dir(ErrorsFile) != d["path"] {
|
||||
if d := byID["state-dir"]; d["path"] != "/var/lib/zfs-tools" || d["owner"] != "${machine:account}" || filepath.Dir(ErrorsFile) != d["path"] {
|
||||
t.Errorf("the state the account keeps: %v", d)
|
||||
}
|
||||
// The scheduled run's state is root's alone, apart from the account's (third review).
|
||||
if d := byID["scrub-state-dir"]; d["path"] != filepath.Dir(ServedFile) || d["mode"] != "0700" || d["owner"] != nil {
|
||||
t.Errorf("the state the run keeps: %v", d)
|
||||
}
|
||||
if strings.Contains(raw, `"package"`) {
|
||||
t.Errorf("the module installs no package: ZFS is the machine's, and its kernel module is built against its kernel")
|
||||
}
|
||||
}
|
||||
|
||||
// One schedule per pool, and no setting in any unit's name but the distribution's timers', which the
|
||||
// controller fills (mesh-controller #171) and checks. The module's own schedule is the node-engine's timer
|
||||
// for the scheduled run, a fixed name; the day and time reach only the configuration, which the run checks.
|
||||
// The run comes before the distribution's timers are stopped, so a run that cannot be installed stops the
|
||||
// apply before the timer that scrubs the pool today is touched.
|
||||
func TestExactlyOneScrubScheduleAndNoSettingInAUnitFile(t *testing.T) {
|
||||
m, raw := readManifest(t)
|
||||
byID := map[string]map[string]any{}
|
||||
order := map[string]int{}
|
||||
for i, r := range m.Resources {
|
||||
byID[r["id"].(string)] = r
|
||||
order[r["id"].(string)] = i
|
||||
}
|
||||
run := byID["run"]
|
||||
args, _ := run["run"].([]any)
|
||||
if run["type"] != "process" || run["name"] != "zfs-mesh-run" || run["schedule"] != "0,15,30,45 * * * *" || run["user"] != nil ||
|
||||
len(args) != 2 || args[1] != "run" {
|
||||
t.Errorf("the scheduled run: %v", run)
|
||||
}
|
||||
// The node-engine writes a cron step ("*/15") as a calendar systemd refuses ("*:*/15"), and the timer never
|
||||
// starts (novox/hq issue 1979, on the home server 2026-11-12). A list of minutes is written as a calendar
|
||||
// systemd takes ("*-*-* *:0,15,30,45:00"), so the schedule names its minutes and never a step.
|
||||
if s, _ := run["schedule"].(string); strings.Contains(s, "/") {
|
||||
t.Errorf("the scheduled run's schedule %q uses a step, which the node-engine writes as a calendar systemd refuses", s)
|
||||
}
|
||||
if RunTimer != run["name"].(string)+".timer" || RunService != run["name"].(string)+".service" {
|
||||
t.Errorf("the tools read %s and %s; the node-engine names them after %v", RunTimer, RunService, run["name"])
|
||||
}
|
||||
for _, c := range []string{"weekly", "monthly"} {
|
||||
d := byID["distribution-"+c]
|
||||
if d["unit"] != "zfs-scrub-"+c+"@${config:scrub-pool}.timer" || d["state"] != "stopped" || d["boot"] != "disabled" {
|
||||
t.Errorf("the distribution's %s timer: %v", c, d)
|
||||
}
|
||||
if order["run"] > order["distribution-"+c] {
|
||||
t.Errorf("the distribution's %s timer is stopped before the run is installed", c)
|
||||
}
|
||||
}
|
||||
for _, r := range m.Resources {
|
||||
p, _ := r["path"].(string)
|
||||
if strings.HasPrefix(p, "/etc/systemd/") {
|
||||
t.Errorf("the module writes a unit file of its own: %s", p)
|
||||
}
|
||||
}
|
||||
// The configuration's text is its template's (novox/hq ADR 0349 §2): the manifest itself names none of them.
|
||||
conf := templateText(t, byID["scrub-config"])
|
||||
if strings.Contains(raw, "scrub-day}") || !strings.Contains(conf, "${config:scrub-day}") {
|
||||
t.Errorf("the day reaches something other than the configuration")
|
||||
}
|
||||
if n := strings.Count(conf, "${config:scrub-day}") + strings.Count(conf, "${config:scrub-time}") + strings.Count(conf, "${config:scrub-cadence}"); n != 3 {
|
||||
t.Errorf("the day, time and cadence are written %d times; only into the configuration", n)
|
||||
}
|
||||
var declared struct {
|
||||
Config map[string]map[string]any `json:"config"`
|
||||
}
|
||||
json.Unmarshal([]byte(raw), &declared)
|
||||
if declared.Config["scrub-cadence"]["default"] != "weekly" || declared.Config["scrub-day"]["default"] != "Mon" ||
|
||||
declared.Config["scrub-time"]["default"] != "00:00" {
|
||||
t.Errorf("defaults: %v", declared.Config)
|
||||
}
|
||||
if p := declared.Config["scrub-pool"]; p["required"] != true || p["default"] != nil {
|
||||
t.Errorf("the pool is the operator's value: required, with no default (ADR 0112, ADR 0348 §4): %v", p)
|
||||
}
|
||||
}
|
||||
|
||||
// The home server, composed as the controller fills its settings (pool storage, cadence monthly, the
|
||||
// operator's choice): the units the node-engine is sent and the configuration the run reads.
|
||||
func TestTheHomeServersComposedUnitsAndConfiguration(t *testing.T) {
|
||||
m, _ := readManifest(t)
|
||||
fill := strings.NewReplacer("${config:scrub-pool}", "storage", "${config:scrub-cadence}", "monthly",
|
||||
"${config:scrub-day}", "Mon", "${config:scrub-time}", "00:00")
|
||||
var units []string
|
||||
var conf string
|
||||
for _, r := range m.Resources {
|
||||
switch {
|
||||
case r["type"] == "service":
|
||||
units = append(units, fill.Replace(r["unit"].(string))+" "+r["state"].(string)+"/"+r["boot"].(string))
|
||||
case r["type"] == "process" && r["schedule"] != nil:
|
||||
units = append(units, r["name"].(string)+".timer running/enabled (the node-engine's, every quarter of an hour)")
|
||||
case r["id"] == "scrub-config":
|
||||
conf = fill.Replace(templateText(t, r))
|
||||
}
|
||||
}
|
||||
want := []string{"zfs-mesh-run.timer running/enabled (the node-engine's, every quarter of an hour)",
|
||||
"zfs-scrub-weekly@storage.timer stopped/disabled", "zfs-scrub-monthly@storage.timer stopped/disabled"}
|
||||
if strings.Join(units, "\n") != strings.Join(want, "\n") {
|
||||
t.Errorf("units:\n%s", strings.Join(units, "\n"))
|
||||
}
|
||||
for _, u := range units {
|
||||
t.Log(u)
|
||||
}
|
||||
_ = conf
|
||||
cfg, refused := ParseConfig([]byte(scrubConf("storage", "monthly", "Mon", "00:00")), 16000)
|
||||
s := ScheduleOf(cfg)
|
||||
if len(refused) != 0 || s == nil || s.Pool != "storage" || s.Cadence != "monthly" {
|
||||
t.Fatalf("configuration: %v", refused)
|
||||
}
|
||||
t.Logf("next scrub of storage after 2026-10-09 12:00: %s", s.NextSlot(time.Date(2026, 10, 9, 12, 0, 0, 0, time.Local)).Format("2006-01-02 15:04"))
|
||||
if next := s.NextSlot(time.Date(2026, 10, 9, 12, 0, 0, 0, time.Local)); !next.Equal(time.Date(2026, 11, 2, 0, 0, 0, 0, time.Local)) {
|
||||
t.Errorf("next monthly slot %s, want the first Monday of November", next)
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing but reading, a snapshot and a scrub's start, pause or stop. Every command the bundle runs goes
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -62,3 +63,34 @@ func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
|
||||
t.Errorf("the build packs files/ into %d archives, not one", packs)
|
||||
}
|
||||
}
|
||||
|
||||
// §9, §12, §14 (A-15), §31: the three templates. The config file is the module's values as one JSON object; the options file holds
|
||||
// config-lines for the ARC (in bytes) and the options; ZED's rc holds none of the package's notification or mail lines, and the
|
||||
// scrub-after-resilver variable only its handler reads.
|
||||
func TestTheConfigOptionsAndZedTemplates(t *testing.T) {
|
||||
_, byID, _ := manifestOf(t)
|
||||
if got := strings.TrimSpace(templateText(t, byID["config"])); got != "${config-json}" {
|
||||
t.Errorf("the config file: %q", got)
|
||||
}
|
||||
opts := templateText(t, byID["options"])
|
||||
for _, want := range []string{"${config-lines:arc-max:options zfs zfs_arc_max=:mib-bytes}", "${config-lines:arc-min:options zfs zfs_arc_min=:mib-bytes}",
|
||||
"${config-lines:module-options:options zfs }"} {
|
||||
if !strings.Contains(opts, want) {
|
||||
t.Errorf("the options file lacks %s", want)
|
||||
}
|
||||
}
|
||||
rc := templateText(t, byID["zed-rc"])
|
||||
for _, bad := range []string{"ZED_NOTIFY", "ZED_EMAIL", "PUSHBULLET", "SLACK", "PUSHOVER", "NTFY", "GOTIFY"} {
|
||||
for _, l := range strings.Split(rc, "\n") {
|
||||
if strings.Contains(l, bad) && !strings.HasPrefix(l, "#") {
|
||||
t.Errorf("zed.rc holds %s: %s", bad, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !strings.Contains(rc, "\nZED_SCRUB_AFTER_RESILVER=1\n") {
|
||||
t.Errorf("zed.rc does not enable the scrub after a resilver for its handler")
|
||||
}
|
||||
if drop := templateText(t, byID["zed-drop-in"]); !strings.Contains(drop, "ExecStart=/usr/bin/zed -F -d /etc/zfs/mesh-zed.d") {
|
||||
t.Errorf("the drop-in does not run ZED from the module's folder: %s", drop)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user