package compose import ( "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/overlay" ) // objects and files are the provider and consumer of mesh-controller's facts_test.go aMeshWithSecrets (cmd/mesh-controller at // 6c2a8d62): a module providing s3-bucket at mesh scope with an identity bound, and one requiring it. func objects() catalogue.Manifest { return catalogue.Manifest{Module: "objects", Version: "1", Provides: []catalogue.Offer{{Name: "s3-bucket", Scope: catalogue.ScopeMesh, Identity: &catalogue.OfferIdentity{Max: 20, In: "an S3 access key"}}}, Receives: map[string]string{"s3-bucket": "/var/lib/mesh/objects/mesh.json"}} } func files() catalogue.Manifest { return catalogue.Manifest{Module: "files", Version: "1", Requires: []string{"s3-bucket"}, Resources: []map[string]any{{"id": "config", "type": "file", "path": "/etc/files/config.json", "mode": "0600", "content": "{}", "merge": "json"}}} } // threeNodes is aMesh and a third node `ace` running a database, so the mesh has three nodes that each offer something: // anchor objects, ace a database, laptop a consumer of the first. func threeNodes(t *testing.T) Input { t.Helper() in := aMesh(t) db := catalogue.Manifest{Module: "pg", Version: "1", Provides: catalogue.FromAnywhere("postgres-database")} in.Catalogue["objects"], in.Catalogue["files"], in.Catalogue["pg"] = objects(), files(), db anchor, laptop := nodeOf(t, &in, "anchor"), nodeOf(t, &in, "laptop") anchor.Assigned = append(anchor.Assigned, "objects") laptop.Assigned = append(laptop.Assigned, "files") in.Nodes = append(in.Nodes, Node{Name: "ace", Place: Place{Site: "there", Address: "10.77.0.3"}, Assigned: []string{"pg"}, Capabilities: map[string]bool{}}) return in } func providers(w catalogue.World, provision string) []string { var out []string for _, p := range w.Offered[provision] { out = append(out, p.Node+"/"+p.Module) } return out } // 1361/AC1: three nodes each offering a provision; the rest of the mesh, for one of them, offers every other node's and none of its // own, and holds what they hold. func TestTheRestOfTheMeshOffersEveryOtherNodesProvisionsAndNoneOfTheExcluded(t *testing.T) { in := threeNodes(t) w, passes, left, err := TheRestOfTheMesh(in, "laptop") if err != nil || len(left) != 0 || len(passes.Passes) != 0 { t.Fatalf("%v %v %+v", err, left, passes) } if got := providers(w, "s3-bucket"); len(got) != 1 || got[0] != "anchor/objects" { t.Errorf("s3-bucket is offered by %v", got) } if got := providers(w, "postgres-database"); len(got) != 1 || got[0] != "ace/pg" { t.Errorf("postgres-database is offered by %v", got) } if p := w.Offered["s3-bucket"][0]; p.At != "anchor.internal" { t.Errorf("anchor is at %q on the private network", p.At) } // Excluding anchor takes its offer away and keeps the others'. w, _, _, err = TheRestOfTheMesh(in, "anchor") if err != nil { t.Fatal(err) } if got := providers(w, "s3-bucket"); len(got) != 0 { t.Errorf("the excluded node's offer is in the world: %v", got) } if got := providers(w, "postgres-database"); len(got) != 1 { t.Errorf("another node's offer is missing: %v", got) } // With nobody excluded, every offer is there, and everyone's claims are held. w, _, _, err = TheRestOfTheMesh(in, "") if err != nil || len(providers(w, "s3-bucket")) != 1 || len(providers(w, "postgres-database")) != 1 { t.Fatalf("%v %v", providers(w, "s3-bucket"), err) } // Each machine is resolved with its own pins, as its plan is: the world is the same for any pin of the excluded node. nodeOf(t, &in, "laptop").Pins = map[string]catalogue.Chosen{"s3-bucket": {Node: "anchor", Module: "objects"}} if _, _, _, err := TheRestOfTheMesh(in, "laptop"); err != nil { t.Fatal(err) } } // 1361/AC2: a node whose assigned set does not resolve is left out and named with today's reason, and the others still offer. func TestANodeWhoseSetDoesNotResolveIsLeftOutAndTheOthersStillOffer(t *testing.T) { in := threeNodes(t) rivalOne := catalogue.Manifest{Module: "rival-one", Version: "1", Claims: []catalogue.Claim{{Name: "the-seat", Scope: catalogue.ScopeNode}}} rivalTwo := catalogue.Manifest{Module: "rival-two", Version: "1", Claims: []catalogue.Claim{{Name: "the-seat", Scope: catalogue.ScopeNode}}} in.Catalogue["rival-one"], in.Catalogue["rival-two"] = rivalOne, rivalTwo ace := nodeOf(t, &in, "ace") ace.Assigned = append(ace.Assigned, "rival-one", "rival-two") w, _, left, err := TheRestOfTheMesh(in, "laptop") if err != nil { t.Fatal(err) } if len(left) == 0 { t.Fatal("a node whose set does not resolve was not left out") } for _, l := range left { if l.Node != "ace" || !strings.Contains(l.String(), "ace is left out of the rest of the mesh: it does not resolve: ") { t.Errorf("left out %q", l.String()) } } if got := providers(w, "s3-bucket"); len(got) != 1 { t.Errorf("the others do not offer: %v", got) } if got := providers(w, "postgres-database"); len(got) != 0 { t.Errorf("the node left out offers: %v", got) } // Said for the whole-mesh view in both passes (as the controller says it), and for a view with one machine excluded in the first only. _, _, whole, _ := TheRestOfTheMesh(in, "") if len(whole) != 2 { t.Errorf("the whole-mesh view says %d line(s), want the two passes'", len(whole)) } if len(left) != 1 { t.Errorf("a view with one excluded says %d line(s), want the first pass's", len(left)) } } // 1361/AC3 and AC4 (mesh-controller seat_pass_test.go TestACompositionPassesTheSeatAndWritesOneHandActByTheController, in // the part that is not a write): a seat held once whose holder on record is assigned nowhere and one claimant: the seat // passes to that claimant in the World, and is returned as a pass; Input's holdings are unchanged. func TestASeatWhoseHolderIsAssignedNowhereGoesToItsOnlyClaimant(t *testing.T) { withSeat(t) mk := func() Input { return Input{ Catalogue: map[string]catalogue.Manifest{"gate": aBrokerClaiming("gate")}, Nodes: []Node{{Name: "novox", Place: Place{Site: "here"}, Assigned: []string{"gate"}}}, Holdings: []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker", Site: "here"}}, } } in := mk() w, passes, _, err := TheRestOfTheMesh(in, "") if err != nil { t.Fatal(err) } if len(w.Holdings) != 1 || w.Holdings[0].Module != "gate" || w.Holdings[0].Node != "novox" { t.Fatalf("the holdings after the composition do not name the gate: %+v", w.Holdings) } if len(passes.Passes) != 1 { t.Fatalf("passes=%d, want one", len(passes.Passes)) } p := passes.Passes[0] if p.Seat.Name != "mesh-broker" || p.From.Module != "old-broker" || p.To.Module != "gate" || p.To.Node != "novox" { t.Errorf("the pass is %+v", p) } unchanged(t, mk, func(in Input) { if _, _, _, err := TheRestOfTheMesh(in, ""); err != nil { t.Fatal(err) } }) } // 1317/AC2 (seat_pass_test.go TestACompositionWithTwoClaimantsRecordsNoneAndSaysSo): two claimants, no pass, the refusal said with // both and the remedy. 1317/AC3 and AC4 (TestACompositionChangesNothingWhileTheHolderIsAssignedOrNobodyClaims): nothing changes while // the holder is assigned, nor with no claimant. func TestNoSeatIsPassedForTwoClaimantsAnAssignedHolderOrNoClaimant(t *testing.T) { withSeat(t) held := []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker"}} _, passes := passSeatsOn(held, []catalogue.Assignment{ {Node: "novox", Manifest: aBrokerClaiming("gate")}, {Node: "ace", Manifest: aBrokerClaiming("other")}}) said := strings.Join(passes.Said, "\n") if len(passes.Passes) != 0 || !strings.Contains(said, "gate") || !strings.Contains(said, "other") || !strings.Contains(said, "seat mesh-broker --to") { t.Fatalf("passes %+v said %q", passes.Passes, said) } got, passes := passSeatsOn(held, []catalogue.Assignment{ {Node: "novox", Manifest: aBrokerClaiming("old-broker")}, {Node: "novox", Manifest: aBrokerClaiming("gate")}}) if len(passes.Passes) != 0 || len(got) != 1 || got[0].Module != "old-broker" { t.Fatalf("holder assigned: %+v %+v", got, passes) } got, passes = passSeatsOn(held, nil) if len(passes.Passes) != 0 || len(got) != 1 || got[0].Module != "old-broker" { t.Fatalf("no claimant: %+v %+v", got, passes) } } // seat_pass_test.go TestAHolderAssignedButNotOnTheShelfIsNotTakenForGone. func TestAHolderAssignedButNotOnTheShelfIsNotTakenForGone(t *testing.T) { withSeat(t) as := assignmentsOf(map[string]catalogue.Manifest{"gate": aBrokerClaiming("gate")}, map[string][]string{"novox": {"gate", "old-broker"}}) passes, _ := catalogue.SeatsToPassOn( []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker"}}, as) if len(passes) != 0 { t.Fatalf("a holder missing from the shelf lost its seat: %+v", passes) } } // The not-resolving node of a view with a holder on record: holdings reach the first pass (novox/hq ADR 0131), so a node running // the next holder beside the current one is not refused as a second holder. func TestAHolderOnRecordSettlesTheSeatInTheRestOfTheMesh(t *testing.T) { withSeat(t) in := Input{ Catalogue: map[string]catalogue.Manifest{"old-broker": aBrokerClaiming("old-broker"), "gate": aBrokerClaiming("gate")}, Nodes: []Node{ {Name: "novox", Assigned: []string{"old-broker", "gate"}}, {Name: "ace", Assigned: []string{}}}, Holdings: []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker"}}, } _, passes, left, err := TheRestOfTheMesh(in, "ace") if err != nil || len(left) != 0 || len(passes.Passes) != 0 { t.Fatalf("%v %v %+v", err, left, passes) } in.Holdings = nil _, _, left, err = TheRestOfTheMesh(in, "ace") if err != nil || len(left) == 0 { t.Fatalf("two claimants with nothing on record were not refused: %v %v", err, left) } } // Each machine is resolved in the second pass with its own pins, as its plan is (novox/hq issue 188): a pin to a provider that // does not answer is its own refusal, so the machine is left out for it; the same machine without the pin is not. func TestEachNodeIsResolvedWithItsOwnPinsInTheRestOfTheMesh(t *testing.T) { in := threeNodes(t) in.Nodes = append(in.Nodes, Node{Name: "newt", Place: Place{Site: "there", Address: "10.77.0.4"}, Assigned: []string{overlay.Name, "files"}, Capabilities: map[string]bool{}}) _, _, left, err := TheRestOfTheMesh(in, "") if err != nil || len(left) != 0 { t.Fatalf("without a pin the consumer is left out: %v %v", left, err) } nodeOf(t, &in, "newt").Pins = map[string]catalogue.Chosen{"s3-bucket": {Node: "ace", Module: "nothing"}} _, _, left, err = TheRestOfTheMesh(in, "") if err != nil || len(left) != 1 || left[0].Node != "newt" || !strings.Contains(left[0].String(), "was told to get \"s3-bucket\" from") { t.Fatalf("a pin to a provider that does not answer did not leave the consumer out: %v %v", left, err) } }