A provider is a (node, module) pair (design 23), and the pin — the one way a consumer names its provider — named only the node. Two modules on one node can both answer a provision (public-acme and step-ca both offer acme-ca on novox), and then the resolver, given a pin naming that node, took the last provider listed: a coin flip. The same ambiguity beside the consumer was settled by a map walk — random per plan — which is how novox's own route-proxy got its issuer (novox/hq #258). - `pin <node> <provision> <from-node> <module>`: both halves, always. The console gains `pin` and `unpin`. The provider may be on the consumer's own node, since two modules beside it can both answer. - The resolver refuses ambiguity instead of picking, across machines and beside the consumer alike, naming every candidate as node/module and the form of the pin that settles it. A plain capability that grants nothing and serves nothing (three shells beside an editor) is not a choice to put to anybody and stays as it was. - provision_pin gains a nullable module (0050); records made before are completed where the node they name answers once, and left for a person where it answers twice (0051). - The provider of something already satisfied is looked for among what was assigned, not only what the walk has reached — a consumer reached before the provider beside it no longer loses its binding. - The start-time check that every declared verb is runnable samples each verb's required arguments from its schema instead of three guessed keys. Live consequence: a node that has two providers of one bound provision assigned (novox: acme-ca) resolves only once pinned — `pin novox acme-ca novox public-acme`.
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// A pin made before it named the module (migration 0051, novox/hq #258): completed where the node it
|
|
// names answers once, left for a person where it answers twice.
|
|
|
|
func legacyPin(t *testing.T, inv *Inventory, node, provision, provider string) {
|
|
t.Helper()
|
|
_, err := inv.store.Pool().Exec(context.Background(),
|
|
`insert into provision_pin (node, name, provider)
|
|
select u.id, $2, p.id from node u, node p where u.name = $1 and p.name = $3`,
|
|
node, provision, provider)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func completeEarlierPins(t *testing.T, inv *Inventory) {
|
|
t.Helper()
|
|
sql, err := os.ReadFile("migrations/0051-a-pin-made-before-is-completed.sql")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := inv.store.Pool().Exec(context.Background(), string(sql)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAPinMadeBeforeIsCompletedWhenTheNodeAnswersOnce(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
for _, n := range []string{"user", "provider"} {
|
|
if _, err := inv.AddNode(ctx, n); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, m := range []string{"postgres", "redis"} {
|
|
if err := inv.RegisterModule(ctx, manifest(m, []string{m + "-database"}, nil), Source{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := inv.Assign(ctx, "provider", m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
legacyPin(t, inv, "user", "postgres-database", "provider")
|
|
completeEarlierPins(t, inv)
|
|
pins, err := inv.PinsFor(ctx, "user")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := pins["postgres-database"]; got.Node != "provider" || got.Module != "postgres" {
|
|
t.Fatalf("the record was not completed with the one module that answers: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestAPinMadeBeforeIsLeftOpenWhenTheNodeAnswersTwice(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
for _, n := range []string{"user", "provider"} {
|
|
if _, err := inv.AddNode(ctx, n); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, m := range []string{"public-acme", "step-ca"} {
|
|
if err := inv.RegisterModule(ctx, manifest(m, []string{"acme-ca"}, nil), Source{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := inv.Assign(ctx, "provider", m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
legacyPin(t, inv, "user", "acme-ca", "provider")
|
|
completeEarlierPins(t, inv)
|
|
pins, err := inv.PinsFor(ctx, "user")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := pins["acme-ca"]; got.Node != "provider" || got.Module != "" {
|
|
t.Fatalf("a node that answers twice was guessed for: %+v", got)
|
|
}
|
|
}
|