Merge pull request 'A record a module once left in the node's composition goes with that module, so the node's own apply is no longer refused (issue 774)' (#186) from fix/folded-record-goes-with-its-module into main
This commit was merged in pull request #186.
This commit is contained in:
+48
-1
@@ -347,8 +347,19 @@ func (sc *Scope) placedOwner(a store.Applied) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// owns reports whether a record belongs to the scope's module.
|
// owns reports whether a record belongs to the scope's module.
|
||||||
|
//
|
||||||
|
// In a module's own apply, a record the node's own composition made for the module while it was folded (recorded as the
|
||||||
|
// node's, its id naming the module) is the module's too: it is the only apply that may remove it once neither
|
||||||
|
// composition declares it (issue 774). One the node's own still declares stays, as another's held resource does.
|
||||||
func (sc *Scope) owns(a store.Applied) bool {
|
func (sc *Scope) owns(a store.Applied) bool {
|
||||||
return store.OwnerOf(a, declaration.ModuleGuess) == sc.Module
|
if store.OwnerOf(a, declaration.ModuleGuess) == sc.Module {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if sc.Module == store.NodeModule || a.Module != store.NodeModule {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m, ok := declaration.ModuleGuess(a.ID)
|
||||||
|
return ok && m == sc.Module
|
||||||
}
|
}
|
||||||
|
|
||||||
// isNode is whether the scope is the node's own composition, the one that carries identities, groups, libraries and
|
// isNode is whether the scope is the node's own composition, the one that carries identities, groups, libraries and
|
||||||
@@ -473,6 +484,29 @@ func applyScoped(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **A resource the node's own carried for a module that now has a composition of its own is that module's to remove**
|
||||||
|
// (issue 774, 2026-10-30 on the bus node): recorded as the node's while the module was folded, it is no longer in the
|
||||||
|
// node's own composition and no longer in the module's own either (restic's measure, replaced by awake). The node's own
|
||||||
|
// apply leaves it, record and all, and says so; the module's own apply sees it as its own (Scope.owns) and removes it.
|
||||||
|
// Refused instead, the node's own applied nothing at all, every two minutes, for as long as the record stayed.
|
||||||
|
takenByItsModule := map[string]string{}
|
||||||
|
if scope != nil && scope.Module == store.NodeModule {
|
||||||
|
composes := map[string]bool{}
|
||||||
|
for _, r := range scope.Others {
|
||||||
|
if m := r.DeclaredModule(); m != "" && m != store.NodeModule {
|
||||||
|
composes[m] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, orphan := range known.Orphans(declared, origin) {
|
||||||
|
if orphan.Module != store.NodeModule {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m, ok := declaration.ModuleGuess(orphan.ID); ok && composes[m] {
|
||||||
|
takenByItsModule[orphan.ID] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **A module a want still places here is never removed by another's apply** (issue 774): its resources go only with
|
// **A module a want still places here is never removed by another's apply** (issue 774): its resources go only with
|
||||||
// its own apply or its want's removal. One that would is refused whole, before anything is changed, naming them: a
|
// its own apply or its want's removal. One that would is refused whole, before anything is changed, naming them: a
|
||||||
// composition that leaves them out lost what runs, and removing what runs is the worst a node can do with that.
|
// composition that leaves them out lost what runs, and removing what runs is the worst a node can do with that.
|
||||||
@@ -485,6 +519,9 @@ func applyScoped(
|
|||||||
if _, kept := heldByOthers[orphan.ID]; kept {
|
if _, kept := heldByOthers[orphan.ID]; kept {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if _, kept := takenByItsModule[orphan.ID]; kept {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if m := scope.placedOwner(orphan); m != "" {
|
if m := scope.placedOwner(orphan); m != "" {
|
||||||
refused = append(refused, orphan.ID+" of "+m)
|
refused = append(refused, orphan.ID+" of "+m)
|
||||||
}
|
}
|
||||||
@@ -513,6 +550,16 @@ func applyScoped(
|
|||||||
log(fmt.Sprintf(" kept %s (%s): %s", orphan.ID, orphan.Target, detail))
|
log(fmt.Sprintf(" kept %s (%s): %s", orphan.ID, orphan.Target, detail))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if module, taken := takenByItsModule[orphan.ID]; taken {
|
||||||
|
detail := "no longer declared here; " + module + " has a composition of its own here, whose apply removes it, " +
|
||||||
|
"so it was left as it is"
|
||||||
|
report.Outcomes = append(report.Outcomes, Outcome{
|
||||||
|
ID: orphan.ID, Type: orphan.Type, Target: orphan.Target,
|
||||||
|
Action: "kept", Detail: detail,
|
||||||
|
})
|
||||||
|
log(fmt.Sprintf(" kept %s (%s): %s", orphan.ID, orphan.Target, detail))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if by, held := heldFiles[fileKey(orphan)]; held && by != orphan.ID {
|
if by, held := heldFiles[fileKey(orphan)]; held && by != orphan.ID {
|
||||||
if scope != nil && !declared[by] {
|
if scope != nil && !declared[by] {
|
||||||
// Another module declares the path: it is that module's, so this record is let go and the file is
|
// Another module declares the path: it is that module's, so this record is let go and the file is
|
||||||
|
|||||||
@@ -128,6 +128,9 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
|||||||
name string
|
name string
|
||||||
id string // the resource the node's own composition carried, and no longer does
|
id string // the resource the node's own composition carried, and no longer does
|
||||||
moved bool // its module's own composition now declares it (Others)
|
moved bool // its module's own composition now declares it (Others)
|
||||||
|
// dropped: its module has a composition of its own here that no longer declares it (Others holds another of its
|
||||||
|
// resources); the node's own apply leaves it, and the module's own apply removes it.
|
||||||
|
dropped bool
|
||||||
placed bool // a want still places its module here
|
placed bool // a want still places its module here
|
||||||
refused bool // the node's own apply is refused whole
|
refused bool // the node's own apply is refused whole
|
||||||
stays bool // the resource is still on the machine and recorded
|
stays bool // the resource is still on the machine and recorded
|
||||||
@@ -141,6 +144,11 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
|||||||
// it; in the node's own apply only a module a want removed loses its resources.
|
// it; in the node's own apply only a module a want removed loses its resources.
|
||||||
{name: "a folded module with no want", id: "nats.server-conf", wants: []string{}, refused: true, stays: true},
|
{name: "a folded module with no want", id: "nats.server-conf", wants: []string{}, refused: true, stays: true},
|
||||||
{name: "a folded module a want removed", id: "nats.server-conf", wants: []string{"nats", "old"}},
|
{name: "a folded module a want removed", id: "nats.server-conf", wants: []string{"nats", "old"}},
|
||||||
|
// 2026-10-30 19:42 on the bus node: restic's manifest replaced its process measure by awake. Its record was the
|
||||||
|
// node's, carried when restic was folded, so restic's own apply never saw it and the node's own was refused
|
||||||
|
// whole every two minutes, applying nothing of the node's own.
|
||||||
|
{name: "its module's own composition dropped it", id: "restic.measure", dropped: true, placed: true,
|
||||||
|
wants: []string{"old"}, stays: true},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
@@ -159,6 +167,11 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
|||||||
moved := declare(t, `{"id":"`+tc.id+`","type":"file","path":"`+fact+`","content":"mesh\n","module":"`+module+`"}`)
|
moved := declare(t, `{"id":"`+tc.id+`","type":"file","path":"`+fact+`","content":"mesh\n","module":"`+module+`"}`)
|
||||||
scope.Others = moved.Resources
|
scope.Others = moved.Resources
|
||||||
}
|
}
|
||||||
|
if tc.dropped {
|
||||||
|
later := declare(t, `{"id":"`+module+`.awake","type":"file","path":"`+filepath.Join(dir, "awake")+
|
||||||
|
`","content":"a\n","module":"`+module+`"}`)
|
||||||
|
scope.Others = later.Resources
|
||||||
|
}
|
||||||
if tc.placed {
|
if tc.placed {
|
||||||
scope.Placed = map[string]bool{module: true}
|
scope.Placed = map[string]bool{module: true}
|
||||||
}
|
}
|
||||||
@@ -188,6 +201,22 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
|||||||
if exists(gone) == !tc.refused {
|
if exists(gone) == !tc.refused {
|
||||||
t.Fatalf("a resource no composition holds any more exists %v, with the apply refused %v", exists(gone), tc.refused)
|
t.Fatalf("a resource no composition holds any more exists %v, with the apply refused %v", exists(gone), tc.refused)
|
||||||
}
|
}
|
||||||
|
if tc.dropped {
|
||||||
|
if o := outcomeOf(report, tc.id); o.Action != "kept" {
|
||||||
|
t.Fatalf("the node's own apply did not say it left it: %+v", o)
|
||||||
|
}
|
||||||
|
later := declare(t, `{"id":"`+module+`.awake","type":"file","path":"`+filepath.Join(dir, "awake")+
|
||||||
|
`","content":"a\n","module":"`+module+`"}`)
|
||||||
|
_, last, err := ApplyModule(context.Background(), archHost(t), later, Scope{Module: module}, after,
|
||||||
|
store.OriginDeclared, noServices, nil, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, still := last.Find(tc.id); still || exists(fact) {
|
||||||
|
t.Fatalf("the module's own apply left what it no longer declares: recorded %v, on the machine %v",
|
||||||
|
still, exists(fact))
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user