package registry import ( "io" "os" "path/filepath" "reflect" "testing" ) func TestRemoteReadOnlyWALRegistry(t *testing.T) { if os.Geteuid() == 0 { t.Skip("read-only permission fixture requires a nonroot uid") } t.Setenv("CARLOS_HOST_CLAIM", "true") owner := ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 3, ClaimOwner: "owner", ClaimRowID: "route"} dir := t.TempDir() path := filepath.Join(dir, "registry.db") writer, err := Open(path) if err != nil { t.Fatal(err) } defer writer.Close() if err := writer.Upsert(Route{Host: "readonly", Kind: "instance", Account: "acct", App: "app", ClaimOwnership: owner}); err != nil { t.Fatal(err) } timer, err := writer.UpsertTimerRegistration(Timer{Host: "readonly", Name: "backup", Kind: "cron", Spec: "* * * * *"}, "") if err != nil { t.Fatal(err) } if err := writer.ReplaceTimers("orphan", []Timer{{Host: "orphan", Name: "paused", Kind: "cron", NextAt: 0}}); err != nil { t.Fatal(err) } for _, suffix := range []string{"", "-wal", "-shm"} { if err := os.Chmod(path+suffix, 0444); err != nil { t.Fatal(err) } } if err := os.Chmod(dir, 0555); err != nil { t.Fatal(err) } defer func() { os.Chmod(dir, 0755) for _, suffix := range []string{"", "-wal", "-shm"} { os.Chmod(path+suffix, 0644) } }() requestR, requestW := io.Pipe() responseR, responseW := io.Pipe() done := make(chan error, 1) go func() { err := ServeRemoteReadOnly(path, requestR, responseW) requestR.Close() responseW.Close() done <- err }() reader, err := OpenRemote(path, requestW, responseR, func() error { return <-done }) if err != nil { t.Fatal(err) } defer reader.Close() hosts, err := reader.ArtifactHosts() if err != nil || !reflect.DeepEqual(hosts, []string{"orphan", "readonly"}) { t.Fatalf("readonly artifact hosts: %v %v", hosts, err) } row, err := reader.Get("readonly") if err != nil || row == nil || row.Account != "acct" || row.App != "app" { t.Fatalf("readonly WAL read: %+v %v", row, err) } timers, err := reader.TimersFor("readonly") if err != nil || len(timers) != 1 || timers[0].Registration != timer.Registration || timers[0].ClaimOwnership != timer.ClaimOwnership || timers[0].ClaimOwner != owner.ClaimOwner || timers[0].ClaimStamp != owner.ClaimStamp { t.Fatalf("readonly timer identity: %+v %v", timers, err) } if err := reader.ReplaceTimers("readonly", nil); err == nil { t.Fatal("read-only worker replaced timers") } if err := reader.DeleteTimer("readonly", "backup"); err == nil { t.Fatal("read-only worker deleted timer") } after, err := reader.TimersFor("readonly") if err != nil || len(after) != 1 || after[0] != timers[0] { t.Fatalf("refused writes changed timer: %+v %v", after, err) } if err := reader.Upsert(Route{Host: "forbidden", Kind: "instance"}); err == nil { t.Fatal("read-only worker accepted mutation") } if _, err := reader.Get("readonly"); err != nil { t.Fatalf("read after refused mutation: %v", err) } if writable, err := Open(path); err == nil { writable.Close() t.Fatal("normal registry open accepted read-only state") } }