diff --git a/cmd/mesh-controller/compose_equal_test.go b/cmd/mesh-controller/compose_equal_test.go deleted file mode 100644 index 5ac92d7c..00000000 --- a/cmd/mesh-controller/compose_equal_test.go +++ /dev/null @@ -1,341 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "sort" - "strings" - "testing" -) - -// The comparison of the controller's composition and mesh-sdk's (novox/hq ADR 0453 clause 3, design 47 R4b, issue 1384), -// the part of it that needs no store. The test that raises a mesh and composes every machine both ways is -// compose_equal_integration_test.go; both are deleted with the controller's own composition (issue 1389). - -// composedBoth is what one composition of a machine said, in the terms the two paths are compared in. -type composedBoth struct { - Err string - Problems []string - LeftOut map[string]string - Withheld []string - Unbound []string - Unplaced []string - Handed []string - // Foreseen are the own secrets the composition names foreseen, as `module/name`. - Foreseen []string - Body []byte -} - -// capturedFrom is a sendable, its validator problems and the error that came with it, as compared. -func capturedFrom(d sendable, problems []string, err error) composedBoth { - if err != nil { - return composedBoth{Err: err.Error()} - } - c := composedBoth{Problems: append([]string(nil), problems...), LeftOut: d.leftOutWhy, - Unplaced: append([]string(nil), d.unplaced...), Handed: append([]string(nil), d.handedOnly...), - Foreseen: append([]string(nil), d.foreseen...)} - for _, o := range d.withheld { - c.Withheld = append(c.Withheld, o.String()) - } - for _, u := range d.unbound { - c.Unbound = append(c.Unbound, u.String()) - } - sort.Strings(c.Problems) - sort.Strings(c.Withheld) - sort.Strings(c.Unbound) - sort.Strings(c.Unplaced) - sort.Strings(c.Handed) - sort.Strings(c.Foreseen) - c.Body, _ = d.Body() - return c -} - -// bothWaysDiffer says how two compositions of one machine differ, one line for each field that does, naming the machine and -// the comparison. A machine both refuse alike is no difference. withBody compares the declaration byte for byte and names -// the first resource that differs. -func bothWaysDiffer(machine, comparison string, old, sdk composedBoth, withBody bool) []string { - var out []string - say := func(field string, a, b any) { - out = append(out, fmt.Sprintf("%s, %s: %s differs: the controller's %v, mesh-sdk's %v", machine, comparison, field, a, b)) - } - if old.Err != sdk.Err { - say("the refusal", quoted(old.Err), quoted(sdk.Err)) - return out - } - if old.Err != "" { - return nil - } - for _, f := range []struct { - name string - old, sdk []string - }{ - {"the validator's problems", old.Problems, sdk.Problems}, - {"Withheld", old.Withheld, sdk.Withheld}, - {"Unbound", old.Unbound, sdk.Unbound}, - {"Unplaced", old.Unplaced, sdk.Unplaced}, - {"HandedOnly", old.Handed, sdk.Handed}, - {"the own secrets named foreseen", old.Foreseen, sdk.Foreseen}, - } { - if !sameStrings(f.old, f.sdk) { - say(f.name, f.old, f.sdk) - } - } - if !sameLeftOut(old.LeftOut, sdk.LeftOut) { - say("LeftOut and its reasons", old.LeftOut, sdk.LeftOut) - } - if withBody && !bytes.Equal(old.Body, sdk.Body) { - out = append(out, fmt.Sprintf("%s, %s: the declaration differs byte for byte; %s", machine, comparison, - firstDifferingResource(old.Body, sdk.Body))) - } - return out -} - -func quoted(s string) string { - if s == "" { - return "none" - } - return fmt.Sprintf("%q", s) -} - -func sameStrings(a, b []string) bool { - if len(a) == 0 && len(b) == 0 { - return true - } - return reflect.DeepEqual(a, b) -} - -func sameLeftOut(a, b map[string]string) bool { - if len(a) == 0 && len(b) == 0 { - return true - } - return reflect.DeepEqual(a, b) -} - -// firstDifferingResource names the first resource of two declaration bodies that is not the same, and the first field of it. -func firstDifferingResource(a, b []byte) string { - var x, y struct { - Resources []map[string]any `json:"resources"` - } - if json.Unmarshal(a, &x) != nil || json.Unmarshal(b, &y) != nil { - return "a body is not a declaration" - } - byID := func(rs []map[string]any) map[string]map[string]any { - out := map[string]map[string]any{} - for _, r := range rs { - out[fmt.Sprint(r["id"])] = r - } - return out - } - ids := map[string]bool{} - left, right := byID(x.Resources), byID(y.Resources) - for id := range left { - ids[id] = true - } - for id := range right { - ids[id] = true - } - var order []string - for id := range ids { - order = append(order, id) - } - sort.Strings(order) - for _, id := range order { - l, inLeft := left[id] - r, inRight := right[id] - switch { - case !inRight: - return fmt.Sprintf("resource %s is only in the controller's", id) - case !inLeft: - return fmt.Sprintf("resource %s is only in mesh-sdk's", id) - } - var fields []string - for k := range l { - fields = append(fields, k) - } - for k := range r { - if _, have := l[k]; !have { - fields = append(fields, k) - } - } - sort.Strings(fields) - for _, k := range fields { - if !reflect.DeepEqual(l[k], r[k]) { - return fmt.Sprintf("resource %s, field %s: the controller's %v, mesh-sdk's %v", id, k, l[k], r[k]) - } - } - } - if !reflect.DeepEqual(left, right) { - return "the envelopes differ" - } - return "the resources are the same; the envelope differs" -} - -// made is what a send made in a store, by key and never by value: the machine ports given, the pair credentials and the -// own secrets. -type made struct { - Ports []string - Pairs []string - OwnSecrets []string -} - -// madeDiffer says how what two sends made differs, by entry: for each kind, the entries only the controller made, the ones only -// mesh-sdk made, and the ones both made with a different value. Never a whole list. The entries of a machine in refused (a -// machine whose send is refused on either side, which sends nothing) are left out of both. -func madeDiffer(comparison string, old, sdk made, refused map[string]bool) []string { - var out []string - for _, f := range []struct { - name string - old, sdk []string - split func(string) (key, value string) - nodes func(string) []string - }{ - {"the ports allocated", old.Ports, sdk.Ports, splitAt("->"), firstWord}, - {"the pair credentials made", old.Pairs, sdk.Pairs, func(l string) (string, string) { return l, "" }, pairNodes}, - {"the own secrets made", old.OwnSecrets, sdk.OwnSecrets, splitAtLast(" ("), firstWord}, - } { - left, right := entriesOf(f.old, f.split, f.nodes, refused), entriesOf(f.sdk, f.split, f.nodes, refused) - var onlyOld, onlySDK, different []string - for key, v := range left { - if w, both := right[key]; !both { - onlyOld = append(onlyOld, key) - } else if w != v { - different = append(different, fmt.Sprintf("%s (the controller's %s, mesh-sdk's %s)", key, v, w)) - } - } - for key := range right { - if _, both := left[key]; !both { - onlySDK = append(onlySDK, key) - } - } - sort.Strings(onlyOld) - sort.Strings(onlySDK) - sort.Strings(different) - var said []string - if len(onlyOld) > 0 { - said = append(said, "made only by the controller: "+strings.Join(onlyOld, ", ")) - } - if len(onlySDK) > 0 { - said = append(said, "made only by mesh-sdk: "+strings.Join(onlySDK, ", ")) - } - if len(different) > 0 { - said = append(said, "made by both with a different value: "+strings.Join(different, ", ")) - } - if len(said) > 0 { - out = append(out, fmt.Sprintf("%s: %s differ: %s", comparison, f.name, strings.Join(said, "; "))) - } - } - return out -} - -// entriesOf is the entries of a list by key, those of a machine in skip left out. -func entriesOf(lines []string, split func(string) (string, string), nodes func(string) []string, skip map[string]bool) map[string]string { - out := map[string]string{} - for _, l := range lines { - skipped := false - for _, n := range nodes(l) { - skipped = skipped || skip[n] - } - if skipped { - continue - } - k, v := split(l) - out[k] = v - } - return out -} - -func splitAt(sep string) func(string) (string, string) { - return func(l string) (string, string) { - k, v, _ := strings.Cut(l, sep) - return k, v - } -} - -func splitAtLast(sep string) func(string) (string, string) { - return func(l string) (string, string) { - if i := strings.LastIndex(l, sep); i >= 0 { - return l[:i], l[i+1:] - } - return l, "" - } -} - -func firstWord(l string) []string { - f := strings.Fields(l) - if len(f) == 0 { - return nil - } - return f[:1] -} - -// pairNodes are the consumer and the provider of a pair credential line ("name module on consumer from provider local=..."). -func pairNodes(l string) []string { - f := strings.Fields(l) - var out []string - for i := 0; i+1 < len(f); i++ { - if f[i] == "on" || f[i] == "from" { - out = append(out, f[i+1]) - } - } - return out -} - -// 1384/AC3: the comparison itself. A composition that differs by one setting's worth is named, with its machine and the -// resource and field that first differ; two that agree, and two that refuse alike, are not differences. It failed first, -// before bothWaysDiffer read the body: a changed setting went unseen. -func TestTheComparisonNamesTheMachineWhoseCompositionDiffers(t *testing.T) { - body := func(port string) []byte { - raw, err := json.Marshal(map[string]any{"declaration": 1, "resources": []map[string]any{ - {"id": "files.config", "type": "file", "content": "port=" + port}, - {"id": "files.dir", "type": "directory"}}}) - if err != nil { - t.Fatal(err) - } - return raw - } - same := composedBoth{Body: body("8080"), Foreseen: []string{"files/admin"}} - if got := bothWaysDiffer("laptop", "Reading", same, same, true); len(got) != 0 { - t.Fatalf("a composition differs from itself: %v", got) - } - changed := composedBoth{Body: body("8081"), Foreseen: []string{"files/admin"}} - got := bothWaysDiffer("laptop", "Reading", same, changed, true) - if len(got) != 1 || !strings.Contains(got[0], "laptop, Reading") || - !strings.Contains(got[0], "resource files.config, field content") || - !strings.Contains(got[0], "port=8080") || !strings.Contains(got[0], "port=8081") { - t.Fatalf("the changed setting is not named with its machine, resource and field: %v", got) - } - // A field of the lists. - other := composedBoth{Body: body("8080"), Foreseen: []string{"files/admin", "files/token"}} - if got := bothWaysDiffer("anchor", "Foreseeing", same, other, false); len(got) != 1 || - !strings.Contains(got[0], "anchor, Foreseeing: the own secrets named foreseen differs") { - t.Fatalf("a secret named foreseen on one path alone is not named: %v", got) - } - // Refused alike is said, not failed; refused differently is a difference. - a, b := composedBoth{Err: "files on laptop needs a credential"}, composedBoth{Err: "files on laptop needs a credential"} - if got := bothWaysDiffer("laptop", "Foreseeing", a, b, true); len(got) != 0 { - t.Fatalf("two compositions that refuse a machine alike differ: %v", got) - } - if got := bothWaysDiffer("laptop", "Foreseeing", a, composedBoth{Body: body("1")}, true); len(got) != 1 || - !strings.Contains(got[0], "the refusal differs") { - t.Fatalf("a machine one path refuses and the other composes is not named: %v", got) - } - // And what a send made: only the entries that differ, per machine, never the whole lists. - long := made{Ports: []string{"anchor files:80->8080", "anchor web:81->8081", "laptop db:5432->5432"}, - OwnSecrets: []string{"anchor files/admin (made)", "laptop db/root (made)"}} - other2 := made{Ports: []string{"anchor files:80->8080", "anchor web:81->9091", "laptop db:5432->5432", "laptop cache:6379->6379"}, - OwnSecrets: []string{"anchor files/admin (made)", "laptop db/root (accepted)"}} - got = madeDiffer("the send", long, other2, nil) - if len(got) != 2 || !strings.Contains(got[0], "made only by mesh-sdk: laptop cache:6379") || - !strings.Contains(got[0], "anchor web:81 (the controller's 8081, mesh-sdk's 9091)") || - strings.Contains(got[0], "anchor files:80") || strings.Contains(got[0], "laptop db:5432") || - !strings.Contains(got[1], "laptop db/root (the controller's (made), mesh-sdk's (accepted))") || - strings.Contains(got[1], "anchor files/admin") { - t.Fatalf("the send's differences are not only the differing entries: %v", got) - } - // A machine refused on either side is left out of both. - if got := madeDiffer("the send", long, other2, map[string]bool{"anchor": true, "laptop": true}); len(got) != 0 { - t.Fatalf("a refused machine is compared: %v", got) - } -} diff --git a/cmd/mesh-controller/compose_made_integration_test.go b/cmd/mesh-controller/compose_made_integration_test.go index 2af13fa4..ba875ea0 100644 --- a/cmd/mesh-controller/compose_made_integration_test.go +++ b/cmd/mesh-controller/compose_made_integration_test.go @@ -8,6 +8,14 @@ import ( "sort" ) +// made is what a send made in a store, by key and never by value: the machine ports given, the pair credentials and the +// own secrets. +type made struct { + Ports []string + Pairs []string + OwnSecrets []string +} + // madeIn is what a store holds that a send makes, by key and never by value. func madeIn(ctx context.Context, open *stores) (made, error) { var out made