Skip to content

Commit 288fe49

Browse files
committed
Add healthcheck for grpc server and client
Signed-off-by: Jian Qiu <jqiu@redhat.com>
1 parent 3fc951c commit 288fe49

11 files changed

Lines changed: 654 additions & 4 deletions

File tree

pkg/cloudevents/generic/options/grpc/agentoptions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package grpc
22

33
import (
44
"context"
5+
"time"
56

67
cloudevents "github.com/cloudevents/sdk-go/v2"
78

@@ -47,6 +48,7 @@ func (o *grpcAgentOptions) Protocol(ctx context.Context, dataType types.CloudEve
4748
ClusterName: o.clusterName,
4849
DataType: dataType.String(),
4950
}),
51+
protocol.WithReconnectErrorOption(o.errorChan, 10*time.Second),
5052
)
5153
if err != nil {
5254
return nil, err

pkg/cloudevents/generic/options/grpc/protocol/message.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const (
1919
prefix = "ce-"
2020
contenttype = "contenttype"
2121
// dataSchema = "dataschema"
22-
subject = "subject"
23-
time = "time"
22+
subject = "subject"
23+
timestamp = "time"
2424
)
2525

2626
var specs = spec.WithPrefix(prefix)

pkg/cloudevents/generic/options/grpc/protocol/option.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package protocol
22

33
import (
44
"fmt"
5+
"time"
56
)
67

78
// Option is the function signature
@@ -24,3 +25,14 @@ func WithSubscribeOption(subscribeOpt *SubscribeOption) Option {
2425
return nil
2526
}
2627
}
28+
29+
func WithReconnectErrorOption(reconnectError chan error, interval time.Duration) Option {
30+
return func(p *Protocol) error {
31+
if reconnectError == nil {
32+
return fmt.Errorf("the reconnect error option must not be nil")
33+
}
34+
p.reconnectErrorChan = reconnectError
35+
p.checkInterval = interval
36+
return nil
37+
}
38+
}

pkg/cloudevents/generic/options/grpc/protocol/protocol.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package protocol
33
import (
44
"context"
55
"fmt"
6+
healthpb "google.golang.org/grpc/health/grpc_health_v1"
67
"io"
78
"sync"
9+
"time"
810

911
"google.golang.org/grpc"
1012
"google.golang.org/grpc/codes"
@@ -30,6 +32,10 @@ type Protocol struct {
3032
openerMutex sync.Mutex
3133

3234
closeChan chan struct{}
35+
36+
// errorChan is to send an error messsage to restart the connection
37+
reconnectErrorChan chan error
38+
checkInterval time.Duration
3339
}
3440

3541
var (
@@ -137,6 +143,10 @@ func (p *Protocol) OpenInbound(ctx context.Context) error {
137143
}
138144
}()
139145

146+
if p.reconnectErrorChan != nil {
147+
go p.healthCheck(ctx)
148+
}
149+
140150
// Wait until external or internal context done
141151
select {
142152
case <-ctx.Done():
@@ -164,3 +174,38 @@ func (p *Protocol) Close(ctx context.Context) error {
164174
close(p.closeChan)
165175
return nil
166176
}
177+
178+
// healthCheck check status of the server
179+
func (p *Protocol) healthCheck(ctx context.Context) {
180+
logger := cecontext.LoggerFrom(ctx)
181+
healthClient := healthpb.NewHealthClient(p.clientConn)
182+
stream, err := healthClient.Watch(ctx, &healthpb.HealthCheckRequest{Service: ""})
183+
if err != nil {
184+
p.reconnectErrorChan <- err
185+
return
186+
}
187+
188+
for {
189+
timeout := time.NewTimer(p.checkInterval)
190+
done := make(chan struct{})
191+
192+
go func() {
193+
resp, err := stream.Recv()
194+
if err != nil {
195+
p.reconnectErrorChan <- err
196+
close(done)
197+
return
198+
}
199+
logger.Infof("Received server health status %s", resp.Status)
200+
close(done)
201+
}()
202+
203+
select {
204+
case <-timeout.C:
205+
p.reconnectErrorChan <- fmt.Errorf("timeout waiting for health check")
206+
return
207+
case <-done:
208+
timeout.Stop()
209+
}
210+
}
211+
}

pkg/cloudevents/generic/options/grpc/protocol/write_message.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@ func (b *pbEventWriter) SetAttribute(attribute spec.Attribute, value interface{}
136136
}
137137
case spec.Time:
138138
if value == nil {
139-
delete(b.Attributes, prefix+time)
139+
delete(b.Attributes, prefix+timestamp)
140140
} else {
141141
attrVal, err := attributeFor(value)
142142
if err != nil {
143143
return err
144144
}
145-
b.Attributes[prefix+time] = attrVal
145+
b.Attributes[prefix+timestamp] = attrVal
146146
}
147147
default:
148148
if value == nil {

pkg/cloudevents/generic/options/grpc/sourceoptions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package grpc
22

33
import (
44
"context"
5+
"time"
56

67
cloudevents "github.com/cloudevents/sdk-go/v2"
78

@@ -42,6 +43,7 @@ func (o *gRPCSourceOptions) Protocol(ctx context.Context, dataType types.CloudEv
4243
Source: o.sourceID,
4344
DataType: dataType.String(),
4445
}),
46+
protocol.WithReconnectErrorOption(o.errorChan, 10*time.Second),
4547
)
4648
if err != nil {
4749
return nil, err

pkg/server/grpc/health/health.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package health
2+
3+
import (
4+
"context"
5+
"google.golang.org/grpc"
6+
"time"
7+
8+
healthpb "google.golang.org/grpc/health/grpc_health_v1"
9+
)
10+
11+
// Custom health server with periodic broadcasts
12+
type heartbeatHealthServer struct {
13+
interval time.Duration
14+
healthpb.UnimplementedHealthServer
15+
status healthpb.HealthCheckResponse_ServingStatus
16+
}
17+
18+
func RegisterHeartbeatHealthServer(srv *grpc.Server, interval time.Duration) {
19+
healthpb.RegisterHealthServer(srv, &heartbeatHealthServer{
20+
interval: interval,
21+
})
22+
}
23+
24+
func (s *heartbeatHealthServer) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
25+
return &healthpb.HealthCheckResponse{Status: s.status}, nil
26+
}
27+
28+
func (s *heartbeatHealthServer) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_WatchServer) error {
29+
ticker := time.NewTicker(s.interval) // send every 5s
30+
defer ticker.Stop()
31+
32+
for {
33+
select {
34+
case <-ticker.C:
35+
if err := stream.Send(&healthpb.HealthCheckResponse{Status: s.status}); err != nil {
36+
return err
37+
}
38+
case <-stream.Context().Done():
39+
return stream.Context().Err()
40+
}
41+
}
42+
}

pkg/server/grpc/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"crypto/x509"
77
"fmt"
88
"net"
9+
"open-cluster-management.io/sdk-go/pkg/server/grpc/health"
910
"os"
11+
"time"
1012

1113
grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
1214
"google.golang.org/grpc"
@@ -133,6 +135,8 @@ func (b *GRPCServer) Run(ctx context.Context) error {
133135
metrics.RegisterGRPCMetrics(promMiddleware, b.extraMetrics...)
134136
// initialize grpc server metrics with appropriate value.
135137
promMiddleware.InitializeMetrics(grpcServer)
138+
// register health server
139+
health.RegisterHeartbeatHealthServer(grpcServer, 5*time.Second)
136140

137141
for _, r := range b.registerFuncs {
138142
r(grpcServer)

0 commit comments

Comments
 (0)