carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package lbhealth
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "reflect"
7 "strings"
8 "testing"
9 "time"
10)
11
12var t0 = time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC)
13
14// healthy is a state with every condition met at t0.
15func healthy() *State {
16 s := &State{}
17 s.SetHosting(true)
18 s.MapLoaded()
19 s.KeyLoaded()
20 s.CertsLoaded()
21 s.EdgeBeat(t0)
22 s.RefreshAttempted(t0)
23 s.SetActivator(func() time.Time { return t0 })
24 return s
25}
26
27func TestFailingNamesEachCondition(t *testing.T) {
28 if got := healthy().Failing(t0); len(got) != 0 {
29 t.Fatalf("healthy box fails %v", got)
30 }
31 if got := (&State{}).Failing(t0); !reflect.DeepEqual(got, []string{CondHosting, CondForwarding, CondCerts, CondEdge, CondActivator, CondRefresh}) {
32 t.Fatalf("zero state fails %v", got)
33 }
34 var nilState *State
35 if got := nilState.Failing(t0); len(got) != 6 {
36 t.Fatalf("nil state fails %v", got)
37 }
38 cases := []struct {
39 name string
40 spoil func(*State)
41 now time.Time
42 want string
43 }{
44 {"joining", func(s *State) { s.SetHosting(false) }, t0, CondHosting},
45 {"map never loaded", func(s *State) { s.mapLoaded.Store(false) }, t0, CondForwarding},
46 {"key never loaded", func(s *State) { s.keyLoaded.Store(false) }, t0, CondForwarding},
47 {"no certificate cache", func(s *State) { s.certs.Store(false) }, t0, CondCerts},
48 {"edge beat stale", func(s *State) { s.EdgeBeat(t0.Add(-61 * time.Second)) }, t0, CondEdge},
49 {"activator beat stale", func(s *State) { s.SetActivator(func() time.Time { return t0.Add(-61 * time.Second) }) }, t0, CondActivator},
50 {"no activator", func(s *State) { s.SetActivator(nil) }, t0, CondActivator},
51 {"refresh stuck", func(s *State) { s.RefreshAttempted(t0.Add(-5*time.Minute - time.Second)) }, t0, CondRefresh},
52 }
53 for _, c := range cases {
54 t.Run(c.name, func(t *testing.T) {
55 s := healthy()
56 c.spoil(s)
57 if got := s.Failing(c.now); !reflect.DeepEqual(got, []string{c.want}) {
58 t.Fatalf("Failing = %v, want [%s]", got, c.want)
59 }
60 })
61 }
62 // The windows are inclusive: a beat exactly 60s old and a refresh
63 // exactly 5 minutes old still count.
64 s := healthy()
65 s.EdgeBeat(t0.Add(-HeartbeatWindow))
66 s.RefreshAttempted(t0.Add(-RefreshWindow))
67 if got := s.Failing(t0); len(got) != 0 {
68 t.Fatalf("boundary fails %v", got)
69 }
70}
71
72// A bucket outage after arming: refreshes keep failing, but each failed
73// attempt is still a completed attempt, and the latches hold. The box
74// stays healthy (A2-3, A3-3).
75func TestBucketOutageAfterArmingStaysHealthy(t *testing.T) {
76 s := healthy()
77 for i := 1; i <= 20; i++ {
78 now := t0.Add(time.Duration(i) * 30 * time.Second)
79 s.RefreshAttempted(now) // a failed attempt: no latch is re-set
80 s.EdgeBeat(now)
81 s.SetActivator(func() time.Time { return now })
82 if got := s.Failing(now); len(got) != 0 {
83 t.Fatalf("after %d failed refreshes: %v", i, got)
84 }
85 }
86}
87
88func TestServe(t *testing.T) {
89 get := func(s *State, method, target, host string) (*httptest.ResponseRecorder, bool) {
90 w := httptest.NewRecorder()
91 r := httptest.NewRequest(method, target, nil)
92 r.Host = host
93 return w, Serve(w, r, s, t0)
94 }
95 w, ok := get(healthy(), http.MethodGet, Path, "10.0.1.7")
96 if !ok || w.Code != http.StatusOK || w.Body.String() != "ok\n" || w.Header().Get("Cache-Control") != "no-store" {
97 t.Fatalf("healthy GET: ok=%v %d %q %v", ok, w.Code, w.Body.String(), w.Header())
98 }
99 w, ok = get(healthy(), http.MethodHead, Path, "any.example.com")
100 if !ok || w.Code != http.StatusOK || w.Body.Len() != 0 {
101 t.Fatalf("healthy HEAD: ok=%v %d %q", ok, w.Code, w.Body.String())
102 }
103 joining := healthy()
104 joining.SetHosting(false)
105 w, ok = get(joining, http.MethodGet, Path, "10.0.1.7")
106 if !ok || w.Code != http.StatusServiceUnavailable || !strings.Contains(w.Body.String(), CondHosting) {
107 t.Fatalf("joining GET: ok=%v %d %q", ok, w.Code, w.Body.String())
108 }
109 for _, c := range []struct{ method, target string }{
110 {http.MethodPost, Path},
111 {http.MethodGet, Path + "/"},
112 {http.MethodGet, "/.carlos/lb-healthz"},
113 {http.MethodGet, "/"},
114 } {
115 if _, ok := get(healthy(), c.method, c.target, "10.0.1.7"); ok {
116 t.Fatalf("%s %s answered", c.method, c.target)
117 }
118 }
119}
120
121func TestNilStateIgnoresUpdates(t *testing.T) {
122 var s *State
123 s.SetHosting(true)
124 s.MapLoaded()
125 s.KeyLoaded()
126 s.CertsLoaded()
127 s.EdgeBeat(t0)
128 s.RefreshAttempted(t0)
129 s.SetActivator(func() time.Time { return t0 })
130 if s.Hosting() {
131 t.Fatal("nil state hosting")
132 }
133}
134