blob: 1cfb46e86839b3548f93913c42729211186fbf7f [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanskic64ba1e2023-03-15 19:15:13 +01004package tinylb
5
6import (
7 "bufio"
8 "fmt"
9 "io"
10 "net"
11 "strings"
12 "testing"
13 "time"
14
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020015 "source.monogon.dev/osbase/event/memory"
16 "source.monogon.dev/osbase/supervisor"
Serge Bazanskic64ba1e2023-03-15 19:15:13 +010017)
18
19func TestLoadbalancer(t *testing.T) {
20 v := memory.Value[BackendSet]{}
Tim Windelschmidt3b5a9172024-05-23 13:33:52 +020021 var set BackendSet
Serge Bazanskic64ba1e2023-03-15 19:15:13 +010022 v.Set(set.Clone())
23
24 ln, err := net.Listen("tcp", ":0")
25 if err != nil {
26 t.Fatalf("Listen failed: %v", err)
27 }
28 s := Server{
29 Provider: &v,
30 Listener: ln,
31 }
32 supervisor.TestHarness(t, s.Run)
33
34 connect := func() net.Conn {
35 conn, err := net.Dial("tcp", ln.Addr().String())
36 if err != nil {
37 t.Fatalf("Connection failed: %v", err)
38 }
39 return conn
40 }
41
42 c := connect()
43 buf := make([]byte, 128)
44 if _, err := c.Read(buf); err == nil {
45 t.Fatalf("Expected error on read (no backends yet)")
46 }
47
48 // Now add a backend and expect it to be served.
49 makeBackend := func(hello string) net.Listener {
50 aln, err := net.Listen("tcp", ":0")
51 if err != nil {
52 t.Fatalf("Failed to make backend listener: %v", err)
53 }
54 // Start backend.
55 go func() {
56 for {
57 c, err := aln.Accept()
58 if err != nil {
59 return
60 }
61 // For each connection, keep writing 'hello' over and over, newline-separated.
62 go func() {
63 defer c.Close()
64 for {
65 if _, err := fmt.Fprintf(c, "%s\n", hello); err != nil {
66 return
67 }
68 time.Sleep(100 * time.Millisecond)
69 }
70 }()
71 }
72 }()
73 addr := aln.Addr().(*net.TCPAddr)
74 set.Insert(hello, &SimpleTCPBackend{Remote: addr.AddrPort().String()})
75 v.Set(set.Clone())
76 return aln
77 }
78
79 as1 := makeBackend("a")
80 defer as1.Close()
81
82 for {
83 c = connect()
84 _, err := c.Read(buf)
85 c.Close()
86 if err == nil {
87 break
88 }
89 }
90
91 measure := func() map[string]int {
92 res := make(map[string]int)
93 for {
94 count := 0
95 for _, v := range res {
96 count += v
97 }
98 if count >= 20 {
99 return res
100 }
101
102 c := connect()
103 b := bufio.NewScanner(c)
104 if !b.Scan() {
105 err := b.Err()
106 if err == nil {
107 err = io.EOF
108 }
109 t.Fatalf("Scan failed: %v", err)
110 }
111 v := b.Text()
112 res[v]++
113 c.Close()
114 }
115 }
116
117 m := measure()
118 if m["a"] < 20 {
119 t.Errorf("Expected only one backend, got: %v", m)
120 }
121
122 as2 := makeBackend("b")
123 defer as2.Close()
124
125 as3 := makeBackend("c")
126 defer as3.Close()
127
128 as4 := makeBackend("d")
129 defer as4.Close()
130
131 m = measure()
132 for _, id := range []string{"a", "b", "c", "d"} {
133 if want, got := 4, m[id]; got < want {
134 t.Errorf("Expected at least %d responses from %s, got %d", want, id, got)
135 }
136 }
137
138 // Test killing backend connections on backend removal.
139 // Open a bunch of connections to 'a'.
140 var conns []*bufio.Scanner
141 for len(conns) < 5 {
142 c := connect()
143 b := bufio.NewScanner(c)
144 b.Scan()
145 if b.Text() != "a" {
146 c.Close()
147 } else {
148 conns = append(conns, b)
149 }
150 }
151
152 // Now remove the 'a' backend.
153 set.Delete("a")
154 v.Set(set.Clone())
155 // All open connections should now get killed.
156 for _, b := range conns {
157 start := time.Now().Add(time.Second)
158 for b.Scan() {
159 if time.Now().After(start) {
160 t.Errorf("Connection still alive")
161 break
162 }
163 }
164 }
165}
166
167func BenchmarkLB(b *testing.B) {
168 v := memory.Value[BackendSet]{}
Tim Windelschmidt3b5a9172024-05-23 13:33:52 +0200169 var set BackendSet
Serge Bazanskic64ba1e2023-03-15 19:15:13 +0100170 v.Set(set.Clone())
171
172 ln, err := net.Listen("tcp", ":0")
173 if err != nil {
174 b.Fatalf("Listen failed: %v", err)
175 }
176 s := Server{
177 Provider: &v,
178 Listener: ln,
179 }
180 supervisor.TestHarness(b, s.Run)
181
182 makeBackend := func(hello string) net.Listener {
183 aln, err := net.Listen("tcp", ":0")
184 if err != nil {
185 b.Fatalf("Failed to make backend listener: %v", err)
186 }
187 // Start backend.
188 go func() {
189 for {
190 c, err := aln.Accept()
191 if err != nil {
192 return
193 }
194 go func() {
195 fmt.Fprintf(c, "%s\n", hello)
196 c.Close()
197 }()
198 }
199 }()
200 addr := aln.Addr().(*net.TCPAddr)
201 set.Insert(hello, &SimpleTCPBackend{Remote: addr.AddrPort().String()})
202 v.Set(set.Clone())
203 return aln
204 }
205 var backends []net.Listener
206 for i := 0; i < 10; i++ {
207 b := makeBackend(fmt.Sprintf("backend%d", i))
208 backends = append(backends, b)
209 }
210
211 defer func() {
212 for _, b := range backends {
213 b.Close()
214 }
215 }()
216
217 b.ResetTimer()
218 b.RunParallel(func(pb *testing.PB) {
219 for pb.Next() {
220 conn, err := net.Dial("tcp", ln.Addr().String())
221 if err != nil {
222 b.Fatalf("Connection failed: %v", err)
223 }
224 buf := bufio.NewScanner(conn)
225 buf.Scan()
226 if !strings.HasPrefix(buf.Text(), "backend") {
227 b.Fatalf("Invalid backend response: %q", buf.Text())
228 }
229 conn.Close()
230 }
231 })
232 b.StopTimer()
233}