mesh/merge-gate head c9dd07f919a2 merged into trunk 404b4409e70e — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c9dd07f919a2 merged into trunk 404b4409e70e — pass: its merge-check.sh passed (run 2)
mesh/delivery delivered
A Servarr app's API key, an indexer's key and a downloader's password are values the provider's
software holds; the pair path made a random value for every consumer of them, sealed it to both ends
and reported success, and the provider refused it. An offer may now say `"credential": {"issued-by":
"outside"}`: SecretFor never makes a value for it, the plan reads such a pair as given or not given
and sends the consumer no credential file until a person gives it, without refusing its machine;
remakeShared and SharedSecretFor never make a provider's own secret issued outside either.
`secret accept <provider's node> <provider module> <provision>` seals one value to every consumer that
binds the provision from that provider, the provider and the operator, under one generation; the
per-pair form is refused for such a provision, and rotate names the accept. A consumer may wait for a
provision's credential: the controller excuses the wait only when the store bears it out, and its
needs-operator condition names the one accept line.
Adds the replay of issue 164 (TestReplay164), written with only what the controller had before.
114 lines
5.5 KiB
Go
114 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// A consumer may wait for a provision's credential only a person gives (novox/hq ADR 0337 §5–§7, issue 164).
|
|
|
|
var requestsManifest = catalogue.Manifest{Module: "requests", Version: "1", Requires: []string{"tv-api", "db"},
|
|
Secrets: map[string]string{"tv-api": "/s/tv-api", "db": "/s/db"}}
|
|
|
|
var tvKeyWait = inventory.Wait{Part: "the tv library", Provision: "tv-api", What: "the API key of tv"}
|
|
|
|
func outsideFacts(given map[string]bool) operatorWaitFacts {
|
|
return operatorWaitFacts{manifests: map[string]catalogue.Manifest{"requests": requestsManifest},
|
|
outside: map[string]outsideProvider{"tv-api": {Module: "tv", Machines: []string{"media"}}},
|
|
pairsGiven: map[string]map[string]bool{"requests@laptop": given}}
|
|
}
|
|
|
|
// §6: excused only when the module requires the provision, its provider says it is issued outside, and no value a
|
|
// person gave is held for the pair under the keys both ends have now.
|
|
func TestAWaitForAProvisionIsExcusedOnlyWhenTheStoreBearsItOut(t *testing.T) {
|
|
for _, c := range []struct {
|
|
name string
|
|
w inventory.Wait
|
|
f operatorWaitFacts
|
|
says string
|
|
}{
|
|
{"required, issued outside, not given", tvKeyWait, outsideFacts(nil), ""},
|
|
{"given", tvKeyWait, outsideFacts(map[string]bool{"tv-api": true}), "was given"},
|
|
{"not required", inventory.Wait{Part: "p", Provision: "music-api", What: "w"}, outsideFacts(nil), "does not require"},
|
|
{"a credential the mesh gives itself", inventory.Wait{Part: "p", Provision: "db", What: "w"}, outsideFacts(nil),
|
|
"mesh gives itself"},
|
|
{"what was given cannot be read", tvKeyWait, operatorWaitFacts{
|
|
manifests: map[string]catalogue.Manifest{"requests": requestsManifest},
|
|
outside: map[string]outsideProvider{"tv-api": {Module: "tv"}}}, "could not be read"},
|
|
{"a provision and a secret", inventory.Wait{Part: "p", Provision: "tv-api", Secret: "x", What: "w"},
|
|
outsideFacts(nil), "one secret, one setting or one provision"},
|
|
} {
|
|
err := checkWait("requests", "laptop", c.w, c.f)
|
|
switch {
|
|
case c.says == "" && err != nil:
|
|
t.Errorf("%s: refused: %v", c.name, err)
|
|
case c.says != "" && (err == nil || !strings.Contains(err.Error(), c.says)):
|
|
t.Errorf("%s: %v; want a refusal saying %q", c.name, err, c.says)
|
|
}
|
|
}
|
|
}
|
|
|
|
// §7: an excused wait is the consumer's needs-operator condition, in plain words, its summary naming the one line
|
|
// that gives the credential for every consumer of the provider; the wait as kept is not changed.
|
|
func TestAWaitForAProvisionNamesTheOneAcceptForItsProvider(t *testing.T) {
|
|
r := inventory.ResourceHealth{Module: "requests", Resource: "requests.app", Kind: "container", Target: "requests",
|
|
State: link.StateWaiting, Check: "tool", Waits: []inventory.Wait{tvKeyWait}}
|
|
kept := []inventory.ResourceHealth{r}
|
|
got := checkWaiting("laptop", kept, outsideFacts(nil))
|
|
if got[0].State != link.StateWaiting {
|
|
t.Fatalf("an excused wait for a provision was judged: %+v", got[0])
|
|
}
|
|
if kept[0].Waits[0].Act != "" {
|
|
t.Fatal("checking the wait changed the statement as kept")
|
|
}
|
|
waits, waiting := operatorWait("requests", got)
|
|
if !waiting {
|
|
t.Fatal("the module does not read as waiting for the operator")
|
|
}
|
|
o := needsOperatorObservation("requests", "laptop", waits, got)
|
|
if !strings.Contains(o.Summary, "secret accept media tv tv-api") {
|
|
t.Fatalf("the summary does not name the accept for the provider: %q", o.Summary)
|
|
}
|
|
if !strings.HasPrefix(o.Needs, "give the API key of tv") {
|
|
t.Fatalf("the needs do not name the act: %q", o.Needs)
|
|
}
|
|
if why, ok := conditions.PlainWords(conditions.Words{Headline: o.Headline, Explanation: o.Explanation,
|
|
Resolved: o.Resolved, Needs: o.Needs, Actions: o.Actions}, "laptop"); !ok {
|
|
t.Fatalf("the condition is not in plain words: %s", why)
|
|
}
|
|
// A provider on several machines, or none: the line names its machine as a place to fill.
|
|
if line := (outsideProvider{Module: "tv", Machines: []string{"a", "b"}}).acceptLine("tv-api"); line !=
|
|
"secret accept <its machine> tv tv-api" {
|
|
t.Fatalf("a provider on two machines reads %q", line)
|
|
}
|
|
}
|
|
|
|
// §3: the consumers one accept reaches are those whose resolution answers the provision from that provider.
|
|
func TestTheConsumersOfAProviderAreThoseItAnswers(t *testing.T) {
|
|
plan := catalogue.Resolution{Needs: []catalogue.Needed{
|
|
{Name: "tv-api", From: "media", Module: "tv", For: "requests"},
|
|
{Name: "tv-api", From: "media", Module: "tv", For: "indexer", Local: "main"},
|
|
{Name: "tv-api", From: "other", Module: "tv", For: "elsewhere"},
|
|
{Name: "tv-api", From: "media", Module: "another-tv", For: "not-this-module"},
|
|
{Name: "db", From: "media", Module: "tv", For: "requests"},
|
|
{Name: "tv-api", ByRecord: true, For: "by-record"},
|
|
}}
|
|
got := bindsFrom(plan, "laptop", "media", "tv", "tv-api")
|
|
if len(got) != 2 || got[0].ConsumerModule != "indexer" || got[0].Local != "main" || got[1].ConsumerModule != "requests" {
|
|
t.Fatalf("the consumers from tv on media are %+v", got)
|
|
}
|
|
said := acceptedSaid("media", "tv", "tv-api", inventory.ProvisionAccepted{
|
|
Sealed: []inventory.PairEnd{{Consumer: "laptop", ConsumerModule: "requests"}},
|
|
Unsealable: []inventory.PairEnd{{Consumer: "new", ConsumerModule: "indexer"}}}, []string{"down: refused"})
|
|
for _, want := range []string{"sealed to requests on laptop", "no sealing key", "down: refused"} {
|
|
if !strings.Contains(said, want) {
|
|
t.Errorf("what the accept says lacks %q:\n%s", want, said)
|
|
}
|
|
}
|
|
}
|