From bfb728730f09f67b8afbed931ddc973b759836a1 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 31 Oct 2026 00:27:11 +0100 Subject: [PATCH 1/2] Answer route-proxy's routes from the seat mesh-provisioner's plans and keys, not the controller's assignments, so a route the seat keys for a module not handed is marked provided and its new builds are no longer withheld (issue 774) --- .../cmd/route-provider/provider_test.go | 63 ++++++++++++++----- modules/route-proxy/go.mod | 2 +- modules/route-proxy/go.sum | 4 +- modules/route-proxy/module.json | 3 +- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/modules/route-proxy/cmd/route-provider/provider_test.go b/modules/route-proxy/cmd/route-provider/provider_test.go index 01b58bfc..1c36b220 100644 --- a/modules/route-proxy/cmd/route-provider/provider_test.go +++ b/modules/route-proxy/cmd/route-provider/provider_test.go @@ -9,7 +9,9 @@ import ( "sync" "testing" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/provider" + "git.novox.be/novox/mesh-sdk/go/provisions" ) // bucketBus is the helper's bus over in-memory buckets. @@ -44,33 +46,60 @@ func (b *bucketBus) Delete(_ context.Context, bucket, key string) error { return nil } -// A consumer of a route is answered with the names its plan's entry carries, and nothing of a credential (issue 792): -// the entry's values are the controller's composition, copied unchanged, and no vault is asked. +// A consumer of a route is answered with the names its plan's entry carries, and nothing of a credential (issue 792), +// from the seat mesh-provisioner's plans and keys (novox/hq ADR 0570 clause 2), never the controller's assignments: a +// module not handed whose route the seat keys (grafana, issue 774) has a plan of provisions alone, and a helper that +// read only the assignments left its key asked for good, so the assigner withheld every new build of 38 such modules. func TestAConsumerOfARouteIsToldItsNamesFromItsPlanEntry(t *testing.T) { bus := &bucketBus{buckets: map[string]map[string][]byte{}} - plan, _ := json.Marshal(map[string]any{"node": "g14", "module": "gitea", "provisioning": map[string]any{ - "provisions": []any{map[string]any{"provision": ProvisionName, "provider": "novox", - "provider-module": ModuleName, "at": "10.7.0.1", "from": "novox", "as": "mesh_g14_gitea", - "names": map[string]any{"name": "git.example.org", "internal-name": "git.novox.internal", - "name-web": "git.example.org"}}}}}) - if err := bus.Put(context.Background(), provider.AssignmentsBucket, "g14.gitea", plan); err != nil { - t.Fatal(err) + for module, names := range map[string]map[string]string{ + "gitea": {"name": "git.example.org", "internal-name": "git.novox.internal", "name-web": "git.example.org"}, + "grafana": {"name": "grafana.example.org"}, + } { + plan, err := provisions.EncodePlan(provisions.Plan{Node: "g14", Module: module, Plan: catalogue.Plan{ + Provisions: []catalogue.PlanProvision{{Provision: ProvisionName, Provider: "novox", ProviderModule: ModuleName, + At: "10.7.0.1", From: "novox", As: "mesh_g14_" + module, Names: names}}}}) + if err != nil { + t.Fatal(err) + } + key := "g14." + module + if err := bus.Put(context.Background(), provisions.PlansBucket, key, plan); err != nil { + t.Fatal(err) + } + asked, err := json.Marshal(provisions.Provision{V: provisions.Version, Provision: ProvisionName, + Consumer: provisions.End{Node: "g14", Module: module}, Provider: provisions.End{Node: "novox", Module: ModuleName}, + State: provisions.Asked}) + if err != nil { + t.Fatal(err) + } + if err := bus.Put(context.Background(), provisions.ProvisionsBucket, key+"."+ProvisionName, asked); err != nil { + t.Fatal(err) + } } p := newProvider("novox", bus, t.Logf) out, err := p.Pass(context.Background()) if err != nil { t.Fatal(err) } - if len(out) != 1 || out[0].Status != provider.Answered { + if len(out) != 2 || out[0].Status != provider.Answered || out[1].Status != provider.Answered { t.Fatalf("the pass did %+v", out) } - var e provider.Entry - if err := json.Unmarshal(bus.buckets[provider.BucketOf(ModuleName)]["g14.gitea.route"], &e); err != nil { - t.Fatal(err) - } - if e.Values["name-web"] != "git.example.org" || e.Values["name"] != "git.example.org" || - e.Values["internal-name"] != "git.novox.internal" || e.VaultName != "" || e.User != "" || e.Refused != "" { - t.Fatalf("the entry is %+v", e) + for key, want := range map[string]map[string]string{ + "g14.gitea.route": {"name-web": "git.example.org", "name": "git.example.org", "internal-name": "git.novox.internal"}, + "g14.grafana.route": {"name": "grafana.example.org"}, + } { + var e provider.Entry + if err := json.Unmarshal(bus.buckets[provider.BucketOf(ModuleName)][key], &e); err != nil { + t.Fatalf("%s: %v", key, err) + } + for k, v := range want { + if e.Values[k] != v { + t.Errorf("%s says %s %v, want %s", key, k, e.Values[k], v) + } + } + if e.State != provisions.Provided || e.VaultName != "" || e.User != "" || e.Refused != "" { + t.Errorf("the entry %s is %+v", key, e) + } } } diff --git a/modules/route-proxy/go.mod b/modules/route-proxy/go.mod index f33ea3f1..c826c270 100644 --- a/modules/route-proxy/go.mod +++ b/modules/route-proxy/go.mod @@ -3,7 +3,7 @@ module route-proxy go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261022190740-104533e8c0c9 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030231858-b9ef1e4fcb68 github.com/nats-io/nats.go v1.54.0 golang.org/x/crypto v0.57.0 ) diff --git a/modules/route-proxy/go.sum b/modules/route-proxy/go.sum index 47599506..286ed6eb 100644 --- a/modules/route-proxy/go.sum +++ b/modules/route-proxy/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261022190740-104533e8c0c9 h1:Ufmd2cYEXICQ817IGflccP9bexABE4WhIO49HPbaoSo= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261022190740-104533e8c0c9/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030231858-b9ef1e4fcb68 h1:2rMJX0RMIBaN5NDIaEZVwbj5MFism1O3bRayEz+WlOs= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030231858-b9ef1e4fcb68/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/route-proxy/module.json b/modules/route-proxy/module.json index 620a733d..1a42825f 100644 --- a/modules/route-proxy/module.json +++ b/modules/route-proxy/module.json @@ -268,6 +268,7 @@ ] }, "reads": [ - "mesh-controller.assignments" + "mesh-provisioner.plans", + "mesh-provisioner.provisions" ] } -- 2.54.0 From 62c05c681cfb333cc4c378321ae5c1e107399049 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 31 Oct 2026 00:32:26 +0100 Subject: [PATCH 2/2] Start route-proxy's provider helper only once Serve holds the runtime's channel, so its first watch is never refused and the helper never stops until a restart (issue 774, review) --- modules/route-proxy/cmd/route-provider/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/route-proxy/cmd/route-provider/main.go b/modules/route-proxy/cmd/route-provider/main.go index 8887ed6f..a00a7090 100644 --- a/modules/route-proxy/cmd/route-provider/main.go +++ b/modules/route-proxy/cmd/route-provider/main.go @@ -28,6 +28,9 @@ func main() { } else { p := newProvider(node, provider.StateBus(ModuleName), say) go func() { + // The helper's watches go through the runtime's channel, which Serve holds: one asked before it does is + // refused ("before Serve"), Run returns and the helper stays stopped until the process restarts. + <-stdio.Served() if err := p.Run(context.Background()); err != nil { say("the provider helper stopped: %v", err) } -- 2.54.0