Compose a lone node off the network while nothing answers its name (issue 701)
mesh/merge-gate out of date: judged against trunk 04730b54a8f2, the trunk is now 535c7641ac41; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 04730b54a8f2, the trunk is now 535c7641ac41; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate out of date: judged against trunk 04730b54a8f2, the trunk is now 535c7641ac41; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 04730b54a8f2, the trunk is now 535c7641ac41; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
The first change kept the base references on loopback, and the build agent then failed to publish: its registry is its same-node binding, composed at the node's own internal name. While a mesh of one has no resolver, compose that node without its name, so what it binds to on itself is loopback, as before the network.
This commit is contained in:
@@ -385,7 +385,11 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) {
|
|||||||
register(t, open, catalogue.Manifest{Module: "builder", Version: "1",
|
register(t, open, catalogue.Manifest{Module: "builder", Version: "1",
|
||||||
Requires: []string{catalogue.ArtifactStoreProvision},
|
Requires: []string{catalogue.ArtifactStoreProvision},
|
||||||
Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-builder",
|
Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-builder",
|
||||||
"image": "registry.example/mesh-builder@" + aDigest}}})
|
"image": "registry.example/mesh-builder@" + aDigest},
|
||||||
|
// Where the build agent publishes, as its manifest says it.
|
||||||
|
{"id": "env", "type": "file", "path": "/etc/mesh-builder.env",
|
||||||
|
"content": "MESH_REGISTRY=${bound:" + catalogue.ArtifactStoreProvision + ":at}:${bound:" +
|
||||||
|
catalogue.ArtifactStoreProvision + ":port}\n"}}})
|
||||||
if err := open.inventory.RecordBuild(ctx, inventory.Build{
|
if err := open.inventory.RecordBuild(ctx, inventory.Build{
|
||||||
ID: "b1", Repository: "r", Module: "mesh-tools", Commit: "abc",
|
ID: "b1", Repository: "r", Module: "mesh-tools", Commit: "abc",
|
||||||
Made: []inventory.Artifact{{Name: "build", Kind: "image",
|
Made: []inventory.Artifact{{Name: "build", Kind: "image",
|
||||||
@@ -417,6 +421,20 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) {
|
|||||||
if got := heldBy(ctx)["mesh-tools/build"]; got != "127.0.0.1:5101/mesh-tools/build@"+aDigest {
|
if got := heldBy(ctx)["mesh-tools/build"]; got != "127.0.0.1:5101/mesh-tools/build@"+aDigest {
|
||||||
t.Fatalf("the builder beside the store is handed the base %q", got)
|
t.Fatalf("the builder beside the store is handed the base %q", got)
|
||||||
}
|
}
|
||||||
|
publishesTo := func() string {
|
||||||
|
t.Helper()
|
||||||
|
for _, r := range composed(t, open, "anchor").Resources {
|
||||||
|
if r["path"] == "/etc/mesh-builder.env" {
|
||||||
|
content, _ := r["content"].(string)
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("the builder's environment was not composed")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if got := publishesTo(); got != "MESH_REGISTRY=127.0.0.1:5101\n" {
|
||||||
|
t.Fatalf("the builder beside the store publishes to %q, a name nothing answers", got)
|
||||||
|
}
|
||||||
|
|
||||||
// A resolver holds the seat: the name is answered, and the store is named by it.
|
// A resolver holds the seat: the name is answered, and the store is named by it.
|
||||||
if _, err := open.inventory.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
|
if _, err := open.inventory.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
|
||||||
@@ -438,4 +456,7 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) {
|
|||||||
if got := heldBy(ctx)["mesh-tools/build"]; got != "anchor.internal:5101/mesh-tools/build@"+aDigest {
|
if got := heldBy(ctx)["mesh-tools/build"]; got != "anchor.internal:5101/mesh-tools/build@"+aDigest {
|
||||||
t.Fatalf("the builder is handed the base %q", got)
|
t.Fatalf("the builder is handed the base %q", got)
|
||||||
}
|
}
|
||||||
|
if got := publishesTo(); got != "MESH_REGISTRY=anchor.internal:5101\n" {
|
||||||
|
t.Fatalf("with a resolver, the builder publishes to %q", got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -728,12 +728,12 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory,
|
|||||||
// the seat, a mesh of one keeps reaching its store by loopback, as it did before the network.
|
// the seat, a mesh of one keeps reaching its store by loopback, as it did before the network.
|
||||||
// A mesh of more than one node is answered as before: loopback is no answer for the others, and
|
// 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.
|
// one name for every node is what a mesh of several without a resolver is still put together to.
|
||||||
if node == forNode && len(on) == 1 {
|
if node == forNode {
|
||||||
answered, err := meshNamesAnswered(ctx, inv, shelf)
|
unanswered, err := aLoneNameNothingAnswers(ctx, inv, shelf, len(on))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if !answered {
|
if unanswered {
|
||||||
return "127.0.0.1:" + port, nil
|
return "127.0.0.1:" + port, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -746,15 +746,21 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory,
|
|||||||
return "127.0.0.1:" + port, nil
|
return "127.0.0.1:" + port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// meshNamesAnswered is whether something answers the mesh's internal names: a holder of the mesh's
|
// aLoneNameNothingAnswers is whether the mesh is one node on its private network and nothing answers that
|
||||||
// resolver seat on the private network (novox/hq ADR 0194, ADR 0223), as a machine's resolver file
|
// node's internal name: no holder of the mesh's resolver seat (novox/hq ADR 0194, ADR 0223), as a machine's
|
||||||
// lists them (replicatedHolders).
|
// resolver file lists them (replicatedHolders). Genesis is exactly that from its network step until a
|
||||||
func meshNamesAnswered(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest) (bool, error) {
|
// 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)
|
holders, err := replicatedHolders(ctx, inv, shelf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return len(holders[resolverSeat]) > 0, nil
|
return len(holders[resolverSeat]) == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// artifactStoreHolder is whichever node is assigned a module offering the artifact store, on or
|
// artifactStoreHolder is whichever node is assigned a module offering the artifact store, on or
|
||||||
|
|||||||
@@ -103,6 +103,19 @@ func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Reso
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return catalogue.Resolution{}, nil, err
|
return catalogue.Resolution{}, nil, err
|
||||||
}
|
}
|
||||||
|
// **This node's own name, only where something answers it** (novox/hq issue 701). A mesh of one on its
|
||||||
|
// network with no resolver yet is composed as off it: what it binds to on itself is reached by
|
||||||
|
// 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))
|
||||||
|
if err != nil {
|
||||||
|
return catalogue.Resolution{}, nil, err
|
||||||
|
}
|
||||||
|
if unanswered {
|
||||||
|
ownName = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// What this mesh can answer with a record rather than a machine, and which record each of
|
// What this mesh can answer with a record rather than a machine, and which record each of
|
||||||
// this node's modules was put on. Read across a context boundary by name, which is what
|
// this node's modules was put on. Read across a context boundary by name, which is what
|
||||||
@@ -136,7 +149,7 @@ func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Reso
|
|||||||
|
|
||||||
resolved, err := catalogue.Resolve(shelf, assigned,
|
resolved, err := catalogue.Resolve(shelf, assigned,
|
||||||
catalogue.Node{Name: nodeName, Site: site, Capabilities: capabilities,
|
catalogue.Node{Name: nodeName, Site: site, Capabilities: capabilities,
|
||||||
At: onNetwork[nodeName], PublicDomain: publicDomain,
|
At: ownName, PublicDomain: publicDomain,
|
||||||
Account: who.Account, AccountHome: who.AccountHome,
|
Account: who.Account, AccountHome: who.AccountHome,
|
||||||
AgentAccount: who.AgentAccount, AgentAccountHome: who.AgentAccountHome}, world)
|
AgentAccount: who.AgentAccount, AgentAccountHome: who.AgentAccountHome}, world)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user