The controller asks no build of anybody (ADR 0501 §5, ADR 0525 clause 6, ADR 0503 §3). Every module of a walk is taken from the builder's done key; a key not done yet waits, named in the plan, and never fails it. - C2b/C2c: internal/builder and cmd/mesh-controller's asks go (askABuild, onlyTheBuildersOwn, buildOneAsked, takeIn from an outcome, buildAgentsOwn, the builder's say and its fallback, the daemon hearing an outcome). The server follows the retired seats' built subjects only to let go of them, because the installer's carried user list names them. - C2d: replay, queue, cancel, clear, kill, pause, resume, delivery-check, check-here and merge-gate are refused by name. - C3: the check queues (CHECKS_READY, CHECKS_OTHER) and their workers, the checks-running and plans-building buckets and their grants, the build queue and its hold (plans_building.go, queue.go, link/holds.go, builds_nats.go, checks_nats.go). - C4: merge_gate*.go, the pull request's repo-check path, the Checked event and its layers; a pull request asks the controller nothing but to tell mesh-config what its head declares. - C5: the builds bucket is written only by key_takein. - C10 (help and refusals): the terminal's help lists only what is served. Integration tests of the plan's tiers, sends, stops and retries now run on a stand-in builder with a done key for every module (walk_keys_scaffold_integration_test.go); the tests of the asks, the queue, the merge gate and the replays are deleted.
225 lines
8.6 KiB
Go
225 lines
8.6 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/licences"
|
|
)
|
|
|
|
func TestTheRegistryTrustAndEveryImageFollowThePortTheNodeGaveTheStore(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
register(t, open, aStore())
|
|
if _, err := assign(ctx, open, "anchor", "distribution"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := open.inventory.SetSettings(ctx, "anchor", "distribution",
|
|
map[string]any{catalogue.PortsSetting: map[string]any{"5000": 5101}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A module the mesh built, recorded by digest and path, running on the other machine.
|
|
if err := open.inventory.RecordBuild(ctx, inventory.Build{
|
|
ID: "b1", Repository: "r", Module: "app", Commit: "abc",
|
|
Made: []inventory.Artifact{{Name: "server", Kind: "image",
|
|
Reference: catalogue.ArtifactStoreScheme + "app/server@" + aDigest}},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
register(t, open, catalogue.Manifest{Module: "app", Version: "1",
|
|
Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-app",
|
|
"image": catalogue.ArtifactStoreScheme + "app/server@" + aDigest}}})
|
|
if _, err := assign(ctx, open, "laptop", "app"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The runtime's trust is the runtime's module's to write (novox/hq ADR 0222): a stand-in for it
|
|
// asks where this machine reaches the store, as the docker module does.
|
|
register(t, open, catalogue.Manifest{Module: "runtime", Version: "1",
|
|
Resources: []map[string]any{{"id": "daemon", "type": "file", "path": "/etc/docker/daemon.json",
|
|
"into": "json", "content": `{"insecure-registries": ["${seat:mesh-artifact-store:reach}"]}` + "\n"}}})
|
|
if _, err := assign(ctx, open, "laptop", "runtime"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
on := map[string]bool{"anchor": true, "laptop": true}
|
|
node, port, found, err := artifactStoreOnNetwork(ctx, open.inventory, on)
|
|
if err != nil || !found || node != "anchor" || port != "5101" {
|
|
t.Fatalf("the store is found on %q:%q (%v, %v); the node put it on 5101", node, port, found, err)
|
|
}
|
|
|
|
var trust string
|
|
for _, r := range composed(t, open, "laptop").Resources {
|
|
if r["path"] == "/etc/docker/daemon.json" {
|
|
trust, _ = r["content"].(string)
|
|
}
|
|
if r["id"] == "app.server" && r["image"] != "anchor.internal:5101/app/server@"+aDigest {
|
|
t.Errorf("the image the mesh built is fetched as %v", r["image"])
|
|
}
|
|
}
|
|
if !strings.Contains(trust, "anchor.internal:5101") || strings.Contains(trust, ":5000") {
|
|
t.Fatalf("the runtime is told to trust %q; the node put the store on 5101", trust)
|
|
}
|
|
|
|
// And a replay to the catalogue says where the store is now.
|
|
announced, err := following{open: open}.Announceable(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(announced) != 1 || announced[0].Made[0].Reference != "anchor.internal:5101/app/server@"+aDigest {
|
|
t.Fatalf("the replay announces %+v", announced)
|
|
}
|
|
}
|
|
|
|
func TestTheControlPlaneIsToldWhereTheNodePutTheStoreAndTheBroker(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
raw, err := os.ReadFile("../../module.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, err := catalogue.ParseManifest(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
control, err := withSeatPorts(m).Resolve([]catalogue.Built{{Name: "controller", Kind: catalogue.ArtifactBundle,
|
|
Reference: "https://registry.example/mesh-controller/controller.tar.gz", Digest: aDigest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
register(t, open, control)
|
|
register(t, open, catalogue.Manifest{Module: "postgres", Version: "1",
|
|
Claims: []catalogue.Claim{{Name: "mesh-store", Scope: catalogue.ScopeMesh}},
|
|
Listens: []catalogue.Listening{{Port: 5432, From: catalogue.FromMesh}},
|
|
Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-store",
|
|
"ports": []any{"5432:5432"}, "image": "pg@" + aDigest}}})
|
|
register(t, open, catalogue.Manifest{Module: "lavinmq", Version: "1",
|
|
Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}},
|
|
Listens: []catalogue.Listening{{Port: 5671, From: catalogue.FromMesh},
|
|
{Port: 5672, From: catalogue.FromMesh}},
|
|
Guards: []int{15672},
|
|
Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-broker",
|
|
"ports": []any{"5671:5671", "5672:5672", "127.0.0.1:15672:15672"}, "image": "mq@" + aDigest}}})
|
|
// The control plane's own bus user is the installer's, seeded at genesis before the controller
|
|
// runs (SeedBusUser); without it a push now refuses the credential nobody issued (issue 203).
|
|
if err := open.inventory.SeedBusUser(ctx, inventory.BusUser{Username: "anchor.mesh-controller",
|
|
Kind: inventory.BusController, Node: "anchor", Module: "mesh-controller"}, "bootstrap"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := assign(ctx, open, "anchor", "mesh-controller"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The store's module is registered and given its port, and NOT assigned: the state genesis
|
|
// leaves a given-port node in before the foundation is adopted as modules (04-ISSUES/085).
|
|
if err := open.inventory.SetSettings(ctx, "anchor", "postgres",
|
|
map[string]any{catalogue.PortsSetting: map[string]any{"5432": 6852}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := assign(ctx, open, "anchor", "lavinmq"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := open.inventory.SetSettings(ctx, "anchor", "lavinmq",
|
|
map[string]any{catalogue.PortsSetting: map[string]any{"5672": 5679}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var env map[string]any
|
|
for _, r := range composed(t, open, "anchor").Resources {
|
|
if r["id"] == "mesh-controller.controller" {
|
|
env, _ = r["env"].(map[string]any)
|
|
}
|
|
}
|
|
if env == nil {
|
|
t.Fatal("the control plane's process is not in its own node's declaration")
|
|
}
|
|
for key, want := range map[string]string{
|
|
"MESH_STORE_INVENTORY_PORT": "6852",
|
|
"MESH_STORE_IDENTITY_PORT": "6852",
|
|
"MESH_STORE_LICENCES_PORT": "6852",
|
|
"MESH_BROKER_AMQP_PORT": "5679",
|
|
// Neither given nor assigned by the mesh: the manifest's own number is NOT the answer,
|
|
// because the sealed value beside it carries the port genesis wrote (finding F3).
|
|
"MESH_BROKER_ADDRESS_PORT": "",
|
|
"MESH_BROKER_MANAGEMENT_PORT": "",
|
|
} {
|
|
if env[key] != want {
|
|
t.Errorf("the control plane is told %s=%v; the node says %q", key, env[key], want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func aLoneNode(t *testing.T) *stores {
|
|
t.Helper()
|
|
inventory.ForTest(t)
|
|
licences.ForTest(t)
|
|
open, err := openStores(t.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(open.Close)
|
|
for _, m := range provided {
|
|
if err := open.inventory.Provide(t.Context(), m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
record, err := open.inventory.AddNode(t.Context(), "anchor")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reported, _ := json.Marshal(map[string]any{"capabilities": []map[string]any{
|
|
{"name": "container-runtime", "present": true}}})
|
|
var profile map[string]any
|
|
_ = json.Unmarshal(reported, &profile)
|
|
if err := open.inventory.RecordProfile(t.Context(), record.ID, profile); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := open.inventory.RecordSealingKey(t.Context(), record.ID, aPublicKey(t)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return open
|
|
}
|
|
|
|
func TestOnANodeWithNoNetworkTheStoreIsReachedByLoopback(t *testing.T) {
|
|
open := aLoneNode(t)
|
|
ctx := t.Context()
|
|
register(t, open, aStore())
|
|
register(t, open, catalogue.Manifest{Module: "builder", Version: "1",
|
|
Requires: []string{catalogue.ArtifactStoreProvision},
|
|
Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-builder",
|
|
"image": "registry.example/mesh-builder@" + aDigest}}})
|
|
if err := open.inventory.RecordBuild(ctx, inventory.Build{
|
|
ID: "b1", Repository: "r", Module: "postgres", Commit: "abc",
|
|
Made: []inventory.Artifact{{Name: "runtime", Kind: "image",
|
|
Reference: catalogue.ArtifactStoreScheme + "postgres/runtime@" + aDigest}},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
register(t, open, catalogue.Manifest{Module: "postgres", Version: "1",
|
|
Resources: []map[string]any{{"id": "runtime", "type": "container", "name": "mesh-postgres",
|
|
"image": catalogue.ArtifactStoreScheme + "postgres/runtime@" + aDigest}}})
|
|
for _, module := range []string{"distribution", "builder", "postgres"} {
|
|
if _, err := assign(ctx, open, "anchor", module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := open.inventory.SetSettings(ctx, "anchor", "distribution",
|
|
map[string]any{catalogue.PortsSetting: map[string]any{"5000": 5100}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var image any
|
|
for _, r := range composed(t, open, "anchor").Resources {
|
|
if r["id"] == "postgres.runtime" {
|
|
image = r["image"]
|
|
}
|
|
}
|
|
if image != "127.0.0.1:5100/postgres/runtime@"+aDigest {
|
|
t.Fatalf("on the store's own node, off any network, the image is fetched as %v", image)
|
|
}
|
|
}
|