| 1 | // canary_region_test.go |
| 2 | package main |
| 3 | |
| 4 | import ( |
| 5 | "errors" |
| 6 | "testing" |
| 7 | ) |
| 8 | |
| 9 | // records is a fake single-record GET: a host it holds is found, any other |
| 10 | // is a 404 (found false), and err fails every read. |
| 11 | func records(err error, recs ...apiInstanceRecord) (func(string) (apiInstanceRecord, bool, error), *[]string) { |
| 12 | var asked []string |
| 13 | return func(host string) (apiInstanceRecord, bool, error) { |
| 14 | asked = append(asked, host) |
| 15 | if err != nil { |
| 16 | return apiInstanceRecord{}, false, err |
| 17 | } |
| 18 | for _, r := range recs { |
| 19 | if r.Host == host { |
| 20 | return r, true, nil |
| 21 | } |
| 22 | } |
| 23 | return apiInstanceRecord{}, false, nil |
| 24 | }, &asked |
| 25 | } |
| 26 | |
| 27 | func TestCanaryRegionCheck(t *testing.T) { |
| 28 | names := []apiRegionEntry{{Code: "ie", Name: "P-ie"}, {Code: "uk", Name: "P-uk"}} |
| 29 | known := func(d apiAppDefault) *apiAppDefault { |
| 30 | d.RegionKnown, d.PlacementKnown, d.InstanceDomain = true, true, "go.tito.io" |
| 31 | return &d |
| 32 | } |
| 33 | plain := apiRegionsResult{Regions: names, Default: "ie", AppDefault: known(apiAppDefault{Region: "uk", Effective: "uk"})} |
| 34 | fleet := apiRegionsResult{Regions: names, Default: "ie", AppDefault: known(apiAppDefault{Region: "uk", Effective: "fleet/acme", Reason: "fleet"})} |
| 35 | older := apiRegionsResult{Regions: names, Default: "ie"} // no app_default |
| 36 | unread := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{Region: "uk", Effective: "uk", RegionKnown: true, Reason: "policy-unreadable"}} |
| 37 | noDomain := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{Region: "uk", Effective: "uk", RegionKnown: true, PlacementKnown: true}} |
| 38 | regionUnknownFleet := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{PlacementKnown: true, Reason: "fleet", InstanceDomain: "go.tito.io"}} |
| 39 | regionUnknown := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{PlacementKnown: true, InstanceDomain: "go.tito.io"}} |
| 40 | boom := errors.New("timeout") |
| 41 | const host = "c-blog.go.tito.io" |
| 42 | existingUK := apiInstanceRecord{Host: host, Region: "uk"} |
| 43 | existingHome := apiInstanceRecord{Host: host, Region: ""} |
| 44 | other := apiInstanceRecord{Host: "d-blog.go.tito.io", Region: "uk"} |
| 45 | cases := []struct { |
| 46 | name string |
| 47 | res apiRegionsResult |
| 48 | err error |
| 49 | recs []apiInstanceRecord |
| 50 | readErr error |
| 51 | asked string |
| 52 | want string |
| 53 | }{ |
| 54 | {"no flag", fleet, boom, []apiInstanceRecord{existingUK}, nil, "", ""}, |
| 55 | {"new canary", plain, nil, nil, nil, "uk", ""}, |
| 56 | {"another canary is not this one", plain, nil, []apiInstanceRecord{other}, nil, "ie", ""}, |
| 57 | {"existing, same", plain, nil, []apiInstanceRecord{existingUK}, nil, "uk", ""}, |
| 58 | {"existing home read as its name", plain, nil, []apiInstanceRecord{existingHome}, nil, "ie", ""}, |
| 59 | {"existing, different, named by place", plain, nil, []apiInstanceRecord{existingUK}, nil, "ie", canaryRegionMismatch(host, "P-uk")}, |
| 60 | {"fleet, first run", fleet, nil, nil, nil, "uk", canaryRegionOnFleet("blog", "acme")}, |
| 61 | {"fleet, repeat", fleet, nil, []apiInstanceRecord{existingUK}, nil, "uk", canaryRegionOnFleet("blog", "acme")}, |
| 62 | {"older console, first run", older, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, |
| 63 | {"older console, repeat", older, nil, []apiInstanceRecord{existingUK}, nil, "uk", canaryFleetUnknown("blog")}, |
| 64 | {"read failed", plain, boom, nil, nil, "uk", canaryFleetUnknown("blog")}, |
| 65 | // codex plan-R2 3: a 200 whose placement the console could not read. |
| 66 | {"placement unknown", unread, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, |
| 67 | // codex plan-R3 1: the canary's own record is read, never inferred |
| 68 | // from a list; a failed read or an unknown domain refuses. |
| 69 | {"record read failed", plain, nil, []apiInstanceRecord{existingUK}, boom, "uk", canaryRegionUnconfirmed("c")}, |
| 70 | {"no instance domain", noDomain, nil, nil, nil, "uk", canaryRegionUnconfirmed("c")}, |
| 71 | // codex plan-R4 1, R9a: with the stored region unread, effective is |
| 72 | // empty, so a fleet cannot be seen; the flag is refused either way. |
| 73 | {"region unknown, fleet", regionUnknownFleet, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, |
| 74 | {"region unknown", regionUnknown, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, |
| 75 | } |
| 76 | for _, c := range cases { |
| 77 | get, asked := records(c.readErr, c.recs...) |
| 78 | pre, err := canaryRegionCheck(c.res, c.err, "blog", "c", c.asked, get) |
| 79 | got := "" |
| 80 | if err != nil { |
| 81 | got = err.Error() |
| 82 | } |
| 83 | if got != c.want { |
| 84 | t.Errorf("%s: %q, want %q", c.name, got, c.want) |
| 85 | } |
| 86 | if len(*asked) > 1 || (len(*asked) == 1 && (*asked)[0] != host) { |
| 87 | t.Errorf("%s: read %v, want at most one read of %s", c.name, *asked, host) |
| 88 | } |
| 89 | if c.want == "" && c.asked != "" && pre.Host != host { |
| 90 | t.Errorf("%s: checked %q, want %q", c.name, pre.Host, host) |
| 91 | } |
| 92 | if c.want == "" && c.asked != "" && pre.Found != (len(c.recs) > 0 && c.recs[0].Host == host) { |
| 93 | t.Errorf("%s: found = %v, want the read's own answer", c.name, pre.Found) |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | // codex plan-R5 1: with --region, the direct read decides the plan, even |
| 99 | // when the capped list does not show the record at all. |
| 100 | func TestCanaryPlanFor(t *testing.T) { |
| 101 | const host, ch = "c-blog.go.tito.io", "canary/c" |
| 102 | listed := []apiInstanceRecord{{Host: host, Channel: ch}} |
| 103 | cases := []struct { |
| 104 | name string |
| 105 | pre canaryPreflight |
| 106 | recs []apiInstanceRecord |
| 107 | want canaryPlan |
| 108 | }{ |
| 109 | {"no --region: the list decides", canaryPreflight{}, listed, canaryPlanReady}, |
| 110 | {"no --region, not listed", canaryPreflight{}, nil, canaryPlanCreate}, |
| 111 | {"read found it, list did not, same channel", canaryPreflight{Host: host, Found: true, Rec: apiInstanceRecord{Host: host, Channel: ch}}, nil, canaryPlanReady}, |
| 112 | {"read found it, list did not, other channel", canaryPreflight{Host: host, Found: true, Rec: apiInstanceRecord{Host: host, Channel: "canary/old"}}, nil, canaryPlanRechannel}, |
| 113 | {"read found none", canaryPreflight{Host: host}, listed, canaryPlanCreate}, |
| 114 | } |
| 115 | for _, c := range cases { |
| 116 | if got := canaryPlanFor(c.pre, c.recs, host, ch); got != c.want { |
| 117 | t.Errorf("%s: %v, want %v", c.name, got, c.want) |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | func TestInstanceCreatedLine(t *testing.T) { |
| 123 | r := apiInstanceRecord{Host: "a.go.tito.io", Channel: "stable", RegionName: "P-uk"} |
| 124 | if got := instanceCreatedLine(r, ""); got != "a.go.tito.io ("+instanceCreatedSuffix("stable", "P-uk")+")" { |
| 125 | t.Fatalf("got %q", got) |
| 126 | } |
| 127 | env := environmentSuffix("acme") |
| 128 | if got := instanceCreatedLine(r, env); got != "a.go.tito.io ("+instanceCreatedSuffix("stable", "P-uk")+env+")" { |
| 129 | t.Fatalf("with environment = %q", got) |
| 130 | } |
| 131 | r.RegionName = "" |
| 132 | if got := instanceCreatedLine(r, env); got != "a.go.tito.io (channel stable"+env+")" { |
| 133 | t.Fatalf("an old console's record = %q, want today's line byte for byte", got) |
| 134 | } |
| 135 | } |
| 136 | |