Hand a mesh of one its store by loopback until a resolver answers its name (issue 701) #325
@@ -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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user