| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "testing" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/boxid" |
| 8 | ) |
| 9 | |
| 10 | // The predicate and the choice are tested in internal/boxchoice. These |
| 11 | // tests hold console's conversion to it: every field the predicate reads |
| 12 | // must survive boxSnapshot, or a box would silently read as missing it. |
| 13 | |
| 14 | // haBox is an eligible box's edges file in placement "uk". |
| 15 | func haBox(id string, mut func(m map[string]any)) string { |
| 16 | m := map[string]any{ |
| 17 | "v": 1, "box": id, "box_id": id, "eip": "1.2.3.4", "region": "eu-west-2", "placement": "uk", |
| 18 | "hosts": map[string]any{}, "hibernation_configured": true, "serves_shared_pool": true, |
| 19 | "accepts_inbound": true, "secrets_key_fp": testKeyFP, "capabilities": []string{"region-ha-1"}, |
| 20 | "headroom": map[string]any{"mem_free_mib": 1000, "disk_free_mib": 2000, "awake": 3}, |
| 21 | } |
| 22 | if mut != nil { |
| 23 | mut(m) |
| 24 | } |
| 25 | b, _ := json.Marshal(m) |
| 26 | return string(b) |
| 27 | } |
| 28 | |
| 29 | func ids(cs []boxCandidate) []string { |
| 30 | out := make([]string, len(cs)) |
| 31 | for i, c := range cs { |
| 32 | out[i] = c.BoxID |
| 33 | } |
| 34 | return out |
| 35 | } |
| 36 | |
| 37 | // rosterFor enrolls each box in placement "uk" as an instance in eu-west-2. |
| 38 | func rosterFor(boxes ...string) map[string]rosterBox { |
| 39 | out := map[string]rosterBox{} |
| 40 | for _, b := range boxes { |
| 41 | out[b] = rosterBox{Placement: "uk", Cloud: boxid.Cloud{Provider: "aws", Account: "1", Region: "eu-west-2", InstanceID: "i-" + b}} |
| 42 | } |
| 43 | return out |
| 44 | } |
| 45 | |
| 46 | func TestEligibleBoxesCarriesEveryFieldThePredicateReads(t *testing.T) { |
| 47 | del := func(k string) func(map[string]any) { return func(m map[string]any) { delete(m, k) } } |
| 48 | set := func(k string, v any) func(map[string]any) { return func(m map[string]any) { m[k] = v } } |
| 49 | cases := []struct { |
| 50 | name string |
| 51 | mut func(map[string]any) |
| 52 | want bool |
| 53 | }{ |
| 54 | {"all present", nil, true}, |
| 55 | {"no dial address", set("eip", ""), false}, |
| 56 | {"hibernation unconfigured", set("hibernation_configured", false), false}, |
| 57 | {"hibernation unknown", del("hibernation_configured"), false}, |
| 58 | {"refuses the shared pool", set("serves_shared_pool", false), false}, |
| 59 | {"refuses inbound", set("accepts_inbound", false), false}, |
| 60 | {"wrong secrets key", set("secrets_key_fp", "sha256:other"), false}, |
| 61 | {"no capability", del("capabilities"), false}, |
| 62 | {"draining", set("draining", true), false}, |
| 63 | {"another placement", set("placement", "us"), false}, |
| 64 | {"another region", set("region", "us-east-1"), false}, |
| 65 | {"box_id names no roster entry", set("box_id", "i-0zzz"), false}, |
| 66 | } |
| 67 | for _, tc := range cases { |
| 68 | t.Run(tc.name, func(t *testing.T) { |
| 69 | snap := views(t, haBox("i-0aaa", tc.mut)) |
| 70 | got := eligibleBoxes(snap, rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil) |
| 71 | if (len(got) == 1) != tc.want { |
| 72 | t.Fatalf("eligible = %v, want %v", ids(got), tc.want) |
| 73 | } |
| 74 | }) |
| 75 | } |
| 76 | got := eligibleBoxes(views(t, haBox("i-0aaa", nil)), rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil) |
| 77 | if want := (boxCandidate{BoxID: "i-0aaa", Region: "eu-west-2", MemFreeMiB: 1000, DiskFreeMiB: 2000, Awake: 3}); len(got) != 1 || got[0] != want { |
| 78 | t.Fatalf("got %+v, want %+v", got, want) |
| 79 | } |
| 80 | if got := eligibleBoxes(markStale(views(t, haBox("i-0aaa", nil)), "i-0aaa"), rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil); len(got) != 0 { |
| 81 | t.Fatalf("a stale box is eligible: %v", ids(got)) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | // Both halves of console's completeness carry over: an unread edges file, |
| 86 | // and unknown liveness. |
| 87 | func TestEligibleBoxesAnIncompleteSnapshotOffersNothing(t *testing.T) { |
| 88 | for _, bend := range []func(*edgesSnapshot){ |
| 89 | func(s *edgesSnapshot) { s.Complete = false }, |
| 90 | func(s *edgesSnapshot) { s.BoxesComplete = false }, |
| 91 | } { |
| 92 | snap := views(t, haBox("i-0aaa", nil)) |
| 93 | bend(&snap) |
| 94 | if got := eligibleBoxes(snap, rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil); len(got) != 0 { |
| 95 | t.Fatalf("got %v from an incomplete snapshot", ids(got)) |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | func TestChooseBoxIsBoxchoiceChoose(t *testing.T) { |
| 101 | a, b := boxCandidate{BoxID: "i-0aaa"}, boxCandidate{BoxID: "i-0bbb"} |
| 102 | got, ok := chooseBox("shop.bab.oncarlos.com", []boxCandidate{a, b}, map[string]boxLoad{"i-0aaa": {Hosts: 5}, "i-0bbb": {Hosts: 2}}, "bab/jam") |
| 103 | if !ok || got.BoxID != "i-0bbb" { |
| 104 | t.Fatalf("got %s ok=%v, want i-0bbb", got.BoxID, ok) |
| 105 | } |
| 106 | } |
| 107 | |