Give every module an identity and keep shared folders through ACL entries (hq ADR 0325, issue 567) #93
@@ -164,6 +164,9 @@ func judgeAccess(a judgedAccess) verdict {
|
||||
What: apply.DiskWords(a.name)}}}
|
||||
case apply.VerdictRoot:
|
||||
return verdict{state: link.StateUnhealthy, reason: apply.RootWords(v.Why)}
|
||||
case apply.VerdictUnknown:
|
||||
// Not a fault and not the operator's: said as not known, and judged again on the next look.
|
||||
return verdict{state: link.StateUnknown, reason: v.Why + "; judged again on the next look"}
|
||||
case apply.VerdictBroken:
|
||||
// Its words carry no path (AccessVerdict says them so): the reason reaches the operator's channel.
|
||||
return verdict{state: link.StateUnhealthy, reason: v.Why}
|
||||
|
||||
+17
-5
@@ -189,6 +189,8 @@ const (
|
||||
folderWaitDisk = "wait-disk"
|
||||
// folderRoot is root's data on its placed filesystem: never kept.
|
||||
folderRoot = "root"
|
||||
// folderUnknown is a folder whose filesystem could not be named: left as it is until it is.
|
||||
folderUnknown = "unknown"
|
||||
)
|
||||
|
||||
// folderVerdict is the judgement of one folder, with what it read.
|
||||
@@ -249,6 +251,13 @@ func assessFolder(ctx context.Context, path string, rec store.KeptFolder, taken
|
||||
// Another filesystem at the same mount point (another pool, another disk) is a move too. A record from an earlier
|
||||
// build names its filesystem another way, and is not compared: the next keep names it this way.
|
||||
here := PlacedID(pl.Mount)
|
||||
// **A filesystem that cannot be named now is unknown, never a move** (a name read that did not answer, a
|
||||
// filesystem with no UUID, id or source): the folder holds what it is — nothing walked, nothing newly kept,
|
||||
// nothing reset — and is judged again when it is named. One never kept is so never kept: it fails closed.
|
||||
if here == "" {
|
||||
return folderVerdict{kind: folderUnknown, mount: pl.Mount, unnamed: true,
|
||||
why: "its filesystem could not be named this time (no UUID, filesystem id or source answered)"}
|
||||
}
|
||||
moved := pl.Moved || (rec.Fs != "" && rec.Mount == pl.Mount && rec.Placed != here)
|
||||
facts, err := readFolder(path)
|
||||
if err != nil {
|
||||
@@ -257,11 +266,6 @@ func assessFolder(ctx context.Context, path string, rec store.KeptFolder, taken
|
||||
if why := rootOwned(facts.UID, facts.GID); why != "" {
|
||||
return folderVerdict{kind: folderRoot, mount: pl.Mount, why: "it is " + why}
|
||||
}
|
||||
// A filesystem that cannot be named is never kept: what is there could be anyone's (§8).
|
||||
if here == "" {
|
||||
return folderVerdict{kind: folderWaitKeep, mount: pl.Mount, moved: moved, unnamed: true,
|
||||
why: "its filesystem cannot be named (no UUID, no filesystem id, no source)"}
|
||||
}
|
||||
// The controller's word that the folder was taken holds for the filesystem the warrant was carried out on: a
|
||||
// folder moved to another is not taken, and asks again (§8).
|
||||
takenHere := taken && (rec.TakenOn == here || (rec.TakenOn == "" && !moved))
|
||||
@@ -520,6 +524,10 @@ func applyAccessFolder(ctx context.Context, r *declaration.Access, in inputs) (O
|
||||
note += "; its grant is no longer acted on: the mesh puts no account in a group for data (novox/hq ADR 0325 §5)"
|
||||
}
|
||||
switch v.kind {
|
||||
case folderUnknown:
|
||||
out.Action = "waiting"
|
||||
out.Detail = v.why + ": the folder is left exactly as it is, and judged again on the next apply" + note
|
||||
return out, nil
|
||||
case folderWaitDisk:
|
||||
out.Action = "waiting"
|
||||
out.Detail = "the disk that holds it is not there: nothing is made, and nothing mounts it until it is " +
|
||||
@@ -792,6 +800,8 @@ const (
|
||||
VerdictWaitDisk = "wait-disk"
|
||||
VerdictRoot = "root"
|
||||
VerdictBroken = "broken"
|
||||
// VerdictUnknown is a folder whose filesystem could not be named this look: neither kept nor faulted.
|
||||
VerdictUnknown = "unknown"
|
||||
// VerdictNobody is a folder no identity uses: nothing is kept, and only a missing disk is said.
|
||||
VerdictNobody = "nobody"
|
||||
)
|
||||
@@ -813,6 +823,8 @@ func AccessVerdict(ctx context.Context, path string, taken, used bool) AccessJud
|
||||
return AccessJudgement{Kind: VerdictBroken, Why: pathless(v.err)}
|
||||
case v.kind == folderWaitDisk:
|
||||
return AccessJudgement{Kind: VerdictWaitDisk}
|
||||
case v.kind == folderUnknown:
|
||||
return AccessJudgement{Kind: VerdictUnknown, Why: v.why}
|
||||
case !used:
|
||||
return AccessJudgement{Kind: VerdictNobody}
|
||||
case v.kind == folderRoot:
|
||||
|
||||
@@ -179,6 +179,8 @@ func reconcileOne(ctx context.Context, path string, f store.KeptFolder) []string
|
||||
why = pathless(v.err)
|
||||
case v.kind == folderRoot:
|
||||
why = RootWords(v.why)
|
||||
case v.kind == folderUnknown:
|
||||
why = v.why
|
||||
case v.moved || v.kind == folderWaitKeep:
|
||||
why = "what is at it now is not what the mesh kept there"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -1370,7 +1371,7 @@ func TestNamingAHungFilesystemIsOneReadPerMount(t *testing.T) {
|
||||
<-release
|
||||
return ""
|
||||
}
|
||||
defer func() { close(release); MountUUID, statBound = was, wasBound }()
|
||||
defer func() { close(release); flightsDone(t); MountUUID, statBound = was, wasBound }()
|
||||
for i := 0; i < 5; i++ {
|
||||
if id := PlacedID("/a/dead/mount"); id != "" {
|
||||
t.Fatalf("a filesystem that does not answer is named %s", id)
|
||||
@@ -1382,3 +1383,66 @@ func TestNamingAHungFilesystemIsOneReadPerMount(t *testing.T) {
|
||||
t.Fatalf("%d reads of one mount were started while the first hung", started)
|
||||
}
|
||||
}
|
||||
|
||||
// Review (sixth): a name read that does not answer is unknown, never a move — a folder kept while empty and filled
|
||||
// since is not asked about again, a warranted one is not walked again, and nothing of either record is reset.
|
||||
func TestAFilesystemNotNamedThisTimeIsNeverAMove(t *testing.T) {
|
||||
uuid, fsid, source := "6f1c0a4e-1b2d-4c3e-8f00-aa11bb22cc33", "0000080100000000", "/dev/sdb1"
|
||||
_, lib, access := poolNamed(t, &uuid, &fsid, &source)
|
||||
// The name's reads hang past their bound while hanging is set; installed once, before anything reads it.
|
||||
var hanging atomic.Bool
|
||||
release := make(chan struct{})
|
||||
named := MountUUID
|
||||
MountUUID = func(p string) string {
|
||||
if hanging.Load() {
|
||||
<-release
|
||||
}
|
||||
return named(p)
|
||||
}
|
||||
takeIt(t, lib, access)
|
||||
taken := strings.Replace(access, `"taken":false`, `"taken":true`, 1)
|
||||
before, _ := keptRecord(lib)
|
||||
|
||||
wasBound := statBound
|
||||
statBound = 20 * time.Millisecond
|
||||
hanging.Store(true)
|
||||
report, _, err := applyAccesses(t, store.State{}, taken)
|
||||
hanging.Store(false)
|
||||
close(release)
|
||||
flightsDone(t)
|
||||
statBound = wasBound
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o := outcomeOf(report, "radarr.media"); o.Action != "waiting" || !strings.Contains(o.Detail, "could not be named") {
|
||||
t.Fatalf("a folder whose filesystem was not named this time is not said so: %+v", o)
|
||||
}
|
||||
after, _ := keptRecord(lib)
|
||||
if !after.Kept || after.Placed != before.Placed || after.TakenOn != before.TakenOn || len(after.Left) != 0 ||
|
||||
!sameUsers(after.Users, before.Users) {
|
||||
t.Fatalf("the record was reset by a name not read:\nbefore %+v\nafter %+v", before, after)
|
||||
}
|
||||
|
||||
// Named again: kept as it was, with nothing asked and nothing walked.
|
||||
waitFor(t, "the hung read returned", func() bool { return PlacedID(filepath.Dir(lib)) == before.Placed })
|
||||
report, _, err = applyAccesses(t, store.State{}, taken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o := outcomeOf(report, "radarr.media"); o.Action == "waiting" {
|
||||
t.Fatalf("named again, the folder asks again: %+v", o)
|
||||
}
|
||||
if r, _ := keptRecord(lib); r.Act == nil || r.Act.Act != "take" {
|
||||
t.Errorf("the folder was walked again: %+v", r.Act)
|
||||
}
|
||||
}
|
||||
|
||||
// flightsDone waits until no read of a hung test is in flight: it goes on reading the stand-ins after its release.
|
||||
func flightsDone(t *testing.T) {
|
||||
t.Helper()
|
||||
waitFor(t, "the hung reads returned", func() bool {
|
||||
inFlight.Lock()
|
||||
defer inFlight.Unlock()
|
||||
return len(inFlight.reads) == 0
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user