The assigner waits for a requirement nothing offers, and for a provision the provisioner keys whoever is placed yet; a requirement something offers (a module's own name, a module the mesh provides itself) is left to Awaited as before (ADR 0603 §5)
This commit is contained in:
@@ -446,6 +446,16 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
|||||||
dbDigest := b.manifest(t, "db", `{"module":"db","version":"1",`+offer+`}`)
|
dbDigest := b.manifest(t, "db", `{"module":"db","version":"1",`+offer+`}`)
|
||||||
appDigest := b.manifest(t, "app", `{"module":"app","version":"1","requires":["database"]}`)
|
appDigest := b.manifest(t, "app", `{"module":"app","version":"1","requires":["database"]}`)
|
||||||
toolDigest := b.manifest(t, "tool", `{"module":"tool","version":"1"}`)
|
toolDigest := b.manifest(t, "tool", `{"module":"tool","version":"1"}`)
|
||||||
|
asked := func(state, why string) {
|
||||||
|
raw, err := provisions.Encode(provisions.Provision{V: provisions.Version, Provision: "database", Consumer: provisions.End{Module: "app", Node: "ace"},
|
||||||
|
Provider: provisions.End{Module: "db", Node: "ace"}, State: state, Why: why, At: time.Now()})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b.putJSON(t, provisions.ProvisionsBucket, "ace.app.database", raw)
|
||||||
|
}
|
||||||
|
// The provisioner keys it from the manifests, as soon as they are known and whether the provider is placed yet or not.
|
||||||
|
asked(provisions.Asked, "the provider's build is not done")
|
||||||
fpOf := func(c byte) string { return delivery.SourcePrefix2 + strings.Repeat(string(c), 64) }
|
fpOf := func(c byte) string { return delivery.SourcePrefix2 + strings.Repeat(string(c), 64) }
|
||||||
toolOld := fpOf('1')
|
toolOld := fpOf('1')
|
||||||
for module, row := range map[string]struct {
|
for module, row := range map[string]struct {
|
||||||
@@ -469,15 +479,6 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
runsOld, _ := delivery.EncodeHave(delivery.Have{FP: toolOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK})
|
runsOld, _ := delivery.EncodeHave(delivery.Have{FP: toolOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK})
|
||||||
b.putJSON(t, delivery.HaveBucket, "ace.tool", runsOld)
|
b.putJSON(t, delivery.HaveBucket, "ace.tool", runsOld)
|
||||||
asked := func(state, why string) {
|
|
||||||
raw, err := provisions.Encode(provisions.Provision{V: provisions.Version, Provision: "database", Consumer: provisions.End{Module: "app", Node: "ace"},
|
|
||||||
Provider: provisions.End{Module: "db", Node: "ace"}, State: state, Why: why, At: time.Now()})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
b.putJSON(t, provisions.ProvisionsBucket, "ace.app.database", raw)
|
|
||||||
}
|
|
||||||
asked(provisions.Asked, "the provider's build is not done")
|
|
||||||
if w := b.wantNow(t, "ace", "tool", "the build module's want at the second build", func(w delivery.Want) bool { return w.FP == fpOf('4') }); w.FP != fpOf('4') {
|
if w := b.wantNow(t, "ace", "tool", "the build module's want at the second build", func(w delivery.Want) bool { return w.FP == fpOf('4') }); w.FP != fpOf('4') {
|
||||||
t.Errorf("tool's want = %+v", w)
|
t.Errorf("tool's want = %+v", w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func (p *passer) wants(ctx context.Context) {
|
|||||||
for name := range names {
|
for name := range names {
|
||||||
fp := trunk[name].FP2
|
fp := trunk[name].FP2
|
||||||
name := name
|
name := name
|
||||||
m := assign.Module{Name: name, FP: fp, Manifest: manifests[name], Chosen: chosenOf(chosen, name), Provisions: provisionsOf(name), Answering: answering(manifests, nodes), Nodes: nodesOf[name], Now: time.Now(), Puts: puts[name],
|
m := assign.Module{Name: name, FP: fp, Manifest: manifests[name], Chosen: chosenOf(chosen, name), Provisions: provisionsOf(name), Answering: answering(manifests, nodes), Offered: offered(manifests), Nodes: nodesOf[name], Now: time.Now(), Puts: puts[name],
|
||||||
Self: func(node string) bool { return placed[node] },
|
Self: func(node string) bool { return placed[node] },
|
||||||
Composed: func(node, fp string) (delivery.ComposedDigest, bool) {
|
Composed: func(node, fp string) (delivery.ComposedDigest, bool) {
|
||||||
return p.w.composed(node, name, fp) // a placed node's own: the digest its own have names (ADR 0571 §6)
|
return p.w.composed(node, name, fp) // a placed node's own: the digest its own have names (ADR 0571 §6)
|
||||||
@@ -1086,6 +1086,26 @@ func (p *passer) seatHolders(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// offered says whether some manifest of the trunk, or a module the mesh provides itself, offers a provision under its name
|
||||||
|
// (a module offers its own name too, catalogue.Manifest.Offers): what a requirement nobody offers is told from.
|
||||||
|
func offered(manifests map[string]catalogue.Manifest) func(string) bool {
|
||||||
|
names := map[string]bool{}
|
||||||
|
add := func(m catalogue.Manifest) {
|
||||||
|
for _, o := range m.Offers() {
|
||||||
|
names[o] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range manifests {
|
||||||
|
add(m)
|
||||||
|
}
|
||||||
|
for _, module := range delivery.ProvidedModules() {
|
||||||
|
if m, _, err := delivery.ProvidedManifest(module); err == nil {
|
||||||
|
add(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func(provision string) bool { return names[provision] }
|
||||||
|
}
|
||||||
|
|
||||||
// answering is, by provision, the manifests of the modules placed on some node that provide it: what a want's
|
// answering is, by provision, the manifests of the modules placed on some node that provide it: what a want's
|
||||||
// provisions are judged by (catalogue.Awaited, issue 774). A provider in the catalogue placed nowhere answers nobody.
|
// provisions are judged by (catalogue.Awaited, issue 774). A provider in the catalogue placed nowhere answers nobody.
|
||||||
func answering(manifests map[string]catalogue.Manifest, n assign.Nodes) func(string) []catalogue.Manifest {
|
func answering(manifests map[string]catalogue.Manifest, n assign.Nodes) func(string) []catalogue.Manifest {
|
||||||
|
|||||||
@@ -111,6 +111,14 @@ func TestAHoldAskedAgainAfterALiftAndARestartHasANewID(t *testing.T) {
|
|||||||
// 1906/H4: an answer counts only for the asking it answered: an answer to an earlier hold of the same fingerprint, or to an ask
|
// 1906/H4: an answer counts only for the asking it answered: an answer to an earlier hold of the same fingerprint, or to an ask
|
||||||
// taken away, is not taken for the hold that stands now.
|
// taken away, is not taken for the hold that stands now.
|
||||||
func TestAnAnswerCountsOnlyForTheAskingItAnswered(t *testing.T) {
|
func TestAnAnswerCountsOnlyForTheAskingItAnswered(t *testing.T) {
|
||||||
|
// ADR 0603 §5: a provision is offered by a manifest's provides, by a module's own name, and by a module the mesh provides
|
||||||
|
// itself (mesh-addressing, mesh-wireguard's); nothing else.
|
||||||
|
off := offered(map[string]catalogue.Manifest{"node-tools": {Module: "node-tools"}, "db": {Module: "db", Provides: []catalogue.Offer{{Name: "database"}}}})
|
||||||
|
for provision, want := range map[string]bool{"node-tools": true, "database": true, "mesh-addressing": true, "queue": false} {
|
||||||
|
if off(provision) != want {
|
||||||
|
t.Errorf("offered(%s) = %v, want %v", provision, !want, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
key, _ := delivery.HoldAskKey("web", fpTest)
|
key, _ := delivery.HoldAskKey("web", fpTest)
|
||||||
p := newPasser(buckets{}, time.Now())
|
p := newPasser(buckets{}, time.Now())
|
||||||
put := func(asking int) string {
|
put := func(asking int) string {
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ type Module struct {
|
|||||||
// Answering is the manifests of the modules placed somewhere that provide a provision: what catalogue.Awaited
|
// Answering is the manifests of the modules placed somewhere that provide a provision: what catalogue.Awaited
|
||||||
// judges a want's provisions by (issue 774). Nil: none known, and no key is waited for that is not there.
|
// judges a want's provisions by (issue 774). Nil: none known, and no key is waited for that is not there.
|
||||||
Answering func(provision string) []catalogue.Manifest
|
Answering func(provision string) []catalogue.Manifest
|
||||||
|
// Offered says whether any module offers a provision by name or by its own name (catalogue.Manifest.Offers) among every
|
||||||
|
// manifest the trunk holds and the modules the mesh provides itself, placed or not. Nil: not known, and no requirement
|
||||||
|
// is waited on for want of an offer.
|
||||||
|
Offered func(provision string) bool
|
||||||
// Chosen is a person's choice of the module's upgrade policy (the assigner's `policies`, ADR 0565 §7); nil: none.
|
// Chosen is a person's choice of the module's upgrade policy (the assigner's `policies`, ADR 0565 §7); nil: none.
|
||||||
Chosen *catalogue.PersonPolicy
|
Chosen *catalogue.PersonPolicy
|
||||||
Nodes []string
|
Nodes []string
|
||||||
@@ -154,10 +158,12 @@ func (m Module) provisionWaiting(node string) (string, bool) {
|
|||||||
}
|
}
|
||||||
keyed := m.Provisions(node)
|
keyed := m.Provisions(node)
|
||||||
// Only a provision the newest manifest still requires is waited on (ADR 0603 §9): a key of an older revision's edge,
|
// Only a provision the newest manifest still requires is waited on (ADR 0603 §9): a key of an older revision's edge,
|
||||||
// asked or retiring for the operator's answer (ADR 0570 §5), holds nothing. A requirement catalogue.Awaited drops for a
|
// asked or retiring for the operator's answer (ADR 0570 §5), holds nothing. A requirement catalogue.Awaited leaves out is
|
||||||
// reason the decision keeps (optional, not keyed: a toolchain on the module's own machine, a login the controller makes) is
|
// not waited on when something offers it (a module's own name, a module the mesh provides itself, a provision the
|
||||||
// not waited on; one it drops only because no answering provider is known yet is (ADR 0603 §5: an unknown provider has
|
// composition does not key: optional, a toolchain on the module's own machine, a login the controller makes). One that
|
||||||
// provided nothing), so a consumer never moves ahead of a provider whose module the indexer has not yet taken in.
|
// nothing offers is waited on (ADR 0603 §5: an unknown provider has provided nothing), so a consumer never moves ahead of
|
||||||
|
// a provider whose module the indexer has not yet taken in; it holds loudly, since the watchman raises the hold past its
|
||||||
|
// bound (ADR 0603 §8).
|
||||||
needed := m.Manifest.Requires
|
needed := m.Manifest.Requires
|
||||||
if m.Answering != nil {
|
if m.Answering != nil {
|
||||||
needed = catalogue.Awaited(m.Manifest, m.Answering)
|
needed = catalogue.Awaited(m.Manifest, m.Answering)
|
||||||
@@ -166,12 +172,16 @@ func (m Module) provisionWaiting(node string) (string, bool) {
|
|||||||
have[p.Provision] = true
|
have[p.Provision] = true
|
||||||
}
|
}
|
||||||
for _, r := range slices.Sorted(slices.Values(m.Manifest.Requires)) {
|
for _, r := range slices.Sorted(slices.Values(m.Manifest.Requires)) {
|
||||||
if m.Manifest.OptionalRequires[r] || len(m.Answering(r)) > 0 || slices.Contains(needed, r) {
|
if have[r] && !m.Manifest.OptionalRequires[r] && !slices.Contains(needed, r) {
|
||||||
|
needed = append(needed, r) // the provisioner keys it from the manifests, whoever is placed yet
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m.Offered == nil || m.Manifest.OptionalRequires[r] || len(m.Answering(r)) > 0 || m.Offered(r) || slices.Contains(needed, r) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
needed = append(needed, r)
|
needed = append(needed, r)
|
||||||
if !have[r] {
|
if !have[r] {
|
||||||
return fmt.Sprintf("no provider of %s is known yet for %s on %s: an unknown provider has provided nothing", r, m.Name, node), true
|
return fmt.Sprintf("no module offers %s yet for %s on %s: an unknown provider has provided nothing, so the want waits for one, and the watchman raises it past its bound", r, m.Name, node), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,24 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ADR 0603 §5: a requirement nobody offers is an unknown provider and waits, loudly; one something offers (a module's own
|
||||||
|
// name, a module the mesh provides itself) is left to catalogue.Awaited as before and does not.
|
||||||
|
for _, c := range []struct {
|
||||||
|
name, requires string
|
||||||
|
offered bool
|
||||||
|
}{{"a module's own name (node-tools)", "node-tools", true}, {"a module the mesh provides itself (mesh-addressing)", "mesh-addressing", true},
|
||||||
|
{"a provision nobody offers", "database", false}} {
|
||||||
|
m, n := ready()
|
||||||
|
m.Manifest.Requires = []string{c.requires}
|
||||||
|
m.Answering = func(string) []catalogue.Manifest { return nil }
|
||||||
|
m.Offered = func(p string) bool { return c.offered && p == c.requires }
|
||||||
|
m.Provisions = func(string) map[string]provisions.Provision { return map[string]provisions.Provision{} }
|
||||||
|
out := Wants(m, n, wantedOld(), nil)
|
||||||
|
if got := out.Wants["g14"].FP == fNew; got != c.offered || (!c.offered && (out.Rules["g14"].Rule != delivery.RuleProvisions ||
|
||||||
|
!strings.Contains(out.Rules["g14"].Words, "no module offers database yet") || !strings.Contains(out.Rules["g14"].Words, "past its bound"))) {
|
||||||
|
t.Errorf("%s: want %+v, held by %+v", c.name, out.Wants["g14"], out.Rules["g14"])
|
||||||
|
}
|
||||||
|
}
|
||||||
// 1960: rule 5 holds no want, and no module is left without a first want for a provider down or a node offline; the
|
// 1960: rule 5 holds no want, and no module is left without a first want for a provider down or a node offline; the
|
||||||
// provider down is said (Unreached). novox on 2026-10-29: route-proxy down held de-spiegel and link2pay, minio not
|
// provider down is said (Unreached). novox on 2026-10-29: route-proxy down held de-spiegel and link2pay, minio not
|
||||||
// healthy held photos, and novox offline left a module with no canary.
|
// healthy held photos, and novox offline left a module with no canary.
|
||||||
|
|||||||
Reference in New Issue
Block a user