vitess.io/vitess@v0.16.2/go/vt/vtadmin/grpcserver/server.go (about) 1 /* 2 Copyright 2020 The Vitess 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 grpcserver 18 19 import ( 20 "fmt" 21 "net" 22 "net/http" 23 "os" 24 "os/signal" 25 "strings" 26 "sync" 27 "syscall" 28 "time" 29 30 "github.com/gorilla/mux" 31 grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 32 grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 33 otgrpc "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" 34 grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 35 "github.com/opentracing/opentracing-go" 36 "github.com/prometheus/client_golang/prometheus/promhttp" 37 "github.com/soheilhy/cmux" 38 "google.golang.org/grpc" 39 channelz "google.golang.org/grpc/channelz/service" 40 "google.golang.org/grpc/health" 41 "google.golang.org/grpc/reflection" 42 43 "vitess.io/vitess/go/vt/log" 44 "vitess.io/vitess/go/vt/vterrors" 45 46 healthpb "google.golang.org/grpc/health/grpc_health_v1" 47 48 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 49 ) 50 51 // Options defines the set of configurations for a gRPC server. 52 type Options struct { 53 // Addr is the network address to listen on. 54 Addr string 55 // CMuxReadTimeout bounds the amount of time spent muxing connections between 56 // gRPC and HTTP. A zero-value specifies unbounded muxing. 57 CMuxReadTimeout time.Duration 58 // LameDuckDuration specifies the length of the lame duck period during 59 // graceful shutdown. If non-zero, the Server will mark itself unhealthy to 60 // stop new incoming connections while continuing to serve existing 61 // connections. 62 LameDuckDuration time.Duration 63 // AllowReflection specifies whether to register the gRPC server for 64 // reflection. This is required to use with tools like grpc_cli. 65 AllowReflection bool 66 // EnableTracing specifies whether to install opentracing interceptors on 67 // the gRPC server. 68 EnableTracing bool 69 // EnableChannelz specifies whether to register the channelz service on the 70 // gRPC server. 71 EnableChannelz bool 72 73 // MetricsEndpoint is the route to serve promhttp metrics on, including 74 // those collected be grpc_prometheus interceptors. 75 // 76 // It is the user's responsibility to ensure this does not conflict with 77 // other endpoints. 78 // 79 // Omit to not export metrics. 80 MetricsEndpoint string 81 82 StreamInterceptors []grpc.StreamServerInterceptor 83 UnaryInterceptors []grpc.UnaryServerInterceptor 84 } 85 86 // Server provides a multiplexed gRPC/HTTP server. 87 type Server struct { 88 name string 89 90 gRPCServer *grpc.Server 91 healthServer *health.Server 92 router *mux.Router 93 serving bool 94 m sync.RWMutex // this locks the serving bool 95 96 opts Options 97 } 98 99 // New returns a new server. See Options for documentation on configuration 100 // options. 101 // 102 // The underlying gRPC server always has the following interceptors: 103 // - prometheus 104 // - recovery: this handles recovering from panics. 105 // 106 // The full list of interceptors is as follows: 107 // - (optional) interceptors defined on the Options struct 108 // - prometheus 109 // - (optional) opentracing, if opts.EnableTracing is set 110 // - recovery 111 func New(name string, opts Options) *Server { 112 streamInterceptors := append(opts.StreamInterceptors, grpc_prometheus.StreamServerInterceptor) 113 unaryInterceptors := append(opts.UnaryInterceptors, grpc_prometheus.UnaryServerInterceptor) 114 115 if opts.EnableTracing { 116 tracer := opentracing.GlobalTracer() 117 streamInterceptors = append(streamInterceptors, otgrpc.StreamServerInterceptor(otgrpc.WithTracer(tracer))) 118 unaryInterceptors = append(unaryInterceptors, otgrpc.UnaryServerInterceptor(otgrpc.WithTracer(tracer))) 119 } 120 121 recoveryHandler := grpc_recovery.WithRecoveryHandler(func(p any) (err error) { 122 return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "panic triggered: %v", p) 123 }) 124 125 streamInterceptors = append(streamInterceptors, grpc_recovery.StreamServerInterceptor(recoveryHandler)) 126 unaryInterceptors = append(unaryInterceptors, grpc_recovery.UnaryServerInterceptor(recoveryHandler)) 127 128 gserv := grpc.NewServer( 129 grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)), 130 grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unaryInterceptors...)), 131 ) 132 133 if opts.AllowReflection { 134 reflection.Register(gserv) 135 } 136 137 healthServer := health.NewServer() 138 healthpb.RegisterHealthServer(gserv, healthServer) 139 140 if opts.EnableChannelz { 141 channelz.RegisterChannelzServiceToServer(gserv) 142 } 143 144 return &Server{ 145 name: name, 146 gRPCServer: gserv, 147 healthServer: healthServer, 148 router: mux.NewRouter(), 149 opts: opts, 150 } 151 } 152 153 // GRPCServer returns the gRPC Server. 154 func (s *Server) GRPCServer() *grpc.Server { 155 return s.gRPCServer 156 } 157 158 // Router returns the mux.Router powering the HTTP side of the server. 159 func (s *Server) Router() *mux.Router { 160 return s.router 161 } 162 163 // MustListenAndServe calls ListenAndServe and panics if an error occurs. 164 func (s *Server) MustListenAndServe() { 165 if err := s.ListenAndServe(); err != nil { 166 panic(err) 167 } 168 } 169 170 // listenFunc is extracted to mock out in tests. 171 var listenFunc = net.Listen // nolint:gochecknoglobals 172 173 // ListenAndServe sets up a listener, multiplexes it into gRPC and non-gRPC 174 // requests, and binds the gRPC server and mux.Router to them, respectively. It 175 // then installs a signal handler on SIGTERM and SIGQUIT, and runs until either 176 // a signal or an unrecoverable error occurs. 177 // 178 // On shutdown, it may begin a lame duck period (see Options) before beginning 179 // a graceful shutdown of the gRPC server and closing listeners. 180 func (s *Server) ListenAndServe() error { // nolint:funlen 181 lis, err := listenFunc("tcp", s.opts.Addr) 182 if err != nil { 183 return err 184 } 185 defer lis.Close() 186 187 lmux := cmux.New(lis) 188 189 if s.opts.CMuxReadTimeout > 0 { 190 lmux.SetReadTimeout(s.opts.CMuxReadTimeout) 191 } 192 193 grpcLis := lmux.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc")) 194 anyLis := lmux.Match(cmux.Any()) 195 196 shutdown := make(chan error, 16) 197 198 signals := make(chan os.Signal, 8) 199 signal.Notify(signals, syscall.SIGTERM, syscall.SIGQUIT) 200 201 // listen for signals 202 go func() { 203 sig := <-signals 204 err := fmt.Errorf("received signal: %v", sig) // nolint:goerr113 205 log.Warning(err) 206 shutdown <- err 207 }() 208 209 if s.opts.MetricsEndpoint != "" { 210 if !strings.HasPrefix(s.opts.MetricsEndpoint, "/") { 211 s.opts.MetricsEndpoint = "/" + s.opts.MetricsEndpoint 212 } 213 214 grpc_prometheus.Register(s.gRPCServer) 215 s.router.Handle(s.opts.MetricsEndpoint, promhttp.Handler()) 216 } 217 218 // Start the servers 219 go func() { 220 err := s.gRPCServer.Serve(grpcLis) 221 err = fmt.Errorf("grpc server stopped: %w", err) 222 log.Warning(err) 223 shutdown <- err 224 }() 225 226 go func() { 227 err := http.Serve(anyLis, s.router) 228 err = fmt.Errorf("http server stopped: %w", err) 229 log.Warning(err) 230 shutdown <- err 231 }() 232 233 // Start muxing connections 234 go func() { 235 err := lmux.Serve() 236 err = fmt.Errorf("listener closed: %w", err) 237 log.Warning(err) 238 shutdown <- err 239 }() 240 241 for service := range s.gRPCServer.GetServiceInfo() { 242 s.healthServer.SetServingStatus(service, healthpb.HealthCheckResponse_SERVING) 243 } 244 245 s.setServing(true) 246 log.Infof("server %s listening on %s", s.name, s.opts.Addr) 247 248 reason := <-shutdown 249 log.Warningf("graceful shutdown triggered by: %v", reason) 250 251 if s.opts.LameDuckDuration > 0 { 252 log.Infof("entering lame duck period for %v", s.opts.LameDuckDuration) 253 s.healthServer.Shutdown() 254 time.Sleep(s.opts.LameDuckDuration) 255 } else { 256 log.Infof("lame duck disabled") 257 } 258 259 log.Info("beginning graceful shutdown") 260 s.gRPCServer.GracefulStop() 261 log.Info("graceful shutdown complete") 262 263 s.setServing(false) 264 265 return nil 266 } 267 268 func (s *Server) setServing(state bool) { 269 s.m.Lock() 270 defer s.m.Unlock() 271 272 s.serving = state 273 } 274 275 func (s *Server) isServing() bool { 276 s.m.RLock() 277 defer s.m.RUnlock() 278 279 return s.serving 280 }