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 }