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 ®isterServiceServerOption{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 }