//go:build integration package main import ( "reflect" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/compose" ) // 1382: the adapter's Input names, for every node, what the inventory answers for it, each compared with the read named // beside the field in go/compose/input.go (novox/hq ADR 0453 clause 3). func TestTheAdaptersInputNamesWhatTheInventoryAnswersForEachNode(t *testing.T) { open, _ := aMeshWithSecrets(t) ctx := t.Context() inv := open.inventory if err := inv.SetPublicDomain(ctx, "anchor", "example.test"); err != nil { t.Fatal(err) } if _, err := inv.PortFor(ctx, "laptop", "files", 8080, false); err != nil { t.Fatal(err) } in, err := readComposeInput(ctx, open) if err != nil { t.Fatal(err) } shelf, err := inv.Catalogue(ctx) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(in.Catalogue, shelf) { t.Error("the Input's Catalogue is not the shelf the inventory holds") } nodes, err := inv.Nodes(ctx) if err != nil { t.Fatal(err) } if len(in.Nodes) != len(nodes) { t.Fatalf("the Input names %d node(s), the inventory has %d", len(in.Nodes), len(nodes)) } places, err := inv.Overlays(ctx) if err != nil { t.Fatal(err) } for _, record := range nodes { n, ok := nodeOf(in.Input, record.Name) if !ok { t.Fatalf("%s is not in the Input", record.Name) } for _, p := range places { if p.Name == record.Name { if want := (compose.Place{Key: p.Key, Endpoint: p.Endpoint, Site: p.Site, Hub: p.Hub, Address: p.Address}); n.Place != want { t.Errorf("%s: Place is %+v, the inventory's %+v", record.Name, n.Place, want) } } } assigned, _ := inv.Assigned(ctx, record.Name) if !reflect.DeepEqual(n.Assigned, assigned) { t.Errorf("%s: Assigned is %v, the inventory's %v", record.Name, n.Assigned, assigned) } caps, _ := inv.ProfileOf(ctx, record.Name) if !reflect.DeepEqual(n.Capabilities, caps) { t.Errorf("%s: Capabilities are %v, the inventory's %v", record.Name, n.Capabilities, caps) } domain, _ := inv.PublicDomainOf(ctx, record.Name) if n.PublicDomain != domain { t.Errorf("%s: PublicDomain is %q, the inventory's %q", record.Name, n.PublicDomain, domain) } if n.Account != record.Account || n.AccountHome != record.AccountHome || n.AgentAccount != record.AgentAccount || n.AgentAccountHome != record.AgentAccountHome || n.Adopted != record.Adopted { t.Errorf("%s: the accounts or the mode differ from the inventory's: %+v", record.Name, n) } pins, _ := inv.PinsFor(ctx, record.Name) if len(pins) != len(n.Pins) || (len(pins) > 0 && !reflect.DeepEqual(n.Pins, pins)) { t.Errorf("%s: Pins are %v, the inventory's %v", record.Name, n.Pins, pins) } bound, _ := inv.BindingsFor(ctx, record.Name) if len(bound) != len(n.Bound) || (len(bound) > 0 && !reflect.DeepEqual(n.Bound, bound)) { t.Errorf("%s: Bound is %v, the inventory's %v", record.Name, n.Bound, bound) } key, _ := inv.SealingKeyOf(ctx, record.Name) if n.SealingKey != key { t.Errorf("%s: SealingKey is %q, the inventory's %q", record.Name, n.SealingKey, key) } ports, _ := inv.PortsFor(ctx, record.Name) want := map[string]map[int]int{} for _, a := range ports { if want[a.Module] == nil { want[a.Module] = map[int]int{} } want[a.Module][a.Wanted] = a.Machine } if len(want) != len(n.Ports) || (len(want) > 0 && !reflect.DeepEqual(n.Ports, want)) { t.Errorf("%s: Ports are %v, the inventory's %v", record.Name, n.Ports, want) } // The settings of every module it resolves to, layer for layer. resolved, err := compose.Resolve(in.Input, record.Name) if err != nil { continue } for _, m := range resolved.Plan.Modules { layers, err := inv.SettingsFor(ctx, record.Name, m.Module) if err != nil { t.Fatal(err) } if len(layers) != len(n.Settings[m.Module]) || (len(layers) > 0 && !reflect.DeepEqual(n.Settings[m.Module], layers)) { t.Errorf("%s: the settings of %s are %v, the inventory's %v", record.Name, m.Module, n.Settings[m.Module], layers) } } } if domain := nodeDomain(in.Input, "anchor"); domain != "example.test" { t.Errorf("anchor's PublicDomain is %q", domain) } if got := nodePorts(in.Input, "laptop"); got["files"][8080] == 0 { t.Errorf("laptop's port for files:8080 is not in the Input: %v", got) } } func nodeDomain(in compose.Input, name string) string { n, _ := nodeOf(in, name) return n.PublicDomain } func nodePorts(in compose.Input, name string) map[string]map[int]int { n, _ := nodeOf(in, name) return n.Ports } // 1382/AC3: the seats on record, the tunnels, the adopted tunnel, its carried peers and the nodes joining equal the // inventory's answers; the range and the suffix are read from the environment once, here. func TestTheAdaptersInputCarriesTheNetworkAsTheInventoryHoldsIt(t *testing.T) { open, _ := aMeshWithSecrets(t) ctx := t.Context() inv := open.inventory t.Setenv(OverlayCIDRVar, "10.99.0.0/16") t.Setenv("MESH_INTERNAL_SUFFIX", "mesh.test") in, err := readComposeInput(ctx, open) if err != nil { t.Fatal(err) } holdings, err := inv.Holdings(ctx) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(in.Holdings, holdings) { t.Errorf("Holdings are %v, the inventory's %v", in.Holdings, holdings) } tunnels, err := inv.Tunnels(ctx) if err != nil { t.Fatal(err) } if len(tunnels) != len(in.Tunnels) { t.Errorf("Tunnels are %v, the inventory's %v", in.Tunnels, tunnels) } if _, hub, _, err := inv.AdoptedTunnel(ctx); err != nil { t.Fatal(err) } else if (hub == "") != (in.Adopted == nil) { t.Errorf("Adopted is %v, the inventory's hub is %q", in.Adopted, hub) } carried, err := inv.CarriedPeers(ctx) if err != nil { t.Fatal(err) } if len(carried) != len(in.CarriedPeers) { t.Errorf("CarriedPeers are %v, the inventory's %v", in.CarriedPeers, carried) } joining, err := inv.NodesWithALiveToken(ctx) if err != nil { t.Fatal(err) } if len(joining) != len(in.Joining) { t.Errorf("Joining is %v, the inventory's %v", in.Joining, joining) } if in.OverlayCIDR != "10.99.0.0/16" || in.InternalSuffix != "mesh.test" { t.Errorf("the range and the suffix are %q and %q", in.OverlayCIDR, in.InternalSuffix) } // Read once: what the environment says afterwards moves nothing already read. t.Setenv(OverlayCIDRVar, "10.1.0.0/16") if in.OverlayCIDR != "10.99.0.0/16" { t.Error("the range moved with the environment") } } // The lead's equality finding (build 1792487777757012513): a provider on one machine serves a fact that says ${setting:domain}, // the setting is set for it, and a consumer on another machine takes that fact. The first pass of the adapter resolved every // node with no settings in the Input, so the served fact could not be settled and the whole Input failed. The settings are in // the Input before any node is resolved, and the Input builds, with the same plan for the consumer as the controller's own // composition. It failed first, with the finding's words. func TestAServedFactThatSaysASettingIsResolvedWithTheSettingInTheInput(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, catalogue.Manifest{Module: "step-ca", Version: "1", Provides: []catalogue.Offer{{Name: "acme-ca", Scope: catalogue.ScopeMesh}}, Serves: map[string]map[string]any{"acme-ca": {"domain": "${setting:domain}"}}}) register(t, open, catalogue.Manifest{Module: "route-proxy", Version: "1", Requires: []string{"acme-ca"}}) // The setting is there before the machines are assigned: an assignment resolves the mesh, and a served fact that says // ${setting:domain} resolves only with it. if err := open.inventory.KeepSettings(ctx, "", "step-ca", map[string]any{"domain": "example.test"}); err != nil { t.Fatal(err) } if _, err := assign(ctx, open, "anchor", "step-ca"); err != nil { t.Fatal(err) } if _, err := assign(ctx, open, "laptop", "route-proxy"); err != nil { t.Fatal(err) } in, err := readComposeInput(ctx, open) if err != nil { t.Fatalf("the Input cannot be built: %v", err) } if anchor, _ := nodeOf(in.Input, "anchor"); len(anchor.Settings["step-ca"]) == 0 { t.Fatalf("the Input does not carry the setting of the provider: %v", anchor.Settings) } oldPlan, _, err := resolveNode(ctx, open, "laptop") if err != nil { t.Fatal(err) } newPlan, _, err := resolveNode(withInputCache(ctx), open, "laptop") if err != nil { t.Fatalf("the consumer does not resolve: %v", err) } if !reflect.DeepEqual(oldPlan.Needs, newPlan.Needs) { t.Errorf("the consumer's needs are %+v, the controller's own %+v", newPlan.Needs, oldPlan.Needs) } t.Logf("a served fact that says a setting resolves with the setting in the Input: the Input builds and the consumer's needs equal planFor's") } // A setting that nothing sets, in a fact a provider serves, is not the adapter's to hide: the controller's own composition // refuses it as well (theRestOfTheMesh returns servedOnNode's error), and the adapter refuses with the same words rather than // building an Input that composes a placeholder. func TestAServedFactThatSaysAnUnsetSettingIsRefusedAsTheControllerRefusesIt(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, catalogue.Manifest{Module: "step-ca", Version: "1", Provides: []catalogue.Offer{{Name: "acme-ca", Scope: catalogue.ScopeMesh}}, Serves: map[string]map[string]any{"acme-ca": {"domain": "${setting:domain}"}}}) register(t, open, catalogue.Manifest{Module: "route-proxy", Version: "1", Requires: []string{"acme-ca"}}) // Assigned with the setting there, then the setting is cleared: the mesh the controller holds when someone clears it. if err := open.inventory.KeepSettings(ctx, "", "step-ca", map[string]any{"domain": "example.test"}); err != nil { t.Fatal(err) } if _, err := assign(ctx, open, "anchor", "step-ca"); err != nil { t.Fatal(err) } if _, err := assign(ctx, open, "laptop", "route-proxy"); err != nil { t.Fatal(err) } if err := open.inventory.ClearSettings(ctx, "", "step-ca"); err != nil { t.Fatal(err) } _, _, oldErr := resolveNode(ctx, open, "laptop") _, newErr := readComposeInput(ctx, open) if (oldErr == nil) != (newErr == nil) { t.Fatalf("the controller's composition says %v, the adapter says %v", oldErr, newErr) } if oldErr != nil && !strings.Contains(newErr.Error(), "nothing sets \"domain\"") { t.Errorf("the adapter's words are %q, the controller's %q", newErr, oldErr) } t.Logf("an unset setting in a served fact is refused alike: the controller says %v, the adapter says %v", oldErr != nil, newErr != nil) }