The seat table has name, scope, delivers and decision, and the protocol ADR 0129 gave a seat lives only in the compiled defaults; loading the rows dropped it, so no role's work queue was ever raised and the first build submitted over the new bus met "no response from stream". Until the table gains the columns, a row with no protocol keeps the compiled one of its name. And the holder of a seat is granted what taking work from its queue needs — asking about the worker consumer it binds, and acknowledging on it — which the first machine to try was refused. The control plane's own seat placeholders no longer include the old bus's port, which the switch removed with the variable.
213 lines
7.5 KiB
Go
213 lines
7.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The control plane's own addresses follow the node's ports (novox/hq 04-ISSUES/102).
|
|
|
|
func TestASeatPlaceholderAnswersWhereThisMachinePutTheHolder(t *testing.T) {
|
|
control := map[string]any{
|
|
"type": "container", "id": "server", "name": "mesh-controller",
|
|
"env": map[string]any{
|
|
"MESH_STORE_INVENTORY_PORT": "${seat:mesh-store:5432}",
|
|
"MESH_BROKER_ADDRESS_PORT": "${seat:mesh-broker:5671}",
|
|
"MESH_STORE_INVENTORY_FILE": "/run/secrets/inventory",
|
|
},
|
|
}
|
|
with := Rendering{Seats: map[string]map[int]int{
|
|
"mesh-store": {5432: 6852}, "mesh-broker": {5672: 5679, 5671: 5671},
|
|
}}
|
|
if err := seatInto(control, "mesh-controller", with); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
env := control["env"].(map[string]any)
|
|
for key, want := range map[string]string{
|
|
"MESH_STORE_INVENTORY_PORT": "6852",
|
|
"MESH_BROKER_ADDRESS_PORT": "5671",
|
|
"MESH_STORE_INVENTORY_FILE": "/run/secrets/inventory",
|
|
} {
|
|
if env[key] != want {
|
|
t.Errorf("%s = %v, want %q", key, env[key], want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A seat nothing on this machine holds — or one whose holder the mesh has given no port — answers
|
|
// with nothing, so the port genesis wrote into the connection string stands. Not the software's
|
|
// own port: on a node given a port at genesis before the store's module exists, that would
|
|
// override the right number with the catalogue's.
|
|
func TestASeatTheMeshCannotPlaceAnswersWithNothing(t *testing.T) {
|
|
control := map[string]any{
|
|
"type": "container", "id": "server", "name": "mesh-controller",
|
|
"env": map[string]any{"MESH_STORE_INVENTORY_PORT": "${seat:mesh-store:5432}"},
|
|
}
|
|
if err := seatInto(control, "mesh-controller", Rendering{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := control["env"].(map[string]any)["MESH_STORE_INVENTORY_PORT"]; got != "" {
|
|
t.Fatalf("with nothing known, the seat answered %q", got)
|
|
}
|
|
file := map[string]any{"type": "file", "content": "port=${seat:mesh-store:5432}\n"}
|
|
if err := seatInto(file, "x", Rendering{Seats: map[string]map[int]int{"mesh-store": {5433: 1}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := file["content"]; got != "port=\n" {
|
|
t.Fatalf("a port the holder does not publish answered %q", got)
|
|
}
|
|
}
|
|
|
|
func TestASeatPlaceholderNamingNoPortIsRefused(t *testing.T) {
|
|
file := map[string]any{"type": "file", "content": "${seat:mesh-store:99999}"}
|
|
if err := seatInto(file, "x", Rendering{}); err == nil {
|
|
t.Fatal("99999 was accepted as a port")
|
|
}
|
|
}
|
|
|
|
func TestFillingASeatLeavesTheManifestAlone(t *testing.T) {
|
|
env := map[string]any{"MESH_STORE_INVENTORY_PORT": "${seat:mesh-store:5432}"}
|
|
manifest := map[string]any{"type": "container", "id": "server", "env": env}
|
|
for _, at := range []int{6852, 5432} {
|
|
copied := map[string]any{}
|
|
for k, v := range manifest {
|
|
copied[k] = v
|
|
}
|
|
with := Rendering{Seats: map[string]map[int]int{"mesh-store": {5432: at}}}
|
|
if err := seatInto(copied, "mesh-controller", with); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if env["MESH_STORE_INVENTORY_PORT"] != "${seat:mesh-store:5432}" {
|
|
t.Fatalf("the module's own manifest was edited: %v", env)
|
|
}
|
|
}
|
|
|
|
// **The control plane's own manifest, composed through the whole path.**
|
|
//
|
|
// The store was given 6852 and the broker's plain port 5679 (the control-node's migration, novox/hq
|
|
// 04-ISSUES/102). The control plane's own connections are sealed at genesis with the ports genesis
|
|
// wrote; what its container is told beside them is where this machine put the store and the
|
|
// broker now, read from the node's settings exactly as every consumer's binding is.
|
|
func TestTheControlPlanesOwnAddressesFollowTheNodesPorts(t *testing.T) {
|
|
raw, err := os.ReadFile("../../module.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, err := ParseManifest(raw)
|
|
if err != nil {
|
|
t.Fatalf("the control plane's own manifest does not parse:\n%v", err)
|
|
}
|
|
// The manifest itself names them now; withSeatPorts is a no-op on it, and this holds it so.
|
|
for _, r := range m.Resources {
|
|
if r["type"] != "container" {
|
|
continue
|
|
}
|
|
env, _ := r["env"].(map[string]any)
|
|
for key, want := range SeatPorts {
|
|
if env[key] != want {
|
|
t.Errorf("module.json says %s=%v, not %q", key, env[key], want)
|
|
}
|
|
}
|
|
}
|
|
m = withSeatPorts(m)
|
|
control, err := m.Resolve([]Built{{
|
|
Name: "server", Kind: ArtifactImage,
|
|
Reference: ArtifactStoreScheme + "mesh-controller/server@sha256:" + strings.Repeat("c", 64),
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := Resolution{Node: "anchor", Modules: []Manifest{control}}
|
|
needed := map[string]map[string]string{"mesh-controller": {}}
|
|
for name := range m.OwnSecrets {
|
|
needed["mesh-controller"][name] = "sealed-" + name
|
|
}
|
|
out, err := r.Declaration(Rendering{
|
|
Needed: needed,
|
|
ArtifactStore: "anchor.internal:5100",
|
|
Seats: map[string]map[int]int{
|
|
"mesh-store": {5432: 6852},
|
|
"mesh-broker": {5671: 5671, 5672: 5679, 15672: 15673},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("the control plane does not compose: %v", err)
|
|
}
|
|
server := fileNamed(out, "mesh-controller.server")
|
|
if server == nil {
|
|
t.Fatalf("the control plane's container is not in the declaration: %v", out)
|
|
}
|
|
env, _ := server["env"].(map[string]any)
|
|
for key, want := range map[string]string{
|
|
"MESH_STORE_INVENTORY_PORT": "6852",
|
|
"MESH_STORE_IDENTITY_PORT": "6852",
|
|
"MESH_STORE_LICENCES_PORT": "6852",
|
|
"MESH_BROKER_MANAGEMENT_PORT": "15673",
|
|
"MESH_BROKER_ADDRESS_PORT": "5671",
|
|
} {
|
|
if env[key] != want {
|
|
t.Errorf("the control plane is told %s=%v; the node put it on %s", key, env[key], want)
|
|
}
|
|
}
|
|
if got := server["image"]; got != "anchor.internal:5100/mesh-controller/server@sha256:"+strings.Repeat("c", 64) {
|
|
t.Errorf("the control plane's own image is %v, not routed through the store", got)
|
|
}
|
|
|
|
// And on a mesh where the foundation is where genesis raised it, nothing is added.
|
|
out, err = r.Declaration(Rendering{Needed: needed, ArtifactStore: "anchor.internal:5100"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
env, _ = fileNamed(out, "mesh-controller.server")["env"].(map[string]any)
|
|
if env["MESH_STORE_INVENTORY_PORT"] != "" {
|
|
t.Errorf("with no settings, the control plane is told %v", env)
|
|
}
|
|
}
|
|
|
|
// SeatPorts is what the control plane's manifest says beside each sealed connection: the port
|
|
// this machine put the seat's holder at (novox/hq 04-ISSUES/102).
|
|
var SeatPorts = map[string]string{
|
|
"MESH_STORE_INVENTORY_PORT": "${seat:mesh-store:5432}",
|
|
"MESH_STORE_IDENTITY_PORT": "${seat:mesh-store:5432}",
|
|
"MESH_STORE_LICENCES_PORT": "${seat:mesh-store:5432}",
|
|
"MESH_BROKER_MANAGEMENT_PORT": "${seat:mesh-broker:15672}",
|
|
"MESH_BROKER_ADDRESS_PORT": "${seat:mesh-broker:5671}",
|
|
}
|
|
|
|
// withSeatPorts is the control plane's manifest with SeatPorts in its container's environment.
|
|
//
|
|
// **The manifest lands one commit after the code that fills it**, deliberately: a control plane
|
|
// still running the previous build passes `${seat:…}` through unfilled, and the manifest may only
|
|
// name the placeholder once every control plane that could compose it knows it. So the test does
|
|
// not depend on module.json carrying these yet, and is a no-op once it does.
|
|
func withSeatPorts(m Manifest) Manifest {
|
|
out := m
|
|
out.Resources = nil
|
|
for _, r := range m.Resources {
|
|
if r["type"] != "container" {
|
|
out.Resources = append(out.Resources, r)
|
|
continue
|
|
}
|
|
copied := map[string]any{}
|
|
for k, v := range r {
|
|
copied[k] = v
|
|
}
|
|
env := map[string]any{}
|
|
if had, ok := r["env"].(map[string]any); ok {
|
|
for k, v := range had {
|
|
env[k] = v
|
|
}
|
|
}
|
|
for k, v := range SeatPorts {
|
|
if _, said := env[k]; !said {
|
|
env[k] = v
|
|
}
|
|
}
|
|
copied["env"] = env
|
|
out.Resources = append(out.Resources, copied)
|
|
}
|
|
return out
|
|
}
|