mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
160 lines
6.6 KiB
Go
160 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/provider"
|
|
"git.novox.be/novox/mesh-sdk/go/provisions"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
// Issue 923: route-proxy serves a consumer of its route the names the controller composed, and the consumer's client
|
|
// fills gitea's origins and keycloak's hostname.env from them. The route-proxy provider is run here as its module
|
|
// runs it (the SDK helper, `route` offered with no credential and nothing served), over a plan entry shaped as the
|
|
// controller writes it (mesh-controller handed.go PlanProvision: `at`, `from`, `as` and `names`), and the entry it
|
|
// writes is the one the client reads.
|
|
|
|
// memBus is the provider helper's bus over in-memory buckets.
|
|
type memBus struct {
|
|
mu sync.Mutex
|
|
buckets map[string]map[string][]byte
|
|
}
|
|
|
|
func (m *memBus) Watch(_ context.Context, bucket string, handle func(string, []byte)) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for k, v := range m.buckets[bucket] {
|
|
handle(k, v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *memBus) Put(_ context.Context, bucket, key string, value []byte) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.buckets[bucket] == nil {
|
|
m.buckets[bucket] = map[string][]byte{}
|
|
}
|
|
m.buckets[bucket][key] = append([]byte(nil), value...)
|
|
return nil
|
|
}
|
|
|
|
func (m *memBus) Delete(_ context.Context, bucket, key string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.buckets[bucket], key)
|
|
return nil
|
|
}
|
|
|
|
// routeEntryFor is the entry route-proxy's helper writes for module's plan entry on the route provision, the plan
|
|
// as the seat mesh-provisioner keeps it (ADR 0570 clause 2).
|
|
func routeEntryFor(t *testing.T, module, planEntry string) string {
|
|
t.Helper()
|
|
bus := &memBus{buckets: map[string]map[string][]byte{provisions.PlansBucket: {
|
|
node + "." + module: []byte(`{"v":1,"node":"` + node + `","module":"` + module + `","plan":{"provisions":[` +
|
|
planEntry + `]}}`)}, provisions.ProvisionsBucket: {}}}
|
|
// The key the seat composed for that entry, provided-for: asked of route-proxy (ADR 0570 clause 3).
|
|
var b struct {
|
|
Provision string `json:"provision"`
|
|
Local string `json:"local"`
|
|
}
|
|
if err := json.Unmarshal([]byte(planEntry), &b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pr := provisions.Provision{Provision: b.Provision, Local: b.Local, State: provisions.Asked,
|
|
Consumer: provisions.End{Node: node, Module: module}, Provider: provisions.End{Node: "novox", Module: "route-proxy"}}
|
|
key, err := pr.Key()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := provisions.Encode(pr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bus.buckets[provisions.ProvisionsBucket][key] = raw
|
|
p := provider.New(provider.Options{Node: "novox", Module: "route-proxy", Bus: bus, Logf: t.Logf,
|
|
Offers: map[string]provider.Offer{"route": {Credential: provider.NoCredential, Serves: map[string]any{}}}})
|
|
out, err := p.Pass(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(out) != 1 || out[0].Status != provider.Answered {
|
|
t.Fatalf("the provider did %+v", out)
|
|
}
|
|
return string(bus.buckets[provider.BucketOf("route-proxy")][node+"."+module+".route"])
|
|
}
|
|
|
|
func routeRun(t *testing.T, module, id, path, templatePath, entryKey, firstEntry string) (*engine, *aBus, *reports) {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(templatePath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b := startBus(t)
|
|
registryOf679(t, b)
|
|
startVault(t, b)
|
|
heard := b.listen(t)
|
|
e := startEngine(t, map[string]string{path: "owned"})
|
|
tpl, _ := templates(t, map[string]string{id: string(raw)})
|
|
b.assign(t, module, Plan{Templates: tpl, Files: []PlannedFile{{ID: id, Path: path, Template: id,
|
|
OwnerAtRuntime: "owned"}},
|
|
Provisions: []PlannedProvision{{Provision: "route", Provider: "novox", ProviderModule: "route-proxy"}}})
|
|
kv, err := b.domain.CreateKeyValue(context.Background(), jetstream.KeyValueConfig{Bucket: "route-proxy_bound"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b.kv["route-proxy_bound"] = kv
|
|
if firstEntry != "" {
|
|
b.put(t, "route-proxy_bound", node+"."+entryKey, firstEntry)
|
|
}
|
|
aClient(t, b, e, t.TempDir())
|
|
return e, b, heard
|
|
}
|
|
|
|
// gitea's plan entry for its `web` route, as the controller writes it for a node whose public domain is example.org.
|
|
const giteaRoutePlanEntry = `{"provision":"route","provider":"novox","provider-module":"route-proxy","credential-form":"pair",` +
|
|
`"at":"novox.internal","from":"novox","as":"mesh_g14_gitea",` +
|
|
`"names":{"name-web":"git.example.org","internal-name-web":"git.novox.internal",` +
|
|
`"name-internal-api-refused":"git.example.org","internal-name-internal-api-refused":"git.novox.internal"}}`
|
|
|
|
const keycloakRoutePlanEntry = `{"provision":"route","provider":"novox","provider-module":"route-proxy","credential-form":"pair",` +
|
|
`"at":"novox.internal","from":"novox","as":"mesh_g14_keycloak",` +
|
|
`"names":{"name":"keycloak.example.org","internal-name":"keycloak.novox.internal"}}`
|
|
|
|
func TestNatsGiteasOriginsAreFilledFromRouteProxysEntryForThePlanEntryTheControllerWrites(t *testing.T) {
|
|
e, _, _ := routeRun(t, "gitea", "origins", "/etc/gitea/origins", "../../../gitea/files/origins", "gitea.route",
|
|
routeEntryFor(t, "gitea", giteaRoutePlanEntry))
|
|
eventually(t, "origins is filled with the public name", func() bool {
|
|
return e.read(t, "/etc/gitea/origins") == "https://git.example.org\n"
|
|
})
|
|
}
|
|
|
|
func TestNatsKeycloaksHostnameIsFilledFromRouteProxysEntryForThePlanEntryTheControllerWrites(t *testing.T) {
|
|
e, _, _ := routeRun(t, "keycloak", "hostname", "/etc/keycloak/hostname.env", "../../../keycloak/files/hostname.env",
|
|
"keycloak.route", routeEntryFor(t, "keycloak", keycloakRoutePlanEntry))
|
|
eventually(t, "hostname.env is filled with the public name", func() bool {
|
|
return e.read(t, "/etc/keycloak/hostname.env") == "KC_HOSTNAME=https://keycloak.example.org\n"
|
|
})
|
|
}
|
|
|
|
// A module that failed on a missing name is filled once the provider's entry carries it: the entry first held no
|
|
// names (as the provider wrote it before the controller composed them), then the controller's plan entry arrives.
|
|
func TestNatsAModuleThatFailedOnAMissingNameIsFilledWhenTheNameArrives(t *testing.T) {
|
|
bare := routeEntryFor(t, "gitea", `{"provision":"route","provider":"novox","provider-module":"route-proxy",`+
|
|
`"as":"mesh_g14_gitea"}`)
|
|
e, b, heard := routeRun(t, "gitea", "origins", "/etc/gitea/origins", "../../../gitea/files/origins", "gitea.route", bare)
|
|
eventually(t, "the module fails for the missing name", func() bool {
|
|
r, _ := heard.last("gitea")
|
|
return r.Status == StatusFailed && strings.Contains(r.Reason, `no "name-web"`)
|
|
})
|
|
b.put(t, "route-proxy_bound", node+".gitea.route", routeEntryFor(t, "gitea", giteaRoutePlanEntry))
|
|
eventually(t, "origins is filled once the name arrives", func() bool {
|
|
return e.read(t, "/etc/gitea/origins") == "https://git.example.org\n"
|
|
})
|
|
}
|