google.golang.org/grpc@v1.72.2/internal/stubserver/stubserver.go (about)

     1  /*
     2   *
     3   * Copyright 2020 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  // Package stubserver is a stubbable implementation of
    20  // google.golang.org/grpc/interop/grpc_testing for testing purposes.
    21  package stubserver
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"net"
    27  	"net/http"
    28  	"testing"
    29  	"time"
    30  
    31  	"golang.org/x/net/http2"
    32  	"google.golang.org/grpc"
    33  	"google.golang.org/grpc/connectivity"
    34  	"google.golang.org/grpc/credentials/insecure"
    35  	"google.golang.org/grpc/resolver"
    36  	"google.golang.org/grpc/resolver/manual"
    37  	"google.golang.org/grpc/serviceconfig"
    38  
    39  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    40  	testpb "google.golang.org/grpc/interop/grpc_testing"
    41  )
    42  
    43  // GRPCServer is an interface that groups methods implemented by a grpc.Server
    44  // or an xds.GRPCServer that are used by the StubServer.
    45  type GRPCServer interface {
    46  	grpc.ServiceRegistrar
    47  	Stop()
    48  	GracefulStop()
    49  	Serve(net.Listener) error
    50  }
    51  
    52  // StubServer is a server that is easy to customize within individual test
    53  // cases.
    54  type StubServer struct {
    55  	// Guarantees we satisfy this interface; panics if unimplemented methods are called.
    56  	testgrpc.TestServiceServer
    57  
    58  	// Customizable implementations of server handlers.
    59  	EmptyCallF           func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error)
    60  	UnaryCallF           func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error)
    61  	FullDuplexCallF      func(stream testgrpc.TestService_FullDuplexCallServer) error
    62  	StreamingInputCallF  func(stream testgrpc.TestService_StreamingInputCallServer) error
    63  	StreamingOutputCallF func(req *testpb.StreamingOutputCallRequest, stream testgrpc.TestService_StreamingOutputCallServer) error
    64  
    65  	// A client connected to this service the test may use.  Created in Start().
    66  	Client testgrpc.TestServiceClient
    67  	CC     *grpc.ClientConn
    68  
    69  	// Server to serve this service from.
    70  	//
    71  	// If nil, a new grpc.Server is created, listening on the provided Network
    72  	// and Address fields, or listening using the provided Listener.
    73  	S GRPCServer
    74  
    75  	// Parameters for Listen and Dial. Defaults will be used if these are empty
    76  	// before Start.
    77  	Network string
    78  	Address string
    79  	Target  string
    80  
    81  	// Custom listener to use for serving. If unspecified, a new listener is
    82  	// created on a local port.
    83  	Listener net.Listener
    84  
    85  	cleanups []func() // Lambdas executed in Stop(); populated by Start().
    86  
    87  	// Set automatically if Target == ""
    88  	R *manual.Resolver
    89  }
    90  
    91  // EmptyCall is the handler for testpb.EmptyCall
    92  func (ss *StubServer) EmptyCall(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) {
    93  	return ss.EmptyCallF(ctx, in)
    94  }
    95  
    96  // UnaryCall is the handler for testpb.UnaryCall
    97  func (ss *StubServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
    98  	return ss.UnaryCallF(ctx, in)
    99  }
   100  
   101  // FullDuplexCall is the handler for testpb.FullDuplexCall
   102  func (ss *StubServer) FullDuplexCall(stream testgrpc.TestService_FullDuplexCallServer) error {
   103  	return ss.FullDuplexCallF(stream)
   104  }
   105  
   106  // StreamingInputCall is the handler for testpb.StreamingInputCall
   107  func (ss *StubServer) StreamingInputCall(stream testgrpc.TestService_StreamingInputCallServer) error {
   108  	return ss.StreamingInputCallF(stream)
   109  }
   110  
   111  // StreamingOutputCall is the handler for testpb.StreamingOutputCall
   112  func (ss *StubServer) StreamingOutputCall(req *testpb.StreamingOutputCallRequest, stream testgrpc.TestService_StreamingOutputCallServer) error {
   113  	return ss.StreamingOutputCallF(req, stream)
   114  }
   115  
   116  // Start starts the server and creates a client connected to it.
   117  func (ss *StubServer) Start(sopts []grpc.ServerOption, dopts ...grpc.DialOption) error {
   118  	if err := ss.StartServer(sopts...); err != nil {
   119  		return err
   120  	}
   121  	if err := ss.StartClient(dopts...); err != nil {
   122  		ss.Stop()
   123  		return err
   124  	}
   125  	return nil
   126  }
   127  
   128  type registerServiceServerOption struct {
   129  	grpc.EmptyServerOption
   130  	f func(grpc.ServiceRegistrar)
   131  }
   132  
   133  // RegisterServiceServerOption returns a ServerOption that will run f() in
   134  // Start or StartServer with the grpc.Server created before serving.  This
   135  // allows other services to be registered on the test server (e.g. ORCA,
   136  // health, or reflection).
   137  func RegisterServiceServerOption(f func(grpc.ServiceRegistrar)) grpc.ServerOption {
   138  	return &registerServiceServerOption{f: f}
   139  }
   140  
   141  func (ss *StubServer) setupServer(sopts ...grpc.ServerOption) (net.Listener, error) {
   142  	if ss.Network == "" {
   143  		ss.Network = "tcp"
   144  	}
   145  	if ss.Address == "" {
   146  		ss.Address = "localhost:0"
   147  	}
   148  	if ss.Target == "" {
   149  		ss.R = manual.NewBuilderWithScheme("whatever")
   150  	}
   151  
   152  	lis := ss.Listener
   153  	if lis == nil {
   154  		var err error
   155  		lis, err = net.Listen(ss.Network, ss.Address)
   156  		if err != nil {
   157  			return nil, fmt.Errorf("net.Listen(%q, %q) = %v", ss.Network, ss.Address, err)
   158  		}
   159  	}
   160  	ss.Address = lis.Addr().String()
   161  
   162  	if ss.S == nil {
   163  		ss.S = grpc.NewServer(sopts...)
   164  	}
   165  	for _, so := range sopts {
   166  		if x, ok := so.(*registerServiceServerOption); ok {
   167  			x.f(ss.S)
   168  		}
   169  	}
   170  
   171  	testgrpc.RegisterTestServiceServer(ss.S, ss)
   172  	ss.cleanups = append(ss.cleanups, ss.S.Stop)
   173  	return lis, nil
   174  }
   175  
   176  // StartHandlerServer only starts an HTTP server with a gRPC server as the
   177  // handler. It does not create a client to it.  Cannot be used in a StubServer
   178  // that also used StartServer.
   179  func (ss *StubServer) StartHandlerServer(sopts ...grpc.ServerOption) error {
   180  	lis, err := ss.setupServer(sopts...)
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	handler, ok := ss.S.(interface{ http.Handler })
   186  	if !ok {
   187  		panic(fmt.Sprintf("server of type %T does not implement http.Handler", ss.S))
   188  	}
   189  
   190  	go func() {
   191  		hs := &http2.Server{}
   192  		opts := &http2.ServeConnOpts{Handler: handler}
   193  		for {
   194  			conn, err := lis.Accept()
   195  			if err != nil {
   196  				return
   197  			}
   198  			hs.ServeConn(conn, opts)
   199  		}
   200  	}()
   201  	ss.cleanups = append(ss.cleanups, func() { lis.Close() })
   202  
   203  	return nil
   204  }
   205  
   206  // StartServer only starts the server. It does not create a client to it.
   207  // Cannot be used in a StubServer that also used StartHandlerServer.
   208  func (ss *StubServer) StartServer(sopts ...grpc.ServerOption) error {
   209  	lis, err := ss.setupServer(sopts...)
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	go ss.S.Serve(lis)
   215  
   216  	return nil
   217  }
   218  
   219  // StartClient creates a client connected to this service that the test may use.
   220  // The newly created client will be available in the Client field of StubServer.
   221  func (ss *StubServer) StartClient(dopts ...grpc.DialOption) error {
   222  	opts := append([]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}, dopts...)
   223  	if ss.R != nil {
   224  		ss.Target = ss.R.Scheme() + ":///" + ss.Address
   225  		opts = append(opts, grpc.WithResolvers(ss.R))
   226  	}
   227  
   228  	cc, err := grpc.NewClient(ss.Target, opts...)
   229  	if err != nil {
   230  		return fmt.Errorf("grpc.NewClient(%q) = %v", ss.Target, err)
   231  	}
   232  	cc.Connect()
   233  	ss.CC = cc
   234  	if ss.R != nil {
   235  		ss.R.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: ss.Address}}})
   236  	}
   237  	if err := waitForReady(cc); err != nil {
   238  		cc.Close()
   239  		return err
   240  	}
   241  
   242  	ss.cleanups = append(ss.cleanups, func() { cc.Close() })
   243  
   244  	ss.Client = testgrpc.NewTestServiceClient(cc)
   245  	return nil
   246  }
   247  
   248  // NewServiceConfig applies sc to ss.Client using the resolver (if present).
   249  func (ss *StubServer) NewServiceConfig(sc string) {
   250  	if ss.R != nil {
   251  		ss.R.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: ss.Address}}, ServiceConfig: parseCfg(ss.R, sc)})
   252  	}
   253  }
   254  
   255  func waitForReady(cc *grpc.ClientConn) error {
   256  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   257  	defer cancel()
   258  	for {
   259  		s := cc.GetState()
   260  		if s == connectivity.Ready {
   261  			return nil
   262  		}
   263  		if !cc.WaitForStateChange(ctx, s) {
   264  			// ctx got timeout or canceled.
   265  			return ctx.Err()
   266  		}
   267  	}
   268  }
   269  
   270  // Stop stops ss and cleans up all resources it consumed.
   271  func (ss *StubServer) Stop() {
   272  	for i := len(ss.cleanups) - 1; i >= 0; i-- {
   273  		ss.cleanups[i]()
   274  	}
   275  	ss.cleanups = nil
   276  }
   277  
   278  func parseCfg(r *manual.Resolver, s string) *serviceconfig.ParseResult {
   279  	g := r.CC().ParseServiceConfig(s)
   280  	if g.Err != nil {
   281  		panic(fmt.Sprintf("Error parsing config %q: %v", s, g.Err))
   282  	}
   283  	return g
   284  }
   285  
   286  // StartTestService spins up a stub server exposing the TestService on a local
   287  // port. If the passed in server is nil, a stub server that implements only the
   288  // EmptyCall and UnaryCall RPCs is started.
   289  func StartTestService(t *testing.T, server *StubServer, sopts ...grpc.ServerOption) *StubServer {
   290  	if server == nil {
   291  		server = &StubServer{
   292  			EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) { return &testpb.Empty{}, nil },
   293  			UnaryCallF: func(context.Context, *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
   294  				return &testpb.SimpleResponse{}, nil
   295  			},
   296  		}
   297  	}
   298  	server.StartServer(sopts...)
   299  
   300  	t.Logf("Started test service backend at %q", server.Address)
   301  	return server
   302  }