Fix 9.7e: route-proxy names the seats it reads (seat:mesh-provisioner, seat:mesh-module-assigner), no module
Builds: design 47 G A-116 (§636, §616) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -12,7 +12,10 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient/provider"
|
||||
meshprovisioner "git.novox.be/novox/mesh-contracts/provisioning/go/seats/meshprovisioner"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/busretry"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
|
||||
"route-proxy/contract"
|
||||
)
|
||||
@@ -26,11 +29,19 @@ func main() {
|
||||
if node == "" {
|
||||
say("MESH_NODE is not set; answering no consumers")
|
||||
} else {
|
||||
p := newProvider(node, provider.StateBus(ModuleName, provisionerModule), 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()
|
||||
// The holder of the seat mesh-provisioner is read from the seat that places modules, not named here (A-116).
|
||||
holder, err := busretry.DoRepeatable("who holds "+meshprovisioner.Seat, func() (string, error) {
|
||||
return seatlist.HolderOf(stdio.Ask, meshprovisioner.Seat)
|
||||
}, nil)
|
||||
if err != nil {
|
||||
say("the provider helper cannot start: %v", err)
|
||||
return
|
||||
}
|
||||
p := newProvider(node, holder, provider.StateBus(ModuleName, holder), say)
|
||||
if err := p.Run(context.Background()); err != nil {
|
||||
say("the provider helper stopped: %v", err)
|
||||
}
|
||||
@@ -43,7 +54,3 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// provisionerModule is the holder of the seat mesh-provisioner, named from this module's manifest (its `reads` of plans and
|
||||
// provisions; TestTheProvisionerIsTheManifests).
|
||||
const provisionerModule = "mesh-provisioner"
|
||||
|
||||
@@ -88,8 +88,8 @@ func TestRouteProxyKeepsItsPublicAccountDirectory(t *testing.T) {
|
||||
// seat mesh-provisioner's plans alone (issue 774, research 072 row 34): it reads them, and receives no file.
|
||||
func TestRouteProxyRunsItsMountedProgramOnTheTrustImage(t *testing.T) {
|
||||
m := ownManifest(t)
|
||||
if !slices.Contains(m.Reads, "mesh-provisioner.plans") {
|
||||
t.Errorf("route-proxy does not read mesh-provisioner.plans, where its routes are: %v", m.Reads)
|
||||
if !slices.Contains(m.Reads, "seat:mesh-provisioner.plans") {
|
||||
t.Errorf("route-proxy does not read seat:mesh-provisioner.plans, where its routes are: %v", m.Reads)
|
||||
}
|
||||
if len(m.Receives) > 0 {
|
||||
t.Errorf("route-proxy receives %v: a route is served from the seat's plans, never a received file", m.Receives)
|
||||
|
||||
@@ -22,12 +22,13 @@ const (
|
||||
ModuleName = contract.Module
|
||||
)
|
||||
|
||||
// newProvider is route-proxy answering its consumers over bus on node: the route, served as the manifest serves it
|
||||
// newProvider is route-proxy answering its consumers over bus on node, the plans and provisions being those of provisioner, the
|
||||
// module holding the seat mesh-provisioner (read by seatlist.HolderOf; design 47 G A-116): the route, served as the manifest serves it
|
||||
// (nothing of its own), with no credential, and nothing to make or retire for a consumer.
|
||||
func newProvider(node string, bus provider.Bus, logf func(string, ...any)) *provider.Provider {
|
||||
func newProvider(node, provisioner string, bus provider.Bus, logf func(string, ...any)) *provider.Provider {
|
||||
return provider.New(provider.Options{
|
||||
Node: node,
|
||||
Module: ModuleName, Provisioner: provisionerModule,
|
||||
Module: ModuleName, Provisioner: provisioner,
|
||||
Offers: map[string]provider.Offer{ProvisionName: {
|
||||
Credential: provider.NoCredential,
|
||||
Serves: map[string]any{},
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestAConsumerOfARouteIsToldItsNamesFromItsPlanEntry(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key := "g14." + module
|
||||
if err := bus.Put(context.Background(), meshprovisionerclient.PlansBucketOf(provisionerModule), key, plan); err != nil {
|
||||
if err := bus.Put(context.Background(), meshprovisionerclient.PlansBucketOf(theProvisioner), key, plan); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asked, err := json.Marshal(meshprovisionerclient.Provision{V: meshprovisionerclient.Version, Provision: ProvisionName,
|
||||
@@ -72,11 +72,11 @@ func TestAConsumerOfARouteIsToldItsNamesFromItsPlanEntry(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := bus.Put(context.Background(), meshprovisionerclient.ProvisionsBucketOf(provisionerModule), key+"."+ProvisionName, asked); err != nil {
|
||||
if err := bus.Put(context.Background(), meshprovisionerclient.ProvisionsBucketOf(theProvisioner), key+"."+ProvisionName, asked); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
p := newProvider("novox", bus, t.Logf)
|
||||
p := newProvider("novox", theProvisioner, bus, t.Logf)
|
||||
out, err := p.Pass(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -127,7 +127,7 @@ func TestRouteProxyIsNotMovedAndKeepsItsOwnFiles(t *testing.T) {
|
||||
t.Fatalf("route is not said to be answered by the helper: %v", provides)
|
||||
}
|
||||
// Its states and its tools are what it says on the bus, not what it runs or writes (issue 774).
|
||||
for _, k := range []string{"build", "provides", "state", "reads", "tools", "replaces"} {
|
||||
for _, k := range []string{"build", "provides", "state", "reads", "invokes", "tools", "replaces"} {
|
||||
delete(m, k)
|
||||
}
|
||||
// Refreshed on purpose for the events its contract names (`emits`: route.served, route.withdrawn, route.refused), and for the
|
||||
@@ -158,3 +158,6 @@ func TestRouteProxyIsNotMovedAndKeepsItsOwnFiles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// theProvisioner is the module a test has hold the seat mesh-provisioner: the code reads it, and names none.
|
||||
const theProvisioner = "a-provisioner"
|
||||
|
||||
@@ -5,24 +5,31 @@ import (
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/provisioning/go/seats/meshprovisioner"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
)
|
||||
|
||||
// The module holding the seat mesh-provisioner, which this provider reads the plans and provisions of, is the one its manifest's
|
||||
// `reads` name: no client names a holder (novox/hq ADR 0625 §4).
|
||||
// The seat mesh-provisioner, whose plans and provisions this provider reads, is named by the seat in its manifest's `reads`
|
||||
// (`seat:<seat>.<state>`), and the manifest invokes the verb that says who holds it: no module is named (design 47 G A-116).
|
||||
func TestTheProvisionerIsTheManifests(t *testing.T) {
|
||||
raw, err := os.ReadFile("../../module.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m struct {
|
||||
Reads []string `json:"reads"`
|
||||
Reads []string `json:"reads"`
|
||||
Invokes []string `json:"invokes"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, state := range []string{"plans", "provisions"} {
|
||||
if !slices.Contains(m.Reads, provisionerModule+"."+state) {
|
||||
t.Errorf("the provider reads %s.%s, its manifest's reads are %v", provisionerModule, state, m.Reads)
|
||||
for _, state := range []string{meshprovisioner.StatePlans, meshprovisioner.StateProvisions} {
|
||||
if want := "seat:" + meshprovisioner.Seat + "." + state; !slices.Contains(m.Reads, want) {
|
||||
t.Errorf("the provider reads %s, its manifest's reads are %v", want, m.Reads)
|
||||
}
|
||||
}
|
||||
if want := seatlist.HolderKey(); !slices.Contains(m.Invokes, want) {
|
||||
t.Errorf("the provider asks %s for the holder, its manifest invokes %v", want, m.Invokes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,39 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
meshprovisioner "git.novox.be/novox/mesh-contracts/provisioning/go/seats/meshprovisioner"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
|
||||
"route-proxy/contract"
|
||||
)
|
||||
|
||||
// assignmentsRead is the seat mesh-module-assigner's state assignments as this module's manifest reads it (`reads`); no seat's
|
||||
// client names a holder (novox/hq ADR 0625 §4), so the name is kept here and held to the manifest by TestTheReadIsTheManifests.
|
||||
const assignmentsRead = "mesh-delivery-assigner.assignments"
|
||||
// The states this proxy reads are those of the holders of two seats, named in the manifest's `reads` by the seat
|
||||
// (`seat:<seat>.<state>`, design 47 G A-116): assignmentsRead the seat mesh-module-assigner's assignments, plansRead the seat
|
||||
// mesh-provisioner's plans. The holder of each is read where the state is read (holderRead), so no module is named here;
|
||||
// TestTheReadIsTheManifests holds both to the manifest.
|
||||
const (
|
||||
assignmentsRead = "seat:" + meshmoduleassigner.Seat + "." + meshmoduleassigner.StateAssignments
|
||||
plansRead = "seat:" + meshprovisioner.Seat + "." + meshprovisioner.StatePlans
|
||||
)
|
||||
|
||||
// plansRead is the seat mesh-provisioner's state plans as this module's manifest reads it (`reads`; TestTheReadIsTheManifests).
|
||||
const plansRead = "mesh-provisioner.plans"
|
||||
// holderRead is a read in the seat form as the bus names it, `<holder>.<state>`: the module on record as the holder of the seat,
|
||||
// read by seatlist.HolderOf through call.
|
||||
func holderRead(call seatlist.HolderCaller, read string) (string, error) {
|
||||
seat, state, ok, err := catalogue.SeatRead(read)
|
||||
if err != nil || !ok {
|
||||
return "", fmt.Errorf("%q is not a read of a seat's state: %v", read, err)
|
||||
}
|
||||
holder, err := seatlist.HolderOf(call, seat)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return holder + "." + state, nil
|
||||
}
|
||||
|
||||
// keptStates is every state route-proxy keeps, as both its halves write them: the routes and the refusals this
|
||||
// proxy writes, readable by others, and the bound entries the provider helper writes.
|
||||
@@ -80,10 +100,17 @@ func followOnce(path string, held *table) error {
|
||||
}
|
||||
}
|
||||
// Who is inside first, so the first table is served to the machines it is meant for.
|
||||
if err := followInside(conn.Read(assignmentsRead), held); err != nil {
|
||||
log.Printf("cannot follow %s, so an internal name is served to this machine alone: %v", assignmentsRead, err)
|
||||
if inside, err := holderRead(conn.CallKept, assignmentsRead); err != nil {
|
||||
log.Printf("cannot read who holds %s, so an internal name is served to this machine alone: %v", assignmentsRead, err)
|
||||
} else if err := followInside(conn.Read(inside), held); err != nil {
|
||||
log.Printf("cannot follow %s, so an internal name is served to this machine alone: %v", inside, err)
|
||||
}
|
||||
if err := followPlans(conn.Read(plansRead), newPlanned(account.Node, say), held, say); err != nil {
|
||||
plans, err := holderRead(conn.CallKept, plansRead)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("reading who holds %s: %w", plansRead, err)
|
||||
}
|
||||
if err := followPlans(conn.Read(plans), newPlanned(account.Node, say), held, say); err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
@@ -110,7 +137,7 @@ func followPlans(plans planWatch, given *planned, held *table, say func(string,
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("watching %s: %w", plansRead, err)
|
||||
return fmt.Errorf("watching the plans: %w", err)
|
||||
}
|
||||
read.Store(true)
|
||||
serve(given, held, say)
|
||||
|
||||
@@ -2,19 +2,22 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The state the proxy follows to know who is inside is one its manifest reads.
|
||||
// The states the proxy follows are read in the seat form its manifest reads (`seat:<seat>.<state>`; design 47 G A-116), and
|
||||
// the manifest invokes the verb that says who holds the seat.
|
||||
func TestTheReadIsTheManifests(t *testing.T) {
|
||||
raw, err := os.ReadFile("../../module.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m struct {
|
||||
Reads []string `json:"reads"`
|
||||
Reads []string `json:"reads"`
|
||||
Invokes []string `json:"invokes"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -24,4 +27,27 @@ func TestTheReadIsTheManifests(t *testing.T) {
|
||||
t.Errorf("the proxy follows %s, its manifest reads %v", read, m.Reads)
|
||||
}
|
||||
}
|
||||
if !slices.Contains(m.Invokes, "seat:mesh-module-assigner.assignments") {
|
||||
t.Errorf("the proxy asks who holds a seat, its manifest invokes %v", m.Invokes)
|
||||
}
|
||||
}
|
||||
|
||||
// A read in the seat form is the bus's `<holder>.<state>` of the holder on record, whoever it is.
|
||||
func TestHolderReadResolvesTheSeat(t *testing.T) {
|
||||
call := func(holder string) func(string, any) (json.RawMessage, error) {
|
||||
return func(string, any) (json.RawMessage, error) {
|
||||
return json.RawMessage(`{"seats":[{"seat":"mesh-provisioner","holders":[{"module":"` + holder + `"}]}]}`), nil
|
||||
}
|
||||
}
|
||||
for _, holder := range []string{"one", "another"} {
|
||||
if got, err := holderRead(call(holder), plansRead); err != nil || got != holder+".plans" {
|
||||
t.Errorf("holderRead = %q, %v; want %s.plans", got, err, holder)
|
||||
}
|
||||
}
|
||||
if _, err := holderRead(func(string, any) (json.RawMessage, error) { return nil, errors.New("down") }, plansRead); err == nil {
|
||||
t.Errorf("a failed ask was taken as a holder")
|
||||
}
|
||||
if _, err := holderRead(call("x"), "mesh-provisioner.plans"); err == nil {
|
||||
t.Errorf("a read naming a module was taken as a seat's")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user