Hand a mesh of one its store by loopback until a resolver answers its name (issue 701) #325

Open
mesh-admin wants to merge 3 commits from fix/701-a-name-nothing-answers-is-not-used into main
4 changed files with 89 additions and 14 deletions
Showing only changes of commit 0350b4e222 - Show all commits
+47
View File
@@ -460,3 +460,50 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) {
t.Fatalf("with a resolver, the builder publishes to %q", got)
}
}
// **A mesh of two in which one machine does not resolve is still a mesh of two** (review 275). The reading of
// who is on the network leaves the unresolvable laptop out, which made it look like a mesh of one; the
// store's own node must keep its internal name, and its builder keep publishing to it.
func TestAMeshOfTwoWithAnUnresolvableMachineKeepsItsNames(t *testing.T) {
open := aMesh(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": "env", "type": "file", "path": "/etc/mesh-builder.env",
"content": "MESH_REGISTRY=${bound:" + catalogue.ArtifactStoreProvision + ":at}:${bound:" +
catalogue.ArtifactStoreProvision + ":port}\n"}}})
for _, module := range []string{"distribution", "builder"} {
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": 5101}}); err != nil {
t.Fatal(err)
}
// The laptop does not resolve: two modules claim one node seat there, straight into the store.
twoRivalsAndANote(t, open)
for _, m := range []string{"rival-one", "rival-two"} {
if _, err := open.inventory.Assign(ctx, "laptop", m); err != nil {
t.Fatal(err)
}
}
shelf, err := open.inventory.Catalogue(ctx)
if err != nil {
t.Fatal(err)
}
onNetwork, err := whereEveryoneIs(ctx, open.inventory, shelf)
if err != nil || len(onNetwork) != 1 {
t.Fatalf("the unresolvable laptop is still read as on the network: %v %v", onNetwork, err)
}
if got, err := artifactStoreAddress(ctx, open.inventory, shelf, "anchor"); err != nil || got != "anchor.internal:5101" {
t.Fatalf("a mesh of two read as one: the store's own node is handed %q (%v)", got, err)
}
for _, r := range composed(t, open, "anchor").Resources {
if r["path"] == "/etc/mesh-builder.env" && r["content"] != "MESH_REGISTRY=anchor.internal:5101\n" {
t.Fatalf("a mesh of two read as one: the builder publishes to %v", r["content"])
}
}
}
+40 -13
View File
@@ -729,7 +729,7 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory,
// A mesh of more than one node is answered as before: loopback is no answer for the others, and
// one name for every node is what a mesh of several without a resolver is still put together to.
if node == forNode {
unanswered, err := aLoneNameNothingAnswers(ctx, inv, shelf, len(on))
unanswered, err := aLoneNameNothingAnswers(ctx, inv)
if err != nil {
return "", err
}
@@ -746,21 +746,48 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory,
return "127.0.0.1:" + port, nil
}
// aLoneNameNothingAnswers is whether the mesh is one node on its private network and nothing answers that
// node's internal name: no holder of the mesh's resolver seat (novox/hq ADR 0194, ADR 0223), as a machine's
// resolver file lists them (replicatedHolders). Genesis is exactly that from its network step until a
// resolver is installed (novox/hq issue 701): the node then reaches what runs on it by loopback, as it did
// before the network, and is not composed a name no lookup can answer.
func aLoneNameNothingAnswers(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest,
onNetwork int) (bool, error) {
if onNetwork != 1 {
return false, nil
}
holders, err := replicatedHolders(ctx, inv, shelf)
// aLoneNameNothingAnswers is whether the mesh is one node and nothing will answer that node's internal
// name: the inventory knows exactly one node, and no module holds or claims the mesh's resolver seat
// anywhere (novox/hq ADR 0194, ADR 0223). Genesis is exactly that from its network step until a resolver is
// installed (novox/hq issue 701): the node then reaches what runs on it by loopback, as it did before the
// network, and is not composed a name no lookup can answer.
//
// **Read from the mesh, never from who is on the network** (review 275): that reading leaves out a machine
// whose assignments cannot be read or whose modules do not resolve, and a bad reading must never make a
// mesh of several look like a mesh of one and send its machines to loopback. Every recorded holding and
// every assignment of a claimant counts, wherever it is and whether or not its machine has an address.
func aLoneNameNothingAnswers(ctx context.Context, inv *inventory.Inventory) (bool, error) {
nodes, err := inv.Nodes(ctx)
if err != nil {
return false, err
}
return len(holders[resolverSeat]) == 0, nil
if len(nodes) != 1 {
return false, nil
}
holdings, err := inv.Holdings(ctx)
if err != nil {
return false, err
}
for _, h := range holdings {
if s, ok := catalogue.SeatNamed(h.Claim); ok && s.Name == resolverSeat {
return false, nil
}
}
entries, err := inv.Catalogued(ctx)
if err != nil {
return false, err
}
for _, e := range entries {
if len(e.On) == 0 {
continue
}
for _, c := range e.Manifest.Claims {
if s, ok := catalogue.SeatNamed(c.Name); ok && s.Name == resolverSeat {
return false, nil
}
}
}
return true, nil
}
// artifactStoreHolder is whichever node is assigned a module offering the artifact store, on or
+1 -1
View File
@@ -108,7 +108,7 @@ func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Reso
// loopback, as before the network, rather than by a name no lookup on the machine can answer.
ownName := onNetwork[nodeName]
if ownName != "" {
unanswered, err := aLoneNameNothingAnswers(ctx, inv, shelf, len(onNetwork))
unanswered, err := aLoneNameNothingAnswers(ctx, inv)
if err != nil {
return catalogue.Resolution{}, nil, err
}
+1
View File
@@ -19,6 +19,7 @@
./internal/inventory TestAWalksStartIsKeptAndARefusedSaveLeavesItUnsaid
./cmd/mesh-controller TestAWalkLetGoAndStoppedByVerbsIsSaidStartedAndEnded
./cmd/mesh-controller TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt
./cmd/mesh-controller TestAMeshOfTwoWithAnUnresolvableMachineKeepsItsNames
./cmd/mesh-controller TestAWalkStoppedOrClosedByAPersonIsSaidEndedStopped
./cmd/mesh-controller TestAWalkRetriedByAPersonIsSaidStartedAgain
./cmd/mesh-controller TestABacklogReleasedByAPersonIsSaidStarted