// Standard gRPC health probe package main import ( "context" "flag" "os" "os/signal" "time" "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger" "google.golang.org/grpc" "google.golang.org/grpc/codes" healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/status" ) var ( userAgent string remoteURL string serviceName string connTimeoutDur = time.Second rpcTimeoutDur = time.Second verbose bool ) var log = logger.DefaultLogger() const ( // StatusInvalidArguments indicates specified invalid arguments. StatusInvalidArguments = 1 // StatusConnectionFailure indicates connection failed. StatusConnectionFailure = 2 // StatusRPCFailure indicates rpc failed. StatusRPCFailure = 3 // StatusUnhealthy indicates rpc succeeded but indicates unhealthy service. StatusUnhealthy = 4 ) func init() { flag.StringVar(&remoteURL, "addr", "", "(required) tcp host:port to connect") flag.StringVar(&serviceName, "service", "", "service name to check (default: \"\")") flag.StringVar(&userAgent, "user-agent", "grpc-health-probe", "user-agent header value of health check requests") // timeouts flag.DurationVar(&connTimeoutDur, "connect-timeout", connTimeoutDur, "timeout for establishing connection") flag.DurationVar(&rpcTimeoutDur, "rpc-timeout", rpcTimeoutDur, "timeout for health check rpc") // verbose flag.BoolVar(&verbose, "v", false, "verbose logs") flag.Parse() argError := func(s string, v ...interface{}) { log.Infof("error: "+s, v...) os.Exit(StatusInvalidArguments) } if remoteURL == "" { argError("--addr not specified") } if connTimeoutDur <= 0 { argError("--connect-timeout must be greater than zero (specified: %v)", connTimeoutDur) } if rpcTimeoutDur <= 0 { argError("--rpc-timeout must be greater than zero (specified: %v)", rpcTimeoutDur) } if verbose { log.Info("parsed options:") log.Infof("> remoteUrl=%s conn-timeout=%v rpc-timeout=%v", remoteURL, connTimeoutDur, rpcTimeoutDur) } } func main() { ctx, cancel := context.WithCancel(context.Background()) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) go func() { sig := <-c if sig == os.Interrupt { log.Infof("cancellation received") cancel() return } }() opts := []grpc.DialOption{ grpc.WithUserAgent(userAgent), grpc.WithBlock(), grpc.WithNoProxy()} opts = append(opts, grpc.WithInsecure()) if verbose { log.Info("establishing connection") } connStart := time.Now() dialCtx, cancel2 := context.WithTimeout(ctx, connTimeoutDur) defer cancel2() conn, err := grpc.DialContext(dialCtx, remoteURL, opts...) if err != nil { if err == context.DeadlineExceeded { log.Infof("timeout: failed to connect service %q within %v", remoteURL, connTimeoutDur) } else { log.Infof("error: failed to connect service at %q: %+v", remoteURL, err) } os.Exit(StatusConnectionFailure) } connDuration := time.Since(connStart) defer conn.Close() if verbose { log.Infof("connection established (took %v)", connDuration) } rpcStart := time.Now() rpcCtx, rpcCancel := context.WithTimeout(ctx, rpcTimeoutDur) defer rpcCancel() resp, err := healthpb.NewHealthClient(conn).Check(rpcCtx, &healthpb.HealthCheckRequest{Service: serviceName}) if err != nil { if stat, ok := status.FromError(err); ok && stat.Code() == codes.Unimplemented { log.Infof("error: this server does not implement the grpc health protocol (grpc.health.v1.Health)") } else if stat, ok := status.FromError(err); ok && stat.Code() == codes.DeadlineExceeded { log.Infof("timeout: health rpc did not complete within %v", rpcTimeoutDur) } else { log.Infof("error: health rpc failed: %+v", err) } os.Exit(StatusRPCFailure) } rpcDuration := time.Since(rpcStart) if resp.GetStatus() != healthpb.HealthCheckResponse_SERVING { log.Infof("service unhealthy (responded with %q)", resp.GetStatus().String()) os.Exit(StatusUnhealthy) } if verbose { log.Infof("time elapsed: connect=%v rpc=%v", connDuration, rpcDuration) } log.Infof("status: %v", resp.GetStatus().String()) }