Skip to content

Commit 218753a

Browse files
authored
Merge pull request #485 from kinvolk/imran/smarter-readiness-check
agent: smarter readiness check
2 parents b006c04 + 581b1f8 commit 218753a

File tree

4 files changed

+160
-11
lines changed

4 files changed

+160
-11
lines changed

cmd/agent/app/server.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package app
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"crypto/tls"
2223
"fmt"
@@ -26,6 +27,7 @@ import (
2627
"runtime"
2728
runpprof "runtime/pprof"
2829
"strconv"
30+
"strings"
2931
"time"
3032

3133
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -36,6 +38,7 @@ import (
3638
"k8s.io/klog/v2"
3739

3840
"sigs.k8s.io/apiserver-network-proxy/cmd/agent/app/options"
41+
"sigs.k8s.io/apiserver-network-proxy/pkg/agent"
3942
"sigs.k8s.io/apiserver-network-proxy/pkg/util"
4043
)
4144

@@ -63,11 +66,13 @@ func (a *Agent) run(o *options.GrpcProxyAgentOptions) error {
6366
}
6467

6568
stopCh := make(chan struct{})
66-
if err := a.runProxyConnection(o, stopCh); err != nil {
69+
70+
cs, err := a.runProxyConnection(o, stopCh)
71+
if err != nil {
6772
return fmt.Errorf("failed to run proxy connection with %v", err)
6873
}
6974

70-
if err := a.runHealthServer(o); err != nil {
75+
if err := a.runHealthServer(o, cs); err != nil {
7176
return fmt.Errorf("failed to run health server with %v", err)
7277
}
7378

@@ -80,11 +85,11 @@ func (a *Agent) run(o *options.GrpcProxyAgentOptions) error {
8085
return nil
8186
}
8287

83-
func (a *Agent) runProxyConnection(o *options.GrpcProxyAgentOptions, stopCh <-chan struct{}) error {
88+
func (a *Agent) runProxyConnection(o *options.GrpcProxyAgentOptions, stopCh <-chan struct{}) (agent.ReadinessManager, error) {
8489
var tlsConfig *tls.Config
8590
var err error
8691
if tlsConfig, err = util.GetClientTLSConfig(o.CaCert, o.AgentCert, o.AgentKey, o.ProxyServerHost, o.AlpnProtos); err != nil {
87-
return err
92+
return nil, err
8893
}
8994
dialOptions := []grpc.DialOption{
9095
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
@@ -97,15 +102,45 @@ func (a *Agent) runProxyConnection(o *options.GrpcProxyAgentOptions, stopCh <-ch
97102
cs := cc.NewAgentClientSet(stopCh)
98103
cs.Serve()
99104

100-
return nil
105+
return cs, nil
101106
}
102107

103-
func (a *Agent) runHealthServer(o *options.GrpcProxyAgentOptions) error {
108+
func (a *Agent) runHealthServer(o *options.GrpcProxyAgentOptions, cs agent.ReadinessManager) error {
104109
livenessHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105110
fmt.Fprintf(w, "ok")
106111
})
112+
113+
checks := []agent.HealthChecker{agent.Ping, agent.NewServerConnected(cs)}
107114
readinessHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
108-
fmt.Fprintf(w, "ok")
115+
var failedChecks []string
116+
var individualCheckOutput bytes.Buffer
117+
for _, check := range checks {
118+
if err := check.Check(r); err != nil {
119+
fmt.Fprintf(&individualCheckOutput, "[-]%s failed: %v\n", check.Name(), err)
120+
failedChecks = append(failedChecks, check.Name())
121+
} else {
122+
fmt.Fprintf(&individualCheckOutput, "[+]%s ok\n", check.Name())
123+
}
124+
}
125+
126+
// Always be verbose if the check has failed
127+
if len(failedChecks) > 0 {
128+
klog.V(0).Infoln("%s check failed: \n%v", strings.Join(failedChecks, ","), individualCheckOutput.String())
129+
w.WriteHeader(http.StatusServiceUnavailable)
130+
fmt.Fprintf(w, individualCheckOutput.String())
131+
return
132+
}
133+
134+
if _, found := r.URL.Query()["verbose"]; !found {
135+
w.WriteHeader(http.StatusOK)
136+
fmt.Fprint(w, "ok")
137+
return
138+
}
139+
140+
fmt.Fprintf(&individualCheckOutput, "check passed\n")
141+
142+
w.WriteHeader(http.StatusOK)
143+
fmt.Fprint(w, individualCheckOutput.String())
109144
})
110145

111146
muxHandler := http.NewServeMux()

pkg/agent/readiness.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
copyright 2023 the kubernetes authors.
3+
4+
licensed under the apache license, version 2.0 (the "license");
5+
you may not use this file except in compliance with the license.
6+
you may obtain a copy of the license at
7+
8+
http://www.apache.org/licenses/license-2.0
9+
10+
unless required by applicable law or agreed to in writing, software
11+
distributed under the license is distributed on an "as is" basis,
12+
without warranties or conditions of any kind, either express or implied.
13+
see the license for the specific language governing permissions and
14+
limitations under the license.
15+
*/
16+
17+
package agent
18+
19+
import (
20+
"fmt"
21+
"net/http"
22+
)
23+
24+
// ReadinessManager supports checking if the agent is ready.
25+
type ReadinessManager interface {
26+
// Ready returns true the proxy server is ready.
27+
Ready() bool
28+
}
29+
30+
var _ ReadinessManager = &ClientSet{}
31+
32+
func (cs *ClientSet) Ready() bool {
33+
// Returns true if the agent is connected to at least one server.
34+
return cs.HealthyClientsCount() > 0
35+
}
36+
37+
// HealthChecker represents an entity capable of performing health checks.
38+
type HealthChecker interface {
39+
Name() string
40+
Check(req *http.Request) error
41+
}
42+
43+
// ping implements the simplest possible healthz checker.
44+
type ping struct{}
45+
46+
var Ping HealthChecker = &ping{}
47+
48+
func (p *ping) Name() string {
49+
return "ping"
50+
}
51+
52+
func (p *ping) Check(_ *http.Request) error {
53+
return nil
54+
}
55+
56+
type serverConnected struct {
57+
rm ReadinessManager
58+
}
59+
60+
var ServerConnected HealthChecker = &serverConnected{}
61+
62+
func NewServerConnected(cs ReadinessManager) HealthChecker {
63+
return &serverConnected{
64+
rm: cs,
65+
}
66+
}
67+
68+
func (s *serverConnected) Name() string {
69+
return "server-connected"
70+
}
71+
72+
func (s *serverConnected) Check(_ *http.Request) error {
73+
if s.rm.Ready() {
74+
return nil
75+
}
76+
77+
return fmt.Errorf("no servers connected")
78+
}

tests/proxy_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,7 @@ func runAgentWithID(agentID, addr string, stopCh <-chan struct{}) *agent.ClientS
969969
ProbeInterval: 100 * time.Millisecond,
970970
DialOptions: []grpc.DialOption{grpc.WithInsecure()},
971971
}
972+
972973
client := cc.NewAgentClientSet(stopCh)
973974
client.Serve()
974975
return client

tests/readiness_test.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@ limitations under the License.
1616

1717
package tests
1818

19-
import (
20-
"testing"
21-
)
19+
import "testing"
2220

23-
func TestReadiness(t *testing.T) {
21+
func TestGRPCServerAndAgentReadiness(t *testing.T) {
2422
stopCh := make(chan struct{})
2523
defer close(stopCh)
2624

@@ -38,8 +36,45 @@ func TestReadiness(t *testing.T) {
3836
clientset := runAgent(proxy.agent, stopCh)
3937
waitForConnectedServerCount(t, 1, clientset)
4038

39+
// check the agent connected status
40+
isAgentReady := clientset.Ready()
41+
if !isAgentReady {
42+
t.Fatalf("expected connection status 'true', got: %t", isAgentReady)
43+
}
4144
ready, _ = server.Readiness.Ready()
4245
if !ready {
4346
t.Fatalf("expected ready")
4447
}
4548
}
49+
50+
func TestHTTPConnServerAndAgentReadiness(t *testing.T) {
51+
stopCh := make(chan struct{})
52+
defer close(stopCh)
53+
54+
proxy, cleanup, err := runHTTPConnProxyServer()
55+
if err != nil {
56+
t.Fatal(err)
57+
}
58+
defer cleanup()
59+
60+
clientset := runAgent(proxy.agent, stopCh)
61+
waitForConnectedServerCount(t, 1, clientset)
62+
63+
// check the agent connected status
64+
isAgentReady := clientset.Ready()
65+
if !isAgentReady {
66+
t.Fatalf("expected connection status 'true', got: %t", isAgentReady)
67+
}
68+
}
69+
70+
func TestAgentReadinessWithoutServer(t *testing.T) {
71+
stopCh := make(chan struct{})
72+
defer close(stopCh)
73+
74+
clientset := runAgent("localhost:8080", stopCh)
75+
// check the agent connected status
76+
isAgentReady := clientset.Ready()
77+
if isAgentReady {
78+
t.Fatalf("expected connection status 'false', got: %t", isAgentReady)
79+
}
80+
}

0 commit comments

Comments
 (0)