The controller plans config, templates and moved providers' provisions, hands bus accounts to the vault and judges the new manifest words, on the feature's SDK and node-engine validator (feature 57, config and provisioning).
245 lines
12 KiB
Go
245 lines
12 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A handed module's plan names the provider serving each provision it requires (design 57 V5a, issue 760): the
|
|
// module on the machine resolution chose for this consumer, and the local name it keeps the credential under where
|
|
// it keeps several, so the vault, which reads the plan in the assignments mirror, sees a consumer bind and unbind
|
|
// (novox/hq ADR 0349 clause 8). One entry per provision and local name, the same entry that names a credential
|
|
// the vault keeps for it alone (issue 757).
|
|
|
|
// notifierConsumer is the notifier requiring a mesh-scoped database, marked moved when moved, with its credential
|
|
// kept under two local names (ADR 0094) when locals is set.
|
|
func notifierConsumer(t *testing.T, moved, locals bool) Manifest {
|
|
t.Helper()
|
|
raw := handedNotifier
|
|
if moved {
|
|
raw = notifierMoved()
|
|
}
|
|
add := `"requires":["postgres-database"],`
|
|
if locals {
|
|
add += `"secrets":{"postgres-database":{"hot":"/var/lib/notify/hot","cold":"/var/lib/notify/cold"}},`
|
|
}
|
|
raw = strings.Replace(raw, `"build":`, add+`"build":`, 1)
|
|
return held(t, raw, builtNotifier())
|
|
}
|
|
|
|
// twoStores is a mesh where two machines each run a store providing the database, the consumer pinned to one.
|
|
func twoStores(pin string) World {
|
|
return World{Offered: map[string][]Provider{"postgres-database": {
|
|
{Node: "anchor", At: "anchor.internal", Module: "store", Serves: map[string]any{"port": 5432}},
|
|
{Node: "home", At: "home.internal", Module: "store-two", Serves: map[string]any{"port": 5434}},
|
|
}}, Pinned: map[string]Chosen{"postgres-database": {Node: pin}}}
|
|
}
|
|
|
|
func consumerOn(t *testing.T, m Manifest, w World) Resolution {
|
|
t.Helper()
|
|
node := workstation()
|
|
node.At = "workstation.internal"
|
|
stores := shelf(m,
|
|
Manifest{Module: "store", Version: "1", Provides: []Offer{{Name: "postgres-database", Scope: ScopeMesh}},
|
|
Serves: map[string]map[string]any{"postgres-database": {"port": 5432}}},
|
|
Manifest{Module: "store-two", Version: "1", Provides: []Offer{{Name: "postgres-database", Scope: ScopeMesh}},
|
|
Serves: map[string]map[string]any{"postgres-database": {"port": 5434}}})
|
|
r, err := Resolve(stores, []string{m.Module}, node, w)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r.Account = "op"
|
|
return r
|
|
}
|
|
|
|
func TestAHandedModulesPlanNamesTheProviderServingEachProvision(t *testing.T) {
|
|
c, _ := composeWith(t, consumerOn(t, notifierConsumer(t, true, false), twoStores("anchor")), handingTo("store:5000"))
|
|
want := []PlanProvision{{Provision: "postgres-database", Provider: "anchor", ProviderModule: "store",
|
|
CredentialForm: CredentialFormPair, At: "anchor.internal", From: "anchor", As: ConsumerIdentity("workstation", "notify")}}
|
|
if got := c.Plans["notify"].Provisions; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("the plan names %+v, want %+v", got, want)
|
|
}
|
|
|
|
// **A changed binding changes the plan**: pinned to the other store, the plan names it and not the first.
|
|
c, _ = composeWith(t, consumerOn(t, notifierConsumer(t, true, false), twoStores("home")), handingTo("store:5000"))
|
|
want = []PlanProvision{{Provision: "postgres-database", Provider: "home", ProviderModule: "store-two",
|
|
CredentialForm: CredentialFormPair, At: "home.internal", From: "home", As: ConsumerIdentity("workstation", "notify")}}
|
|
if got := c.Plans["notify"].Provisions; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("rebound, the plan names %+v, want %+v", got, want)
|
|
}
|
|
}
|
|
|
|
// A consumer keeping one provider's credential under several local names is bound once per local name.
|
|
func TestAHandedModulesPlanNamesEachLocalName(t *testing.T) {
|
|
c, _ := composeWith(t, consumerOn(t, notifierConsumer(t, true, true), twoStores("anchor")), handingTo("store:5000"))
|
|
want := []PlanProvision{
|
|
{Provision: "postgres-database", Local: "cold", Provider: "anchor", ProviderModule: "store",
|
|
CredentialForm: CredentialFormPair, At: "anchor.internal", From: "anchor", As: ConsumerIdentity("workstation", "notify")},
|
|
{Provision: "postgres-database", Local: "hot", Provider: "anchor", ProviderModule: "store",
|
|
CredentialForm: CredentialFormPair, At: "anchor.internal", From: "anchor", As: ConsumerIdentity("workstation", "notify")},
|
|
}
|
|
if got := c.Plans["notify"].Provisions; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("the plan names %+v, want %+v", got, want)
|
|
}
|
|
}
|
|
|
|
// A module not moved, or moved where it is not handed, composes as before and has no plan.
|
|
func TestAConsumerNotHandedComposesAsBefore(t *testing.T) {
|
|
_, before := composeWith(t, consumerOn(t, notifierConsumer(t, false, false), twoStores("anchor")),
|
|
handingTo("store:5000"))
|
|
c, got := composeWith(t, consumerOn(t, notifierConsumer(t, true, false), twoStores("anchor")),
|
|
Rendering{Provisioner: "mesh-provisioner-client", ArtifactStore: "store:5000"})
|
|
if got != before {
|
|
t.Fatalf("the moved consumer composes differently where it is not handed:\nbefore: %s\nnow: %s", before, got)
|
|
}
|
|
if c.Plans != nil {
|
|
t.Fatalf("a consumer not handed has plans %+v", c.Plans)
|
|
}
|
|
}
|
|
|
|
// A credential the vault keeps for the holder alone is its provision's one entry, with its provider's module too.
|
|
func TestAHandedModulesIssuedCredentialIsItsProvisionsOneEntry(t *testing.T) {
|
|
r := forgeReader(t, true, true)
|
|
r.Needs = append(r.Needs, Needed{Name: "s3-bucket", From: "ace", For: "bot", Module: "minio"},
|
|
Needed{Name: "openai", From: "personal", For: "bot", ByRecord: true})
|
|
c, _ := composeWith(t, r, handingTo("store:5000"))
|
|
got := c.Plans["bot"].Provisions
|
|
if len(got) != 2 || got[0].Provision != "forge-read" || got[0].Name != "forge-read" ||
|
|
got[0].ProviderModule != "forge" || got[0].Path == "" ||
|
|
!reflect.DeepEqual(got[1], PlanProvision{Provision: "s3-bucket", Provider: "ace", ProviderModule: "minio",
|
|
From: "ace", As: ConsumerIdentity("workstation", "bot")}) {
|
|
t.Fatalf("the plan names %+v: one entry per provision, the issued credential's with its name and file, "+
|
|
"none for a licence", got)
|
|
}
|
|
}
|
|
|
|
// Each entry names the credential form the provider's offer declares (issue 768), from the provider's manifest, so
|
|
// the vault makes a first value in that form for a provider it holds no row of: one of its own secrets shared by
|
|
// every consumer, a login per consumer when the offer says no credential, issued by its software, or given by a
|
|
// person (the offer's own secret issued outside).
|
|
func TestAHandedModulesPlanNamesTheCredentialFormOfItsProvidersOffer(t *testing.T) {
|
|
storeOffering := func(credential string) Manifest {
|
|
raw := `{"module":"store","version":"1","provides":[{"name":"postgres-database","scope":"mesh"` +
|
|
credential + `}],"serves":{"postgres-database":{"port":5432}}`
|
|
if strings.Contains(credential, `"own"`) {
|
|
raw += `,"own-secrets":{"admin":{"path":"/var/lib/store/admin","taken":"at-start"},` +
|
|
`"token":{"path":"/var/lib/store/token","taken":"at-start","issued-by":"outside"}}`
|
|
}
|
|
return mustParse(t, raw+`}`)
|
|
}
|
|
for credential, want := range map[string]string{
|
|
``: CredentialFormPair,
|
|
`,"credential":{"own":"admin"}`: CredentialFormShared,
|
|
`,"credential":{"own":"token"}`: CredentialFormGiven,
|
|
`,"credential":{"issued-by":"outside"}`: CredentialFormGiven,
|
|
`,"credential":{"issued-by":"software","tool":"store_issued_token"}`: CredentialFormIssued,
|
|
} {
|
|
node := workstation()
|
|
node.At = "workstation.internal"
|
|
consumer := notifierConsumer(t, true, false)
|
|
r, err := Resolve(shelf(consumer, storeOffering(credential)), []string{consumer.Module}, node, twoStores("anchor"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r.Account = "op"
|
|
c, _ := composeWith(t, r, handingTo("store:5000"))
|
|
got := c.Plans["notify"].Provisions
|
|
if len(got) != 1 || got[0].CredentialForm != want {
|
|
t.Errorf("an offer with %q: the plan names %+v, want the form %q", credential, got, want)
|
|
}
|
|
}
|
|
if got := (Manifest{Module: "x"}).CredentialFormOf("postgres-database"); got != "" {
|
|
t.Errorf("a module offering nothing has the credential form %q", got)
|
|
}
|
|
}
|
|
|
|
// A consumer's plan entry carries what its manifest contributes to that provision (issue 776), so the provider's
|
|
// helper learns a database's name from the bus; a consumer contributing nothing has the entry it had.
|
|
func TestAHandedModulesPlanEntryCarriesWhatItContributesToItsProvider(t *testing.T) {
|
|
m := notifierConsumer(t, true, false)
|
|
m.Contributes = map[string]map[string]any{"postgres-database": {"name": "nextcloud"}}
|
|
c, _ := composeWith(t, consumerOn(t, m, twoStores("anchor")), handingTo("store:5000"))
|
|
want := []PlanProvision{{Provision: "postgres-database", Provider: "anchor", ProviderModule: "store",
|
|
CredentialForm: CredentialFormPair, Contributes: map[string]any{"name": "nextcloud"}, At: "anchor.internal", From: "anchor", As: ConsumerIdentity("workstation", "notify"),
|
|
// What the binding file says as `${bound:postgres-database:name}` (issue 792).
|
|
Names: map[string]string{"name": "nextcloud"}}}
|
|
if got := c.Plans["notify"].Provisions; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("the plan names %+v, want %+v", got, want)
|
|
}
|
|
|
|
m = notifierConsumer(t, true, false)
|
|
c, _ = composeWith(t, consumerOn(t, m, twoStores("anchor")), handingTo("store:5000"))
|
|
raw, err := json.Marshal(c.Plans["notify"].Provisions)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const before = `[{"provision":"postgres-database","provider":"anchor","provider-module":"store",` +
|
|
`"credential-form":"pair","at":"anchor.internal","from":"anchor","as":"mesh_workstation_notify"}]`
|
|
if string(raw) != before {
|
|
t.Fatalf("a consumer contributing nothing has the entry %s, want %s", raw, before)
|
|
}
|
|
}
|
|
|
|
// A consumer's plan entry carries the names the mesh composed for it through that provision (issue 792): what its
|
|
// binding file names as `${bound:<provision>:name|internal-name|name-<local>|internal-name-<local>}`, so a moved
|
|
// provider's helper copies them into the consumer's entry and the client fills the same text. A consumer
|
|
// contributing no label has the entry it had.
|
|
func TestAHandedModulesPlanEntryCarriesTheNamesComposedForIt(t *testing.T) {
|
|
m := notifierConsumer(t, true, false)
|
|
m.ContributesMany = map[string]map[string]map[string]any{
|
|
"postgres-database": {"web": {"label": "git"}, "api": {"label": "git-api"}}}
|
|
r := consumerOn(t, m, twoStores("anchor"))
|
|
r.PublicDomain = "example.org"
|
|
c, _ := composeWith(t, r, handingTo("store:5000"))
|
|
got := c.Plans["notify"].Provisions
|
|
if len(got) != 1 {
|
|
t.Fatalf("the plan names %+v", got)
|
|
}
|
|
want := map[string]string{"name-web": "git.example.org", "name-api": "git-api.example.org"}
|
|
own, err := r.ownNames(m, "postgres-database", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file := map[string]string{}
|
|
withOwnNames(file, own)
|
|
for k, v := range file {
|
|
if strings.HasPrefix(k, "internal-") {
|
|
want[k] = v
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(got[0].Names, want) {
|
|
t.Fatalf("the entry carries %v, the binding file names %v", got[0].Names, want)
|
|
}
|
|
}
|
|
|
|
// The flag a provides entry carries, `"answered-by": "helper"`, is read and written back, and refused with any other
|
|
// word (issue 792). It is no part of moving the module: the flag alone hands none of the module's files, and the
|
|
// module composes byte for byte as it did without it.
|
|
func TestAnOfferAnsweredByTheHelperIsReadAndHandsNoFile(t *testing.T) {
|
|
with := strings.Replace(handedNotifier, `"version":"1",`,
|
|
`"version":"1","provides":[{"name":"route","scope":"mesh","answered-by":"helper"}],`, 1)
|
|
without := strings.Replace(handedNotifier, `"version":"1",`,
|
|
`"version":"1","provides":[{"name":"route","scope":"mesh"}],`, 1)
|
|
flagged := held(t, with, builtNotifier())
|
|
if flagged.Moved || len(flagged.Provides) != 1 || flagged.Provides[0].AnsweredBy != AnsweredByHelper {
|
|
t.Fatalf("the flag was read as %+v (moved %v)", flagged.Provides, flagged.Moved)
|
|
}
|
|
if flagged.Handed != nil {
|
|
t.Fatalf("the flag alone kept what a moved module's build keeps: %+v", flagged.Handed)
|
|
}
|
|
raw, err := json.Marshal(flagged.Provides[0])
|
|
if err != nil || !strings.Contains(string(raw), `"answered-by":"helper"`) {
|
|
t.Fatalf("written back as %s: %v", raw, err)
|
|
}
|
|
if _, err := ParseManifest([]byte(strings.Replace(with, `"helper"`, `"harness"`, 1))); err == nil {
|
|
t.Fatal("an unknown answerer was accepted")
|
|
}
|
|
c1, before := composeWith(t, notifierOn(t, held(t, without, builtNotifier())), handingTo("store:5000"))
|
|
c2, got := composeWith(t, notifierOn(t, flagged), handingTo("store:5000"))
|
|
if got != before || len(c2.Filled) > 0 || c2.Plans != nil || c1.Plans != nil {
|
|
t.Fatalf("the flag changed what is composed or handed:\nbefore: %s\nnow: %s\n%v %v", before, got, c2.Filled, c2.Plans)
|
|
}
|
|
}
|