Files
mesh-controller/internal/broker/prov_applied_live_test.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
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).
2026-10-15 21:27:33 +00:00

136 lines
4.9 KiB
Go

package broker
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
"golang.org/x/crypto/bcrypt"
)
// **On a real server, as composed** (design 57 P6, issue 672, ADR 0362): a machine's provisioner client's word on a
// module's files is kept by the mesh's own stream, newest per module, and the controller reads it by subject; the
// client cannot say it of another machine, and nothing but a machine's own client is granted to say it.
func TestTheProvisionersReportIsKeptAndReadByTheController(t *testing.T) {
hash, err := bcrypt.GenerateFromPassword([]byte("pw"), bcrypt.MinCost)
if err != nil {
t.Fatal(err)
}
accounts, err := ComposeAccounts([]Principal{
{Kind: KindController, PasswordHash: string(hash)},
{Kind: KindProvisioner, Node: "g14", Module: "mesh-provisioner-client", PasswordHash: string(hash)},
{Kind: KindProvisioner, Node: "shanks", Module: "mesh-provisioner-client", PasswordHash: string(hash)},
})
if err != nil {
t.Fatal(err)
}
conf := filepath.Join(t.TempDir(), "bus.conf")
if err := os.WriteFile(conf, []byte("listen: 127.0.0.1:-1\njetstream { store_dir: "+
`"`+t.TempDir()+`"`+" }\n"+accounts), 0o600); err != nil {
t.Fatal(err)
}
opts, err := server.ProcessConfigFile(conf)
if err != nil {
t.Fatalf("the composed accounts do not parse: %v", err)
}
opts.NoLog, opts.NoSigs = true, true
s, err := server.NewServer(opts)
if err != nil {
t.Fatal(err)
}
go s.Start()
if !s.ReadyForConnections(10 * time.Second) {
t.Fatal("the bus did not come up")
}
defer s.Shutdown()
refusals := make(chan error, 16)
connect := func(user string) *nats.Conn {
t.Helper()
nc, err := nats.Connect(s.ClientURL(), nats.UserInfo(user, "pw"), nats.CustomInboxPrefix("_INBOX."+user),
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) { refusals <- err }))
if err != nil {
t.Fatal(err)
}
t.Cleanup(nc.Close)
return nc
}
control := OnConn(connect("controller"))
if err := AssertMeshStreams(control); err != nil {
t.Fatalf("the mesh's streams cannot be asserted: %v", err)
}
g14 := connect("provisioner.g14")
for _, said := range []string{`{"status":"waiting"}`, `{"status":"applied","templates":"sha256:new"}`} {
if err := g14.Publish(ProvisionerAppliedSubject("g14", "dunst"), []byte(said)); err != nil {
t.Fatal(err)
}
}
if err := g14.Flush(); err != nil {
t.Fatal(err)
}
// Another machine's client says nothing of g14: refused by the server, and nothing kept.
shanks := connect("provisioner.shanks")
if err := shanks.Publish(ProvisionerAppliedSubject("g14", "dunst"), []byte(`{"status":"failed"}`)); err != nil {
t.Fatal(err)
}
_ = shanks.Flush()
select {
case err := <-refusals:
if !errors.Is(err, nats.ErrPermissionViolation) {
t.Fatalf("the server said %v, want a permissions violation", err)
}
case <-time.After(2 * time.Second):
t.Fatal("the server did not refuse shanks's client a report on g14")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
js, err := control.Conn().JetStream(nats.Context(ctx))
if err != nil {
t.Fatal(err)
}
// A core publish is kept by the stream once the server has stored it: read until it has, a few seconds at most.
var got []byte
var said bool
for deadline := time.Now().Add(5 * time.Second); ; {
got, said, err = LastProvApplied(js, "g14", "dunst")
if (err == nil && said && strings.Contains(string(got), "applied")) || time.Now().After(deadline) {
break
}
time.Sleep(20 * time.Millisecond)
}
if err != nil || !said || string(got) != `{"status":"applied","templates":"sha256:new"}` {
t.Fatalf("the controller read %q %v %v, want g14's newest report", got, said, err)
}
if _, said, err := LastProvApplied(js, "shanks", "dunst"); err != nil || said {
t.Fatalf("a module nothing was said of reads as said: %v %v", said, err)
}
all, err := ProvAppliedReports(js)
if err != nil || len(all) != 1 || string(all["prov.applied.g14.dunst"]) != `{"status":"applied","templates":"sha256:new"}` {
t.Fatalf("every report, newest per module: %q %v", all, err)
}
info, err := js.StreamInfo(ProvAppliedStream)
if err != nil || info.State.Msgs != 1 {
t.Fatalf("the stream keeps the newest report per module only: %+v %v", info, err)
}
// The writers table: a machine's own client alone is granted to say it.
if err := CheckWriters(Principal{Kind: KindProvisioner, Node: "g14"}, []string{"prov.applied.g14.>"}); err != nil {
t.Fatalf("g14's client may not say its own: %v", err)
}
for _, p := range []Principal{{Kind: KindProvisioner, Node: "shanks"}, {Kind: KindNode, Node: "g14"},
{Kind: KindModule, Node: "g14", Module: "dunst"}} {
err := CheckWriters(p, []string{"prov.applied.g14.>"})
if err == nil || !strings.Contains(err.Error(), "a module's files placed on a machine") {
t.Errorf("%s may say g14's provisioner's report: %v", p.Username(), err)
}
}
}