Merge pull request 'A node holds the one module it cannot compose and applies the rest, as its shadow already answers (group fix/1126-apply-culprit, issue 1126)' (#118) from fix/1126-apply-culprit into main
This commit was merged in pull request #118.
This commit is contained in:
+52
-2
@@ -55,11 +55,19 @@ func CompositionsShadow(in Input, node string, fps map[string]string) (out map[s
|
||||
return compositions(in, node, fps, Foreseeing, false)
|
||||
}
|
||||
|
||||
// CompositionsIn is the node's compositions in mode with what they stand in for answered (stood, sorted, the bus's user
|
||||
// list left out) rather than refused: CompositionsShadow in Foreseeing, and in Reading what CompositionsApplied refuses
|
||||
// before it is refused, so a caller that applies refuses each stand-in to the module it is for alone (issue 1126).
|
||||
func CompositionsIn(in Input, node string, fps map[string]string, mode Mode) (out map[string]delivery.Composed, skipped, stood []string, err error) {
|
||||
return compositions(in, node, fps, mode, false)
|
||||
}
|
||||
|
||||
func compositions(in Input, node string, fps map[string]string, mode Mode, applied bool) (out map[string]delivery.Composed, skipped, stood []string, err error) {
|
||||
reading := in
|
||||
reading.Nodes = append([]Node(nil), in.Nodes...)
|
||||
found := false
|
||||
inOwn := map[string]bool{}
|
||||
held := map[string]bool{}
|
||||
for i := range reading.Nodes {
|
||||
if reading.Nodes[i].Name == node {
|
||||
reading.Nodes[i].ReadsModules = true
|
||||
@@ -67,6 +75,9 @@ func compositions(in Input, node string, fps map[string]string, mode Mode, appli
|
||||
for m, ok := range reading.Nodes[i].InOwn {
|
||||
inOwn[m] = ok
|
||||
}
|
||||
for m, ok := range reading.Nodes[i].Held {
|
||||
held[m] = ok
|
||||
}
|
||||
}
|
||||
}
|
||||
// The private network's module is built by nobody: no fingerprint of the new form ever names it.
|
||||
@@ -88,15 +99,26 @@ func compositions(in Input, node string, fps map[string]string, mode Mode, appli
|
||||
continue
|
||||
}
|
||||
runsOwn = n.RunsOwn
|
||||
candidates := map[string]bool{}
|
||||
for m := range inOwn {
|
||||
if !folds(m) {
|
||||
candidates[m] = true
|
||||
}
|
||||
for m := range held {
|
||||
candidates[m] = true
|
||||
}
|
||||
for m := range candidates {
|
||||
// A held module is carried as it runs wherever the node's own runs it, folded or leaving the fold for a
|
||||
// composition of its own (its want new, its names still unset): its stub is never what the node's own applies,
|
||||
// and what the node's own applies never drops what runs of it (issue 1126, review of the apply; issue 1945).
|
||||
heldHere := held[m] && RunsAnyOf(runsOwn, m)
|
||||
if !folds(m) && !heldHere {
|
||||
continue
|
||||
}
|
||||
man, ok := n.Kept[m]
|
||||
if !ok {
|
||||
man, ok = reading.Catalogue[m]
|
||||
}
|
||||
if !ok || !NamesBuild(man) {
|
||||
if !ok || (!NamesBuild(man) && !held[m]) {
|
||||
continue
|
||||
}
|
||||
carried[m] = true
|
||||
@@ -231,6 +253,10 @@ func compositions(in Input, node string, fps map[string]string, mode Mode, appli
|
||||
out[own] = c
|
||||
}
|
||||
for key, c := range out {
|
||||
if held[c.Module] {
|
||||
delete(out, key) // its stub's: the node keeps what it last applied of it (Node.Held)
|
||||
continue
|
||||
}
|
||||
if m, ok := in.Catalogue[c.Module]; ok && c.Module != "" {
|
||||
c.Data = len(m.DataItems()) > 0
|
||||
out[key] = c
|
||||
@@ -243,6 +269,30 @@ func compositions(in Input, node string, fps map[string]string, mode Mode, appli
|
||||
return out, skipped, stood, nil
|
||||
}
|
||||
|
||||
// RunsAnyOf says runs (a composition as it runs) holds a resource of module, by its module or the module its id names.
|
||||
func RunsAnyOf(runs *delivery.Composed, module string) bool {
|
||||
if runs == nil {
|
||||
return false
|
||||
}
|
||||
for _, raw := range runs.Resources {
|
||||
var head struct {
|
||||
ID string `json:"id"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
if json.Unmarshal(raw, &head) != nil {
|
||||
continue
|
||||
}
|
||||
owner := head.Module
|
||||
if owner == "" {
|
||||
owner, _ = declaration.ModuleGuess(head.ID)
|
||||
}
|
||||
if owner == module {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NamesBuild says a manifest names what a build makes: its build, or a resource naming an artifact. A node cannot
|
||||
// compose such a module folded into its own composition: the build of the old form it runs is held by the controller
|
||||
// alone (issue 1126).
|
||||
|
||||
+299
-33
@@ -2,6 +2,7 @@ package frombus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -70,21 +72,14 @@ func Answer(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]st
|
||||
return refuse(fmt.Errorf("frombus: the credentials of %s: %w", s.Node, err))
|
||||
}
|
||||
}
|
||||
composed, skipped, stood, err := compose.CompositionsShadow(in, s.Node, fps)
|
||||
for err != nil {
|
||||
module, bare := culprit(in, s.Node, fps, failed, err)
|
||||
if module == "" {
|
||||
// Not one module's: the node's own (a value served elsewhere, the mesh's network), said once as the node's.
|
||||
if len(failed) == 0 {
|
||||
return refuse(err)
|
||||
}
|
||||
failed[delivery.HaveNodeWord] = err.Error()
|
||||
composed, skipped, stood = nil, nil, nil
|
||||
break
|
||||
composed, skipped, stood, err := composeEach(in, s.Node, fps, false, s.Running, failed)
|
||||
if err != nil {
|
||||
// Not one module's: the node's own (a value served elsewhere, the mesh's network), said once as the node's.
|
||||
if len(failed) == 0 {
|
||||
return refuse(err)
|
||||
}
|
||||
failed[module] = err.Error()
|
||||
in = stubbed(in, s.Node, module, bare)
|
||||
composed, skipped, stood, err = compose.CompositionsShadow(in, s.Node, fps)
|
||||
failed[delivery.HaveNodeWord] = err.Error()
|
||||
composed, skipped, stood = nil, nil, nil
|
||||
}
|
||||
// The node's own composition is judged only where the node runs one: one that runs none applies it only once a want
|
||||
// names it (the node agent's own composer hands it on that want alone), so a candidate does not add it.
|
||||
@@ -244,12 +239,259 @@ func standsInFor(x, node string) (string, string) {
|
||||
return node, ""
|
||||
}
|
||||
|
||||
// composeEach is the one rule, for the node that applies and for its answer to a candidate alike, by which a module's
|
||||
// own refusal refuses that module and never the node (issue 1126: seed-8 fixed it in the answer alone, and the
|
||||
// applying path still refused all of novox for de-spiegel's unset route names). The node is composed; a refusal is laid
|
||||
// on the module whose stub (compose.Stub) changes it (culprit), that module is held (compose.Node.Held) and said in
|
||||
// failed with the refusal's words, and the rest is composed again, until it composes or the refusal is no single
|
||||
// module's, which is answered as the node's (err). Applied composes as the node applies (compose.Reading) and holds the
|
||||
// module placed here each stand-in is for; one for no module placed here (the node's own, a value this node holds for a
|
||||
// module elsewhere) refuses the node (compose.ErrStoodIn). Not applied, it composes in compose.Foreseeing and answers
|
||||
// every stand-in for the caller to judge (Answer).
|
||||
//
|
||||
// **A held module is what runs of it** (review of the apply, round 1): composed from what running holds of it
|
||||
// (compose.Carried of its own composition there; the node's own carries it wherever it runs there), so what the others
|
||||
// read of it (the machine ports its containers publish, the routes reaching it) is what runs, and none of its own
|
||||
// composition is in out: the node keeps what it last applied. **What depends on how it is held is held too:** a module
|
||||
// whose composition differs when the held ones are composed as their stubs instead is held with them, and a node's own
|
||||
// composition that differs (but for the held modules' own resources) refuses the node. And since a held module is
|
||||
// composed from its new manifest, which may not be the build's that runs, what reads that manifest is held with it
|
||||
// (dependents): the providers of what it contributes to (the proxy its routes), and, when it listens, the filter.
|
||||
func composeEach(in compose.Input, node string, fps map[string]string, applied bool, running []delivery.Composed, failed map[string]string) (out map[string]delivery.Composed, skipped, stood []string, err error) {
|
||||
mode := compose.Foreseeing
|
||||
if applied {
|
||||
mode = compose.Reading
|
||||
}
|
||||
runs := map[string]*delivery.Composed{}
|
||||
for i := range running {
|
||||
if m := running[i].Module; m != "" {
|
||||
runs[m] = &running[i]
|
||||
}
|
||||
}
|
||||
src := map[string]catalogue.Manifest{} // each held module's manifest before it was held
|
||||
bareOf := map[string]bool{}
|
||||
var hold func(m, why string, bare bool)
|
||||
hold = func(m, why string, bare bool) {
|
||||
if _, done := failed[m]; !done {
|
||||
failed[m] = why // a held module tried bare keeps the words it was first held for
|
||||
}
|
||||
first := false
|
||||
if _, ok := src[m]; !ok {
|
||||
man, ok := manifestOn(in, node, m)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
src[m], first = man, true
|
||||
}
|
||||
bareOf[m] = bareOf[m] || bare
|
||||
in = heldAs(in, node, m, src[m], bareOf[m], runs[m])
|
||||
if !first {
|
||||
return
|
||||
}
|
||||
// What the others read of its manifest (what it contributes, what it listens on) is its new manifest's, not
|
||||
// necessarily the build's that runs: what reads it is held with it (review of the apply, round 2).
|
||||
for _, r := range dependents(in, node, src[m], failed) {
|
||||
hold(r, "it reads what "+m+" contributes or listens on, and "+m+" is held", false)
|
||||
}
|
||||
}
|
||||
for _, m := range sortedKeys(failed) {
|
||||
hold(m, failed[m], false)
|
||||
}
|
||||
for {
|
||||
out, skipped, stood, err = compose.CompositionsIn(in, node, fps, mode)
|
||||
if err != nil {
|
||||
module, bare := culprit(in, node, fps, mode, failed, err)
|
||||
if module == "" {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
hold(module, err.Error(), bare)
|
||||
continue
|
||||
}
|
||||
again := false
|
||||
if applied && len(stood) > 0 {
|
||||
placed := map[string]bool{}
|
||||
for _, m := range assignedOn(in, node) {
|
||||
placed[m] = true
|
||||
}
|
||||
of := map[string][]string{}
|
||||
var nodes []string
|
||||
for _, x := range stood {
|
||||
if n, m := standsInFor(x, node); n == node && placed[m] {
|
||||
of[m] = append(of[m], x)
|
||||
} else {
|
||||
nodes = append(nodes, x)
|
||||
}
|
||||
}
|
||||
for _, m := range sortedKeys(of) {
|
||||
if _, done := failed[m]; !done {
|
||||
hold(m, "stands in for "+strings.Join(of[m], ", "), false)
|
||||
again = true
|
||||
}
|
||||
}
|
||||
if !again && len(nodes) > 0 {
|
||||
return nil, nil, nil, fmt.Errorf("%w on %s: %s", compose.ErrStoodIn, node, strings.Join(nodes, ", "))
|
||||
}
|
||||
}
|
||||
if !again && len(failed) > 0 {
|
||||
dependent, own := dependsOnHeld(in, node, fps, mode, out, src, bareOf, failed)
|
||||
if own {
|
||||
return nil, nil, nil, fmt.Errorf("frombus: %s's own composition depends on how %s, held, is composed, and is not applied",
|
||||
node, strings.Join(sortedKeys(failed), ", "))
|
||||
}
|
||||
for _, m := range dependent {
|
||||
hold(m, "what it composes of "+strings.Join(sortedKeys(failed), ", ")+", held, is not what runs of them", false)
|
||||
again = true
|
||||
}
|
||||
}
|
||||
if !again {
|
||||
break
|
||||
}
|
||||
}
|
||||
skipped = slices.DeleteFunc(skipped, func(m string) bool { _, held := failed[m]; return held })
|
||||
return out, skipped, stood, nil
|
||||
}
|
||||
|
||||
// dependsOnHeld composes in again with each held module as its stub rather than as what runs of it, and answers the
|
||||
// modules, not held, whose composition differs between the two (sorted), and own when the node's own composition does
|
||||
// but for the held modules' own resources. A composition of stubs that fails says nothing more: what runs composed.
|
||||
func dependsOnHeld(in compose.Input, node string, fps map[string]string, mode compose.Mode, asRuns map[string]delivery.Composed,
|
||||
src map[string]catalogue.Manifest, bareOf map[string]bool, failed map[string]string) (dependent []string, own bool) {
|
||||
stubs := in
|
||||
for _, m := range sortedKeys(failed) {
|
||||
if man, ok := src[m]; ok {
|
||||
stubs = heldAs(stubs, node, m, man, bareOf[m], nil)
|
||||
}
|
||||
}
|
||||
asStubs, _, _, err := compose.CompositionsIn(stubs, node, fps, mode)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
isHeld := func(m string) bool { _, ok := failed[m]; return ok }
|
||||
keys := map[string]bool{}
|
||||
for k := range asRuns {
|
||||
keys[k] = true
|
||||
}
|
||||
for k := range asStubs {
|
||||
keys[k] = true
|
||||
}
|
||||
for _, k := range sortedKeys(keys) {
|
||||
a, inA := asRuns[k]
|
||||
b, inB := asStubs[k]
|
||||
m := a.Module
|
||||
if !inA {
|
||||
m = b.Module
|
||||
}
|
||||
if m != "" && isHeld(m) {
|
||||
continue
|
||||
}
|
||||
if m == "" {
|
||||
a, b = withoutHeld(a, isHeld), withoutHeld(b, isHeld)
|
||||
}
|
||||
da, errA := delivery.ComposedDigestOf(a)
|
||||
db, errB := delivery.ComposedDigestOf(b)
|
||||
if inA == inB && errA == nil && errB == nil && da == db {
|
||||
continue
|
||||
}
|
||||
if m == "" {
|
||||
own = true
|
||||
continue
|
||||
}
|
||||
dependent = append(dependent, m)
|
||||
}
|
||||
return dependent, own
|
||||
}
|
||||
|
||||
// withoutHeld is the node's own composition c without the resources of a held module (its id naming it) and without
|
||||
// its filled paths, for comparing the rest.
|
||||
func withoutHeld(c delivery.Composed, isHeld func(string) bool) delivery.Composed {
|
||||
out := c
|
||||
out.Filled = nil
|
||||
out.Resources = nil
|
||||
for _, raw := range c.Resources {
|
||||
var head struct {
|
||||
ID string `json:"id"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
_ = json.Unmarshal(raw, &head)
|
||||
owner := head.Module
|
||||
if owner == "" {
|
||||
owner, _ = declaration.ModuleGuess(head.ID)
|
||||
}
|
||||
if owner != "" && isHeld(owner) {
|
||||
continue
|
||||
}
|
||||
out.Resources = append(out.Resources, raw)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// dependents is, of the modules placed on node and not held, those that read what man says to them: each that provides
|
||||
// what man contributes to (the proxy its routes), and, when man listens on a port, each that holds the filter (the
|
||||
// openings).
|
||||
func dependents(in compose.Input, node string, man catalogue.Manifest, failed map[string]string) []string {
|
||||
to := map[string]bool{}
|
||||
for p := range man.Contributes {
|
||||
to[p] = true
|
||||
}
|
||||
for p := range man.ContributesMany {
|
||||
to[p] = true
|
||||
}
|
||||
listens := len(man.Listens) > 0
|
||||
if len(to) == 0 && !listens {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for _, m := range assignedOn(in, node) {
|
||||
if _, held := failed[m]; held || m == man.Module {
|
||||
continue
|
||||
}
|
||||
pm, ok := manifestOn(in, node, m)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if listens && pm.Filtering != nil {
|
||||
out = append(out, m)
|
||||
continue
|
||||
}
|
||||
for _, o := range pm.Provides {
|
||||
if to[o.Name] {
|
||||
out = append(out, m)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// assignedOn is the modules placed on node, in placement order.
|
||||
func assignedOn(in compose.Input, node string) []string {
|
||||
for _, n := range in.Nodes {
|
||||
if n.Name == node {
|
||||
return n.Assigned
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// manifestOn is the manifest node composes module from: its Kept, else the catalogue's.
|
||||
func manifestOn(in compose.Input, node, module string) (catalogue.Manifest, bool) {
|
||||
for _, n := range in.Nodes {
|
||||
if n.Name == node {
|
||||
if man, ok := n.Kept[module]; ok {
|
||||
return man, true
|
||||
}
|
||||
}
|
||||
}
|
||||
man, ok := in.Catalogue[module]
|
||||
return man, ok
|
||||
}
|
||||
|
||||
// culprit is the module placed on node whose composition the error is of: the first, in placement order, whose stub
|
||||
// (compose.Stub) makes the composition succeed or fail otherwise; empty when none does (the error is not one module's).
|
||||
// Modules already refused are passed over. A module whose stub still fails the same is tried bare (its stub contributing
|
||||
// nothing, bare true): a refusal of what it contributes is its own, which the stub keeps (issue 1126, seed-8: de-spiegel's
|
||||
// route names on novox, an unset required value, refused the whole node).
|
||||
func culprit(in compose.Input, node string, fps map[string]string, failed map[string]string, err error) (string, bool) {
|
||||
// (compose.Stub) makes the composition in mode succeed or fail otherwise; empty when none does (the error is not one
|
||||
// module's). Modules already refused are passed over. A module whose stub still fails the same is tried bare (its stub
|
||||
// contributing nothing, bare true): a refusal of what it contributes is its own, which the stub keeps (issue 1126,
|
||||
// seed-8: de-spiegel's route names on novox, an unset required value, refused the whole node).
|
||||
func culprit(in compose.Input, node string, fps map[string]string, mode compose.Mode, failed map[string]string, err error) (string, bool) {
|
||||
var assigned []string
|
||||
for _, n := range in.Nodes {
|
||||
if n.Name == node {
|
||||
@@ -266,7 +508,7 @@ func culprit(in compose.Input, node string, fps map[string]string, failed map[st
|
||||
}
|
||||
}
|
||||
changes := func(m string, bare bool) bool {
|
||||
_, _, _, again := compose.CompositionsShadow(stubbed(in, node, m, bare), node, fps)
|
||||
_, _, _, again := compose.CompositionsIn(stubbed(in, node, m, bare), node, fps, mode)
|
||||
return again == nil || again.Error() != err.Error()
|
||||
}
|
||||
for _, m := range order {
|
||||
@@ -277,8 +519,16 @@ func culprit(in compose.Input, node string, fps map[string]string, failed map[st
|
||||
return m, false
|
||||
}
|
||||
}
|
||||
var kept map[string]catalogue.Manifest
|
||||
for _, n := range in.Nodes {
|
||||
if n.Name == node {
|
||||
kept = n.Kept
|
||||
}
|
||||
}
|
||||
for _, m := range order {
|
||||
if _, done := failed[m]; done {
|
||||
// One already held is tried bare too, once: held for its build, its stub may still refuse (its own secret
|
||||
// never issued, issue 1126).
|
||||
if _, done := failed[m]; done && isBare(kept[m]) {
|
||||
continue
|
||||
}
|
||||
if changes(m, true) {
|
||||
@@ -288,31 +538,47 @@ func culprit(in compose.Input, node string, fps map[string]string, failed map[st
|
||||
return "", false
|
||||
}
|
||||
|
||||
// stubbed is in with module composed as a stub on node alone (compose.Stub, as node's Kept); bare, a stub that also
|
||||
// contributes nothing to what it requires.
|
||||
// isBare says a stub contributes nothing and declares no own secret (stubbed, bare).
|
||||
func isBare(m catalogue.Manifest) bool {
|
||||
return len(m.Contributes) == 0 && len(m.ContributesMany) == 0 && len(m.OwnSecrets) == 0
|
||||
}
|
||||
|
||||
// stubbed is in with module held on node as its stub alone (heldAs, nothing of it running), from the manifest node
|
||||
// composes it from: a culprit's try.
|
||||
func stubbed(in compose.Input, node, module string, bare bool) compose.Input {
|
||||
man, ok := manifestOn(in, node, module)
|
||||
if !ok {
|
||||
return in
|
||||
}
|
||||
return heldAs(in, node, module, man, bare, nil)
|
||||
}
|
||||
|
||||
// heldAs is in with module held on node (compose.Node.Held): composed from man as what runs of it (compose.Carried of
|
||||
// runs; its stub, compose.Stub, when runs is nil or holds nothing of it), as node's Kept; bare, contributing nothing to
|
||||
// what it requires and declaring no own secret: none of a held module's own composition is applied, so what it has not
|
||||
// been issued (restic's repository on a node it never ran on) is no refusal of the rest.
|
||||
func heldAs(in compose.Input, node, module string, man catalogue.Manifest, bare bool, runs *delivery.Composed) compose.Input {
|
||||
out := in
|
||||
out.Nodes = append([]compose.Node(nil), in.Nodes...)
|
||||
for i := range out.Nodes {
|
||||
if out.Nodes[i].Name != node {
|
||||
continue
|
||||
}
|
||||
man, ok := out.Nodes[i].Kept[module]
|
||||
if !ok {
|
||||
if man, ok = in.Catalogue[module]; !ok {
|
||||
return in
|
||||
}
|
||||
if bare {
|
||||
man.Contributes, man.ContributesMany, man.OwnSecrets = nil, nil, nil
|
||||
}
|
||||
kept := make(map[string]catalogue.Manifest, len(out.Nodes[i].Kept)+1)
|
||||
for k, v := range out.Nodes[i].Kept {
|
||||
kept[k] = v
|
||||
}
|
||||
stub := compose.Stub(man)
|
||||
if bare {
|
||||
stub.Contributes, stub.ContributesMany = nil, nil
|
||||
}
|
||||
kept[module] = stub
|
||||
kept[module] = compose.Carried(man, module, runs)
|
||||
out.Nodes[i].Kept = kept
|
||||
held := make(map[string]bool, len(out.Nodes[i].Held)+1)
|
||||
for k, v := range out.Nodes[i].Held {
|
||||
held[k] = v
|
||||
}
|
||||
held[module] = true
|
||||
out.Nodes[i].Held = held
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
|
||||
}
|
||||
// A refusal naming no module placed here is no module's: no compose is spent stubbing each in turn (issue 1126: ace's
|
||||
// answer took 7m46s, one compose per module), though here a stub would compose.
|
||||
if m, _ := culprit(in, "n1", fps, map[string]string{}, fmt.Errorf("nothing in this mesh provides \"artifact-store\"")); m != "" {
|
||||
if m, _ := culprit(in, "n1", fps, compose.Foreseeing, map[string]string{}, fmt.Errorf("nothing in this mesh provides \"artifact-store\"")); m != "" {
|
||||
t.Errorf("a refusal naming no placed module was laid on %s", m)
|
||||
}
|
||||
ownKey, _ := delivery.ComposedNodeKey("n1")
|
||||
@@ -129,7 +129,10 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
|
||||
running = append(running, c)
|
||||
}
|
||||
scope := Scope{Node: "n1", InOwn: []string{"spiegel"}, Running: running}
|
||||
got, _, err := Compose(ctx, b, Mesh{}, scope, fps)
|
||||
got, _, held, err := Compose(ctx, b, Mesh{}, scope, fps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("the node does not compose itself with spiegel folded at a build of the old form: %v", err)
|
||||
}
|
||||
@@ -162,9 +165,9 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
|
||||
t.Errorf("the node's own with its bus membership is answered %+v, want the same", a)
|
||||
}
|
||||
// Without what runs, the fold composes spiegel from its manifest, as before, and the composition refuses it.
|
||||
if _, _, err := Compose(ctx, b, Mesh{}, Scope{Node: "n1", InOwn: []string{"spiegel"}}, fps); err == nil ||
|
||||
!strings.Contains(err.Error(), "spiegel has not been built for this mesh") {
|
||||
t.Errorf("composed spiegel without what runs: %v", err)
|
||||
if _, _, held, err := Compose(ctx, b, Mesh{}, Scope{Node: "n1", InOwn: []string{"spiegel"}}, fps); err != nil ||
|
||||
!strings.Contains(held["spiegel"], "spiegel has not been built for this mesh") {
|
||||
t.Errorf("composed spiegel without what runs: %v %v, want spiegel held alone", err, held)
|
||||
}
|
||||
|
||||
// (C) Issue 1126, C5: what the others read of a module carried as it runs is what runs, never its stub's: the filter
|
||||
@@ -217,7 +220,10 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
|
||||
}
|
||||
running = append(running, c)
|
||||
}
|
||||
got, _, err = Compose(ctx, b, Mesh{}, Scope{Node: "n1", InOwn: []string{"spiegel"}, Running: running}, fps)
|
||||
got, _, held, err = Compose(ctx, b, Mesh{}, Scope{Node: "n1", InOwn: []string{"spiegel"}, Running: running}, fps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("the node does not compose itself with spiegel carried: %v", err)
|
||||
}
|
||||
@@ -278,7 +284,8 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
|
||||
// de-spiegel folded, and its manifest at the indexer's commit (e25e1beb, captured) asks four required config values
|
||||
// for its route names that nothing sets. novox answered the whole candidate refused ("de-spiegel contributing
|
||||
// praktijk to route says ${setting:praktijk-name} ...": its stub still contributed the route). Only de-spiegel is
|
||||
// refused, in the config form's words, and the rest is judged.
|
||||
// refused, in the config form's words, and proxy, which receives its routes (what it would compose lacks them: review
|
||||
// of the apply, round 1); the rest is judged.
|
||||
spiegel, err := os.ReadFile("testdata/de-spiegel-e25e1beb.module.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -302,11 +309,143 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
|
||||
t.Errorf("de-spiegel is refused in other words than its config form's: %s", r.Why)
|
||||
}
|
||||
}
|
||||
if a.Refused != "" || !reflect.DeepEqual(refused, []string{"de-spiegel"}) {
|
||||
t.Errorf("a module's own refusal: %+v, want de-spiegel refused alone, the node not", a)
|
||||
if a.Refused != "" || !reflect.DeepEqual(refused, []string{"de-spiegel", "proxy"}) {
|
||||
t.Errorf("a module's own refusal: %+v, want de-spiegel refused with proxy, which receives its routes, the node not", a)
|
||||
}
|
||||
if !slices.ContainsFunc(a.Added, func(m delivery.ModuleAdded) bool { return m.Module == "proxy" }) {
|
||||
t.Errorf("proxy, which composes, is not judged beside de-spiegel's refusal: %+v", a)
|
||||
if !slices.ContainsFunc(a.Added, func(m delivery.ModuleAdded) bool { return m.Module == "lemurs" }) {
|
||||
t.Errorf("lemurs, which composes, is not judged beside de-spiegel's refusal: %+v", a)
|
||||
}
|
||||
|
||||
// (E) Issue 1126, the applying path (review of the seed): the composition novox applies follows the same rule. It
|
||||
// composed with no culprit, so de-spiegel's unset route names refused the whole node and every module it runs was
|
||||
// marked not composed. Now de-spiegel is held, its refusal said in its config form's words, with proxy, which
|
||||
// receives its routes; lemurs composes, and the node's own composition carries de-spiegel as it runs, so applying it
|
||||
// removes nothing of it.
|
||||
spiegelRuns := []json.RawMessage{
|
||||
json.RawMessage(`{"id":"de-spiegel.state","type":"directory","path":"/var/lib/de-spiegel","mode":"0755"}`),
|
||||
json.RawMessage(`{"id":"de-spiegel.server-env","type":"file","path":"/var/lib/de-spiegel/server.env","mode":"0600","content":"A=1\n"}`),
|
||||
json.RawMessage(`{"id":"de-spiegel.server","type":"container","name":"de-spiegel","image":"registry.example/de-spiegel@sha256:` + strings.Repeat("c", 64) + `"}`)}
|
||||
if in, err = Input(ctx, b, Mesh{}, stub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in.Nodes[0].Kept = map[string]catalogue.Manifest{"de-spiegel": compose.Stub(in.Catalogue["de-spiegel"])}
|
||||
in.Nodes[0].Kept["de-spiegel"] = func(m catalogue.Manifest) catalogue.Manifest {
|
||||
m.Contributes, m.ContributesMany = nil, nil
|
||||
return m
|
||||
}(in.Nodes[0].Kept["de-spiegel"])
|
||||
if base, _, err = compose.Compositions(in, "n1", fps, compose.Reading); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
running = nil
|
||||
for _, c := range base {
|
||||
if c.Module == "" {
|
||||
c.Resources = append(append([]json.RawMessage{}, c.Resources...), spiegelRuns...)
|
||||
}
|
||||
running = append(running, c)
|
||||
}
|
||||
got, _, held, err = Compose(ctx, b, Mesh{}, Scope{Node: "n1", InOwn: []string{"de-spiegel"}, Running: running}, fps)
|
||||
if err != nil {
|
||||
t.Fatalf("de-spiegel's own refusal refuses the node's whole composition: %v", err)
|
||||
}
|
||||
if len(held) != 2 || !strings.Contains(held["de-spiegel"], "${config:praktijk-name}") || !strings.Contains(held["proxy"], "de-spiegel") {
|
||||
t.Errorf("held %v, want de-spiegel in its config form's words, and proxy for its routes", held)
|
||||
}
|
||||
lemursKey, _ := delivery.ComposedKey("n1", "lemurs", fps["lemurs"])
|
||||
proxyKey, _ := delivery.ComposedKey("n1", "proxy", fps["proxy"])
|
||||
if _, ok := got[lemursKey]; !ok {
|
||||
t.Errorf("lemurs, which composes, is not composed beside de-spiegel's refusal: %v", sortedKeys(got))
|
||||
}
|
||||
if _, ok := got[proxyKey]; ok {
|
||||
t.Errorf("proxy is composed without the routes of held de-spiegel: %v", sortedKeys(got))
|
||||
}
|
||||
carries := func(c delivery.Composed, what string) {
|
||||
t.Helper()
|
||||
ownRes, _ := json.Marshal(c.Resources)
|
||||
for _, raw := range spiegelRuns {
|
||||
var r struct{ ID string }
|
||||
_ = json.Unmarshal(raw, &r)
|
||||
if !strings.Contains(string(ownRes), `"id":"`+r.ID+`"`) {
|
||||
t.Errorf("%s: the node's own composition drops %s, which de-spiegel runs: %s", what, r.ID, ownRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
carries(got[ownKey], "folded")
|
||||
// (F) Leaving the fold (review of the apply, round 1, finding 1): de-spiegel's want names a build of the new form,
|
||||
// so it is no longer folded, while its names are still unset and the node's own still runs it. Held, it is carried
|
||||
// in the node's own as it runs until a composition of its own is handed: else the node's own apply removes its
|
||||
// container, files and units (issue 1945 by a new path).
|
||||
fps["de-spiegel"] = fp("7")
|
||||
got, _, held, err = Compose(ctx, b, Mesh{}, Scope{Node: "n1", Running: running}, fps)
|
||||
if err != nil || !strings.Contains(held["de-spiegel"], "${config:praktijk-name}") {
|
||||
t.Fatalf("de-spiegel leaving the fold: %v, held %v", err, held)
|
||||
}
|
||||
spiegelKey, _ := delivery.ComposedKey("n1", "de-spiegel", fps["de-spiegel"])
|
||||
if _, ok := got[spiegelKey]; ok {
|
||||
t.Errorf("held de-spiegel's own composition is handed")
|
||||
}
|
||||
carries(got[ownKey], "leaving the fold")
|
||||
|
||||
// (G) A held module that runs a composition of its own (review of the apply, round 1, finding 2): what the others
|
||||
// compose of it is what runs, never its stub's. site listens on 8080, placed at the machine's 20005, and its own
|
||||
// secret was never issued here: held, the filter must not open 8080 and drop the 20005 that runs. A module whose
|
||||
// composition depends on how site is held (here the filter) is held with it.
|
||||
site := []byte(`{"module":"site","version":"1","capabilities":["container-runtime"],` +
|
||||
`"listens":[{"port":8080,"from":"mesh","why":"the site"}],"own-secrets":{"key":"${dir:state}/key"},` +
|
||||
`"resources":[{"id":"state","type":"directory","path":"/var/lib/site","mode":"0700"},` +
|
||||
`{"id":"server","type":"container","name":"site","image":"registry.example/site@sha256:` + strings.Repeat("c", 64) + `","ports":["8080"]}]}`)
|
||||
b = mk("lemurs", overlay.Name, "site", "nft")
|
||||
for m, raw := range map[string][]byte{"site": site, "nft": nft} {
|
||||
b.manifests[delivery.ManifestDigest(raw)] = raw
|
||||
b.modules[m] = delivery.Module{Manifest: delivery.ManifestDigest(raw)}
|
||||
}
|
||||
p = b.placements["n1"]
|
||||
p.Decided.Endpoint = "n1.example:51820"
|
||||
for i := range p.Modules {
|
||||
if p.Modules[i].Module == "site" {
|
||||
p.Modules[i].Ports = map[string]int{"8080": 20005}
|
||||
}
|
||||
}
|
||||
b.placements["n1"] = p
|
||||
rec = b.records["n1"]
|
||||
rec.OutwardLinks = []string{"eth0"}
|
||||
b.records["n1"] = rec
|
||||
fps = map[string]string{"lemurs": fp("e"), "nft": fp("f"), "site": fp("8")}
|
||||
siteRuns := delivery.Composed{Node: "n1", Module: "site", FP: fps["site"], Resources: []json.RawMessage{
|
||||
json.RawMessage(`{"id":"site.state","module":"site","type":"directory","path":"/var/lib/site","mode":"0700"}`),
|
||||
json.RawMessage(`{"id":"site.server","module":"site","type":"container","name":"site","image":"registry.example/site@sha256:` + strings.Repeat("c", 64) + `","ports":["10.77.0.1:20005:8080"]}`)}}
|
||||
got, _, held, err = Compose(ctx, b, Mesh{}, Scope{Node: "n1", Running: []delivery.Composed{siteRuns}}, fps)
|
||||
if err != nil || held["site"] == "" || held["nft"] == "" {
|
||||
t.Fatalf("site, its own secret never issued: %v, held %v; want site held, and the filter, which depends on it", err, held)
|
||||
}
|
||||
for key, c := range got {
|
||||
for _, raw := range c.Resources {
|
||||
if strings.Contains(string(raw), "dport 8080") || (c.Module == "nft" && !strings.Contains(string(raw), "dport 20005") && strings.Contains(string(raw), "# site")) {
|
||||
t.Errorf("%s composes held site from its stub, not from what runs: %s", key, raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
// (H) Review of the apply, round 2: a held module is composed from its new manifest, so what the others read of its
|
||||
// manifest (its listens, what it contributes) may not be what runs. site's new manifest moves it to 9090 while the
|
||||
// build that runs listens on 8080: the filter, the holder of what site listens on, is held with it, and nothing
|
||||
// applied opens 9090.
|
||||
moved := []byte(strings.Replace(string(site), `"port":8080`, `"port":9090`, 1))
|
||||
b.manifests[delivery.ManifestDigest(moved)] = moved
|
||||
b.modules["site"] = delivery.Module{Manifest: delivery.ManifestDigest(moved)}
|
||||
for i := range p.Modules {
|
||||
p.Modules[i].Ports = nil
|
||||
}
|
||||
b.placements["n1"] = p
|
||||
siteRuns.Resources[1] = json.RawMessage(`{"id":"site.server","module":"site","type":"container","name":"site","image":"registry.example/site@sha256:` + strings.Repeat("c", 64) + `","ports":["8080"]}`)
|
||||
got, _, held, err = Compose(ctx, b, Mesh{}, Scope{Node: "n1", Running: []delivery.Composed{siteRuns}}, fps)
|
||||
if err != nil || held["site"] == "" || held["nft"] == "" {
|
||||
t.Errorf("site moved to 9090 in its new manifest: %v, held %v; want site held, and the filter with it", err, held)
|
||||
}
|
||||
for key, c := range got {
|
||||
for _, raw := range c.Resources {
|
||||
if strings.Contains(string(raw), "dport 9090") {
|
||||
t.Errorf("%s opens 9090, which held site's new manifest names and nothing runs: %s", key, raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
// **Own secrets and pair credentials** come from the vault sealed to the composing node's key (OwnSecrets, Credentials),
|
||||
// for that node alone: as a consumer and as a provider, exactly the keys compose.CredentialsNeeded names. **Not yet
|
||||
// read** (each a later subtask of issue 1126): licences (none is placed today) and core boxes. Without one
|
||||
// the composition stands in for it, and Compose refuses a composition that stands in for anything (compose.ErrStoodIn):
|
||||
// a node never applies a stand-in. No node is joining before ADR 0547's third milestone (ADR 0550 §6), so Joining is
|
||||
// the composition stands in for it, and Compose holds the module it stands in for (or refuses the node, for a stand-in
|
||||
// of no module placed there; compose.ErrStoodIn): a node never applies a stand-in. No node is joining before ADR 0547's third milestone (ADR 0550 §6), so Joining is
|
||||
// always empty.
|
||||
package frombus
|
||||
|
||||
@@ -379,25 +379,31 @@ func input(ctx context.Context, r Reader, mesh Mesh, s Scope, refuse func(module
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// Compose is the node's compositions over the bus (ADR 0547 §2): Input for s, then compose.CompositionsApplied at the
|
||||
// fingerprints fps gives each module: composed in the Reading mode as the controller composes them, and refused when it
|
||||
// stands in for anything the input does not carry (compose.ErrStoodIn), since the node applies what it composed.
|
||||
func Compose(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]string) (map[string]delivery.Composed, []string, error) {
|
||||
in, err := Input(ctx, r, mesh, s)
|
||||
// Compose is the node's compositions over the bus (ADR 0547 §2): input for s, then composed at the fingerprints fps
|
||||
// gives each module in the Reading mode, as the controller composes them, by the one rule its answer to a candidate
|
||||
// follows too (composeEach, issue 1126): a module refused alone (its build unread or not taken in, its composition
|
||||
// refused, a stand-in for a value of its own the input does not carry, compose.ErrStoodIn) is held, named in held with
|
||||
// why, and none of its own composition is in out, so the node keeps what it last applied of it and applies the rest; a
|
||||
// node never applies a stand-in. Only a refusal that is no single module's (the node's own resources, a value served
|
||||
// elsewhere, a stand-in for the node's own) refuses the node, err.
|
||||
func Compose(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]string) (out map[string]delivery.Composed, skipped []string, held map[string]string, err error) {
|
||||
held = map[string]string{}
|
||||
in, err := input(ctx, r, mesh, s, func(module string, err error) { held[module] = err.Error() })
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, held, err
|
||||
}
|
||||
// The pair credentials its composition reads, asked of the vault sealed to it (ADR 0550 §5).
|
||||
asConsumer, asProvider, err := compose.CredentialsNeeded(in, s.Node)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, held, err
|
||||
}
|
||||
if len(asConsumer)+len(asProvider) > 0 {
|
||||
if in.Credentials, in.Issued, err = r.Credentials(ctx, s.Node, asConsumer, asProvider); err != nil {
|
||||
return nil, nil, fmt.Errorf("frombus: the credentials of %s: %w", s.Node, err)
|
||||
return nil, nil, held, fmt.Errorf("frombus: the credentials of %s: %w", s.Node, err)
|
||||
}
|
||||
}
|
||||
return compose.CompositionsApplied(in, s.Node, fps)
|
||||
out, skipped, _, err = composeEach(in, s.Node, fps, true, s.Running, held)
|
||||
return out, skipped, held, err
|
||||
}
|
||||
|
||||
// fromBuilds puts in the built manifest of each module placed on the composing node, at the trunk's FP2, and of each
|
||||
|
||||
@@ -223,7 +223,10 @@ func TestInput(t *testing.T) {
|
||||
// The same composer over both: the compositions from the bus are the store's, byte for byte.
|
||||
fps := map[string]string{overlay.Name: "src2:" + strings.Repeat("a", 64), "xterm": "src2:" + strings.Repeat("b", 64),
|
||||
"media": "src2:" + strings.Repeat("c", 64)}
|
||||
fromBus, _, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop"}, fps)
|
||||
fromBus, _, held, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop"}, fps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -243,7 +246,10 @@ func TestInput(t *testing.T) {
|
||||
running = append(running, c)
|
||||
}
|
||||
laptop := mk().placements["laptop"]
|
||||
asIs, _, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Placements: map[string]*delivery.Placement{"laptop": &laptop}}, fps)
|
||||
asIs, _, held, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Placements: map[string]*delivery.Placement{"laptop": &laptop}}, fps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -252,7 +258,10 @@ func TestInput(t *testing.T) {
|
||||
}
|
||||
moved := mk().placements["anchor"]
|
||||
moved.Decided.Endpoint = "anchor.example:51821"
|
||||
changed, _, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Placements: map[string]*delivery.Placement{"laptop": &laptop, "anchor": &moved}}, fps)
|
||||
changed, _, held, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Placements: map[string]*delivery.Placement{"laptop": &laptop, "anchor": &moved}}, fps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -289,7 +298,10 @@ func TestInput(t *testing.T) {
|
||||
}
|
||||
oneFps := map[string]string{"keeper": "src2:" + strings.Repeat("d", 64), "lemurs": "src2:" + strings.Repeat("e", 64),
|
||||
"objects": "src2:" + strings.Repeat("f", 64), "files": "src2:" + strings.Repeat("0", 64)}
|
||||
runs, _, err := Compose(ctx, one("keeper", "lemurs"), Mesh{}, Scope{Node: "n1"}, oneFps)
|
||||
runs, _, held, err := Compose(ctx, one("keeper", "lemurs"), Mesh{}, Scope{Node: "n1"}, oneFps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -297,7 +309,10 @@ func TestInput(t *testing.T) {
|
||||
for _, c := range runs {
|
||||
runsNow = append(runsNow, c)
|
||||
}
|
||||
objectsRun, _, err := Compose(ctx, one("objects"), Mesh{}, Scope{Node: "n1"}, oneFps)
|
||||
objectsRun, _, held, err := Compose(ctx, one("objects"), Mesh{}, Scope{Node: "n1"}, oneFps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -579,11 +594,21 @@ func TestInput(t *testing.T) {
|
||||
}
|
||||
return b
|
||||
}
|
||||
if _, _, err := Compose(ctx, movedMesh(false), Mesh{}, Scope{Node: "n1"}, movedFps); err == nil || !strings.Contains(err.Error(), `restic needs a secret called "repository"`) {
|
||||
t.Errorf("restic composed from the indexer's manifest: %v, want its own secret refused", err)
|
||||
if out, _, held, err := Compose(ctx, movedMesh(false), Mesh{}, Scope{Node: "n1"}, movedFps); err != nil || !strings.Contains(held["restic"], `restic needs a secret called "repository"`) {
|
||||
t.Errorf("restic composed from the indexer's manifest: %v %v, want its own secret refusing restic alone", err, held)
|
||||
} else {
|
||||
for key, c := range out {
|
||||
if c.Module == "restic" {
|
||||
// Its stub's composition, applied, would remove what restic runs: a held module keeps its last one.
|
||||
t.Errorf("held restic is composed as %s, want none of its own composition handed", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
builtBus := movedMesh(true)
|
||||
fromBuild, _, err := Compose(ctx, builtBus, Mesh{}, Scope{Node: "n1"}, movedFps)
|
||||
fromBuild, _, held, err := Compose(ctx, builtBus, Mesh{}, Scope{Node: "n1"}, movedFps)
|
||||
if err == nil && len(held) > 0 {
|
||||
err = fmt.Errorf("modules held: %v", held)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("restic composed from its build: %v", err)
|
||||
}
|
||||
@@ -597,8 +622,8 @@ func TestInput(t *testing.T) {
|
||||
// A done key an artifact of which names no reference is waited for, in the controller's words (ADR 0498 §8).
|
||||
unreferenced := movedMesh(true)
|
||||
unreferenced.builds["restic "+movedFps["restic"]].Artifacts = []delivery.BuiltArtifact{{Name: "tools", Kind: "bundle", Digest: digestOf("6")}, resticArtifacts[1]}
|
||||
if _, _, err := Compose(ctx, unreferenced, Mesh{}, Scope{Node: "n1"}, movedFps); err == nil || !strings.Contains(err.Error(), "the build of restic is done and waits for the reference of tools") {
|
||||
t.Errorf("a done key with an unreferenced artifact: %v", err)
|
||||
if _, _, held, err := Compose(ctx, unreferenced, Mesh{}, Scope{Node: "n1"}, movedFps); err != nil || !strings.Contains(held["restic"], "the build of restic is done and waits for the reference of tools") {
|
||||
t.Errorf("a done key with an unreferenced artifact: %v %v, want restic held alone", err, held)
|
||||
}
|
||||
// A module the node runs inside its own composition (the fold) composes from the indexer's manifest though a done
|
||||
// build of it is at the trunk's FP2: its build is not read.
|
||||
@@ -662,12 +687,12 @@ func TestInput(t *testing.T) {
|
||||
}
|
||||
return b
|
||||
}
|
||||
if _, _, err := Compose(ctx, withMailer(false), Mesh{}, Scope{Node: "laptop"}, fps); err == nil || !strings.Contains(err.Error(), `nothing sets "domain"`) {
|
||||
if _, _, _, err := Compose(ctx, withMailer(false), Mesh{}, Scope{Node: "laptop"}, fps); err == nil || !strings.Contains(err.Error(), `nothing sets "domain"`) {
|
||||
t.Errorf("a provider's served setting nobody sets: %v", err)
|
||||
}
|
||||
served := withMailer(true)
|
||||
if _, _, err := Compose(ctx, served, Mesh{}, Scope{Node: "laptop"}, fps); err != nil {
|
||||
t.Errorf("laptop with what anchor's mailer serves: %v", err)
|
||||
if _, _, held, err := Compose(ctx, served, Mesh{}, Scope{Node: "laptop"}, fps); err != nil || len(held) > 0 {
|
||||
t.Errorf("laptop with what anchor's mailer serves: %v %v", err, held)
|
||||
}
|
||||
if !reflect.DeepEqual(served.servedRead, []string{"anchor.mailer:domain"}) || slices.Contains(served.valuesRead, "anchor.mailer") {
|
||||
t.Errorf("laptop read %v of other nodes (and %v of config values): the served keys of anchor's mailer alone", served.servedRead, served.valuesRead)
|
||||
|
||||
@@ -204,6 +204,12 @@ type Node struct {
|
||||
// so it is left to the node: Compositions composes it as a stub (Stub) and carries its resources as they run, each
|
||||
// after the resource it runs after (issue 1126). Nil composes every folded module from its manifest, as before.
|
||||
RunsOwn *delivery.Composed
|
||||
// Held is the modules refused alone on this node, each composed as what runs of it (Carried, in Kept; its Stub when
|
||||
// nothing of it runs) so the rest is composed (novox/hq issue 1126): none of a held module's own composition is in what
|
||||
// Compositions answers, so the node keeps what it last applied of it; one whose resources the node's own runs
|
||||
// (RunsOwn), folded or leaving the fold, is carried there as it runs, whatever its manifest names, so the node's own
|
||||
// never drops it.
|
||||
Held map[string]bool
|
||||
}
|
||||
|
||||
// Place is where a node is, as its placement says (inv.Overlay).
|
||||
|
||||
Reference in New Issue
Block a user