1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package healthcheck_test
16
17 import (
18 "context"
19 "crypto/tls"
20 "crypto/x509"
21 "errors"
22 "net"
23 "net/http"
24 "testing"
25 "time"
26
27 "github.com/GoogleCloudPlatform/cloudsql-proxy/cmd/cloud_sql_proxy/internal/healthcheck"
28 "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"
29 )
30
31 const (
32 startupPath = "/startup"
33 livenessPath = "/liveness"
34 readinessPath = "/readiness"
35 testPort = "8090"
36 )
37
38 type fakeCertSource struct{}
39
40 func (cs *fakeCertSource) Local(instance string) (tls.Certificate, error) {
41 return tls.Certificate{
42 Leaf: &x509.Certificate{
43 NotAfter: time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC),
44 },
45 }, nil
46 }
47
48 func (cs *fakeCertSource) Remote(instance string) (cert *x509.Certificate, addr, name, version string, err error) {
49 return &x509.Certificate{}, "fake address", "fake name", "fake version", nil
50 }
51
52 type failingCertSource struct{}
53
54 func (cs *failingCertSource) Local(instance string) (tls.Certificate, error) {
55 return tls.Certificate{}, errors.New("failed")
56 }
57
58 func (cs *failingCertSource) Remote(instance string) (cert *x509.Certificate, addr, name, version string, err error) {
59 return nil, "", "", "", errors.New("failed")
60 }
61
62
63 func TestLivenessPasses(t *testing.T) {
64 s, err := healthcheck.NewServer(&proxy.Client{}, testPort, nil)
65 if err != nil {
66 t.Fatalf("Could not initialize health check: %v", err)
67 }
68 defer s.Close(context.Background())
69
70 resp, err := http.Get("http://localhost:" + testPort + livenessPath)
71 if err != nil {
72 t.Fatalf("HTTP GET failed: %v", err)
73 }
74 if resp.StatusCode != http.StatusOK {
75 t.Errorf("want %v, got %v", http.StatusOK, resp.StatusCode)
76 }
77 }
78
79 func TestLivenessFails(t *testing.T) {
80 c := &proxy.Client{
81 Certs: &failingCertSource{},
82 Dialer: func(string, string) (net.Conn, error) {
83 return nil, errors.New("error")
84 },
85 }
86
87 _, err := c.Dial("proj:region:instance")
88 if err == nil {
89 t.Fatalf("expected Dial to fail, but it succeeded")
90 }
91
92 s, err := healthcheck.NewServer(c, testPort, []string{"proj:region:instance"})
93 if err != nil {
94 t.Fatalf("Could not initialize health check: %v", err)
95 }
96 defer s.Close(context.Background())
97
98 resp, err := http.Get("http://localhost:" + testPort + livenessPath)
99 if err != nil {
100 t.Fatalf("HTTP GET failed: %v", err)
101 }
102 defer resp.Body.Close()
103 want := http.StatusServiceUnavailable
104 if got := resp.StatusCode; got != want {
105 t.Errorf("want %v, got %v", want, got)
106 }
107 }
108
109
110
111 func TestStartupPass(t *testing.T) {
112 s, err := healthcheck.NewServer(&proxy.Client{}, testPort, nil)
113 if err != nil {
114 t.Fatalf("Could not initialize health check: %v", err)
115 }
116 defer s.Close(context.Background())
117
118
119 s.NotifyStarted()
120
121 resp, err := http.Get("http://localhost:" + testPort + startupPath)
122 if err != nil {
123 t.Fatalf("HTTP GET failed: %v", err)
124 }
125 if resp.StatusCode != http.StatusOK {
126 t.Errorf("%v: want %v, got %v", startupPath, http.StatusOK, resp.StatusCode)
127 }
128
129 resp, err = http.Get("http://localhost:" + testPort + readinessPath)
130 if err != nil {
131 t.Fatalf("HTTP GET failed: %v", err)
132 }
133 if resp.StatusCode != http.StatusOK {
134 t.Errorf("%v: want %v, got %v", readinessPath, http.StatusOK, resp.StatusCode)
135 }
136 }
137
138
139
140 func TestStartupFail(t *testing.T) {
141 s, err := healthcheck.NewServer(&proxy.Client{}, testPort, nil)
142 if err != nil {
143 t.Fatalf("Could not initialize health check: %v", err)
144 }
145 defer s.Close(context.Background())
146
147 resp, err := http.Get("http://localhost:" + testPort + startupPath)
148 if err != nil {
149 t.Fatalf("HTTP GET failed: %v", err)
150 }
151 if resp.StatusCode != http.StatusServiceUnavailable {
152 t.Errorf("%v: want %v, got %v", startupPath, http.StatusOK, resp.StatusCode)
153 }
154
155 resp, err = http.Get("http://localhost:" + testPort + readinessPath)
156 if err != nil {
157 t.Fatalf("HTTP GET failed: %v", err)
158 }
159 if resp.StatusCode != http.StatusServiceUnavailable {
160 t.Errorf("%v: want %v, got %v", readinessPath, http.StatusOK, resp.StatusCode)
161 }
162 }
163
164
165
166 func TestMaxConnectionsReached(t *testing.T) {
167 c := &proxy.Client{
168 MaxConnections: 1,
169 }
170 s, err := healthcheck.NewServer(c, testPort, nil)
171 if err != nil {
172 t.Fatalf("Could not initialize health check: %v", err)
173 }
174 defer s.Close(context.Background())
175
176 s.NotifyStarted()
177 c.ConnectionsCounter = c.MaxConnections
178
179 resp, err := http.Get("http://localhost:" + testPort + readinessPath)
180 if err != nil {
181 t.Fatalf("HTTP GET failed: %v", err)
182 }
183 if resp.StatusCode != http.StatusServiceUnavailable {
184 t.Errorf("want %v, got %v", http.StatusServiceUnavailable, resp.StatusCode)
185 }
186 }
187
188
189
190 func TestDialFail(t *testing.T) {
191 tests := map[string]struct {
192 insts []string
193 }{
194 "Single instance": {insts: []string{"project:region:instance"}},
195 "Multiple instances": {insts: []string{"project:region:instance-1", "project:region:instance-2", "project:region:instance-3"}},
196 }
197
198 c := &proxy.Client{
199 Certs: &fakeCertSource{},
200 Dialer: func(string, string) (net.Conn, error) {
201 return nil, errors.New("error")
202 },
203 }
204
205 for name, test := range tests {
206 func() {
207 s, err := healthcheck.NewServer(c, testPort, test.insts)
208 if err != nil {
209 t.Fatalf("%v: Could not initialize health check: %v", name, err)
210 }
211 defer s.Close(context.Background())
212 s.NotifyStarted()
213
214 resp, err := http.Get("http://localhost:" + testPort + readinessPath)
215 if err != nil {
216 t.Fatalf("%v: HTTP GET failed: %v", name, err)
217 }
218 if resp.StatusCode != http.StatusServiceUnavailable {
219 t.Errorf("want %v, got %v", http.StatusServiceUnavailable, resp.StatusCode)
220 }
221 }()
222 }
223 }
224
225
226
227 func TestCloseHealthCheck(t *testing.T) {
228 s, err := healthcheck.NewServer(&proxy.Client{}, testPort, nil)
229 if err != nil {
230 t.Fatalf("Could not initialize health check: %v", err)
231 }
232 defer s.Close(context.Background())
233
234 resp, err := http.Get("http://localhost:" + testPort + livenessPath)
235 if err != nil {
236 t.Fatalf("HTTP GET failed: %v", err)
237 }
238 if resp.StatusCode != http.StatusOK {
239 t.Errorf("want %v, got %v", http.StatusOK, resp.StatusCode)
240 }
241
242 err = s.Close(context.Background())
243 if err != nil {
244 t.Fatalf("Failed to close health check: %v", err)
245 }
246
247 _, err = http.Get("http://localhost:" + testPort + livenessPath)
248 if err == nil {
249 t.Fatalf("HTTP GET did not return error after closing health check server.")
250 }
251 }
252
View as plain text