k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/images/agnhost/grpc-health-checking/grpc-health-checking.go (about)

     1  /*
     2  Copyright 2022 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 grpchealthchecking offers a tiny grpc health checking endpoint.
    18  package grpchealthchecking
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"log"
    24  	"net"
    25  	"time"
    26  
    27  	"net/http"
    28  
    29  	"github.com/spf13/cobra"
    30  
    31  	"google.golang.org/grpc"
    32  	"google.golang.org/grpc/codes"
    33  	"google.golang.org/grpc/credentials"
    34  	"google.golang.org/grpc/health/grpc_health_v1"
    35  	"google.golang.org/grpc/status"
    36  )
    37  
    38  // CmdGrpcHealthChecking is used by agnhost Cobra.
    39  var CmdGrpcHealthChecking = &cobra.Command{
    40  	Use:   "grpc-health-checking",
    41  	Short: "Starts a simple grpc health checking endpoint",
    42  	Long:  "Starts a simple grpc health checking endpoint with --port to serve on and --service to check status for. The endpoint returns SERVING for the first --delay-unhealthy-sec, and NOT_SERVING after this. NOT_FOUND will be returned for the requests for non-configured service name. Probe can be forced to be set NOT_SERVING by calling /make-not-serving http endpoint.",
    43  	Args:  cobra.MaximumNArgs(0),
    44  	Run:   main,
    45  }
    46  
    47  var (
    48  	port              int
    49  	httpPort          int
    50  	delayUnhealthySec int
    51  	service           string
    52  	forceUnhealthy    *bool
    53  	certFile          string
    54  	privKeyFile       string
    55  )
    56  
    57  func init() {
    58  	CmdGrpcHealthChecking.Flags().IntVar(&port, "port", 5000, "Port number.")
    59  	CmdGrpcHealthChecking.Flags().IntVar(&httpPort, "http-port", 8080, "Port number for the /make-serving and /make-not-serving.")
    60  	CmdGrpcHealthChecking.Flags().IntVar(&delayUnhealthySec, "delay-unhealthy-sec", -1, "Number of seconds to delay before start reporting NOT_SERVING, negative value indicates never.")
    61  	CmdGrpcHealthChecking.Flags().StringVar(&service, "service", "", "Service name to register the health check for.")
    62  	CmdGrpcHealthChecking.Flags().StringVar(&certFile, "tls-cert-file", "",
    63  		"File containing an x509 certificate for gRPC TLS. (CA cert, if any, concatenated after server cert).")
    64  	CmdGrpcHealthChecking.Flags().StringVar(&privKeyFile, "tls-private-key-file", "",
    65  		"File containing an x509 private key matching --tls-cert-file.")
    66  	forceUnhealthy = nil
    67  }
    68  
    69  type HealthChecker struct {
    70  	started time.Time
    71  }
    72  
    73  func (s *HealthChecker) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
    74  	log.Printf("Serving the Check request for health check, started at %v", s.started)
    75  
    76  	if req.Service != service {
    77  		return nil, status.Errorf(codes.NotFound, "unknown service")
    78  	}
    79  
    80  	duration := time.Since(s.started)
    81  	if ((forceUnhealthy != nil) && *forceUnhealthy) || ((delayUnhealthySec >= 0) && (duration.Seconds() >= float64(delayUnhealthySec))) {
    82  		return &grpc_health_v1.HealthCheckResponse{
    83  			Status: grpc_health_v1.HealthCheckResponse_NOT_SERVING,
    84  		}, nil
    85  	}
    86  
    87  	return &grpc_health_v1.HealthCheckResponse{
    88  		Status: grpc_health_v1.HealthCheckResponse_SERVING,
    89  	}, nil
    90  }
    91  
    92  func (s *HealthChecker) Watch(req *grpc_health_v1.HealthCheckRequest, server grpc_health_v1.Health_WatchServer) error {
    93  	return status.Error(codes.Unimplemented, "unimplemented")
    94  }
    95  
    96  func NewHealthChecker(started time.Time) *HealthChecker {
    97  	return &HealthChecker{
    98  		started: started,
    99  	}
   100  }
   101  
   102  func main(cmd *cobra.Command, args []string) {
   103  	started := time.Now()
   104  
   105  	// Validate flags
   106  	//
   107  	// if certFile or privKeyFile are not both set, exit with error
   108  	if (certFile == "" && privKeyFile != "") || (certFile != "" && privKeyFile == "") {
   109  		log.Fatalf("Both --tls-cert-file and --tls-private-key-file must be set")
   110  	}
   111  
   112  	http.HandleFunc("/make-not-serving", func(w http.ResponseWriter, r *http.Request) {
   113  		log.Printf("Mark as unhealthy")
   114  		forceUnhealthy = new(bool)
   115  		*forceUnhealthy = true
   116  		w.WriteHeader(200)
   117  		data := (time.Since(started)).String()
   118  		w.Write([]byte(data))
   119  	})
   120  
   121  	http.HandleFunc("/make-serving", func(w http.ResponseWriter, r *http.Request) {
   122  		log.Printf("Mark as healthy")
   123  		forceUnhealthy = new(bool)
   124  		*forceUnhealthy = false
   125  		w.WriteHeader(200)
   126  		data := (time.Since(started)).String()
   127  		w.Write([]byte(data))
   128  	})
   129  
   130  	go func() {
   131  		httpServerAdr := fmt.Sprintf(":%d", httpPort)
   132  		log.Printf("Http server starting to listen on %s", httpServerAdr)
   133  		log.Fatal(http.ListenAndServe(httpServerAdr, nil))
   134  	}()
   135  
   136  	serverAdr := fmt.Sprintf(":%d", port)
   137  	listenAddr, err := net.Listen("tcp", serverAdr)
   138  
   139  	if err != nil {
   140  		log.Fatalf("Error while starting the listening service %v", err)
   141  	}
   142  
   143  	var grpcServer *grpc.Server
   144  
   145  	if certFile != "" && privKeyFile != "" {
   146  		creds, err := credentials.NewServerTLSFromFile(certFile, privKeyFile)
   147  		if err != nil {
   148  			log.Fatalf("Failed to generate credentials %v", err)
   149  		}
   150  		grpcServer = grpc.NewServer(grpc.Creds(creds))
   151  	} else {
   152  		grpcServer = grpc.NewServer()
   153  	}
   154  
   155  	healthService := NewHealthChecker(started)
   156  	grpc_health_v1.RegisterHealthServer(grpcServer, healthService)
   157  
   158  	log.Printf("gRPC server starting to listen on %s", serverAdr)
   159  	if err = grpcServer.Serve(listenAddr); err != nil {
   160  		log.Fatalf("Error while starting the gRPC server on the %s listen address %v", listenAddr, err)
   161  	}
   162  
   163  	select {}
   164  }