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  }