blob: 68805eb06bacfbac4b649e54f264fac3b0f1f10a [file] [log] [blame]
// Copyright 2017 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package healthz provides /healthz for grpc server.
package healthz
import (
"context"
"fmt"
"net/http"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"go.chromium.org/goma/server/log"
)
var (
connMu sync.Mutex
conn *grpc.ClientConn
)
func dialOnce(ctx context.Context, addr string) (*grpc.ClientConn, error) {
connMu.Lock()
defer connMu.Unlock()
if conn != nil {
return conn, nil
}
logger := log.FromContext(ctx)
deadline, ok := ctx.Deadline()
if ok {
logger.Debugf("dialing %s for /healthz check. deadline=%s", addr, deadline)
} else {
logger.Debugf("dialing %s for /healthz check", addr)
}
var err error
conn, err = grpc.DialContext(ctx, addr, grpc.WithInsecure())
if err != nil {
return nil, err
}
return conn, nil
}
var (
mu sync.Mutex
unhealthy string
)
// SetUnhealthy sets m as unhealthy message.
// empty message means healthy.
func SetUnhealthy(m string) {
mu.Lock()
defer mu.Unlock()
unhealthy = m
}
func getUnhealthy() string {
mu.Lock()
defer mu.Unlock()
return unhealthy
}
// Register registers /healthz handler for grpc server.
func Register(s *grpc.Server, addr string) {
healthpb.RegisterHealthServer(s, health.NewServer())
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
logger := log.FromContext(ctx)
now := time.Now()
m := getUnhealthy()
if m != "" {
logger.Warnf("/healthz reports unhealthy: %s", m)
http.Error(w, m, http.StatusServiceUnavailable)
return
}
conn, err := dialOnce(ctx, addr)
if err != nil {
logger.Warnf("/healthz check failed for %s to dial: %v", time.Since(now), err)
http.Error(w, fmt.Sprintf("failed to create grpc connection: %v", err), http.StatusServiceUnavailable)
return
}
hc := healthpb.NewHealthClient(conn)
resp, err := hc.Check(ctx, &healthpb.HealthCheckRequest{})
if err != nil {
logger.Errorf("/healthz check failed to call Check: %v", err)
http.Error(w, fmt.Sprintf("failed to call Check: %v", err), http.StatusServiceUnavailable)
return
}
if resp.Status != healthpb.HealthCheckResponse_SERVING {
logger.Errorf("/healthz check failed to get serving status: %v", resp.Status)
http.Error(w, fmt.Sprintf("health server is not serving: %v", resp.Status), http.StatusServiceUnavailable)
return
}
w.Write([]byte("ok"))
logger.Debugf("%s is healthy: %s", addr, time.Since(now))
})
}