google.golang.org/grpc@v1.62.1/internal/testutils/fakegrpclb/server.go (about) 1 /* 2 * 3 * Copyright 2022 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 fakegrpclb provides a fake implementation of the grpclb server. 20 package fakegrpclb 21 22 import ( 23 "errors" 24 "fmt" 25 "io" 26 "net" 27 "strconv" 28 "sync" 29 "time" 30 31 "google.golang.org/grpc" 32 lbgrpc "google.golang.org/grpc/balancer/grpclb/grpc_lb_v1" 33 lbpb "google.golang.org/grpc/balancer/grpclb/grpc_lb_v1" 34 "google.golang.org/grpc/codes" 35 "google.golang.org/grpc/grpclog" 36 "google.golang.org/grpc/internal/pretty" 37 "google.golang.org/grpc/status" 38 ) 39 40 var logger = grpclog.Component("fake_grpclb") 41 42 // ServerParams wraps options passed while creating a Server. 43 type ServerParams struct { 44 ListenPort int // Listening port for the balancer server. 45 ServerOptions []grpc.ServerOption // gRPC options for the balancer server. 46 47 LoadBalancedServiceName string // Service name being load balanced for. 48 LoadBalancedServicePort int // Service port being load balanced for. 49 BackendAddresses []string // Service backends to balance load across. 50 ShortStream bool // End balancer stream after sending server list. 51 } 52 53 // Server is a fake implementation of the grpclb LoadBalancer service. It does 54 // not support stats reporting from clients, and always sends back a static list 55 // of backends to the client to balance load across. 56 // 57 // It is safe for concurrent access. 58 type Server struct { 59 lbgrpc.UnimplementedLoadBalancerServer 60 61 // Options copied over from ServerParams passed to NewServer. 62 sOpts []grpc.ServerOption // gRPC server options. 63 serviceName string // Service name being load balanced for. 64 servicePort int // Service port being load balanced for. 65 shortStream bool // End balancer stream after sending server list. 66 67 // Values initialized using ServerParams passed to NewServer. 68 backends []*lbpb.Server // Service backends to balance load across. 69 lis net.Listener // Listener for grpc connections to the LoadBalancer service. 70 71 // mu guards access to below fields. 72 mu sync.Mutex 73 grpcServer *grpc.Server // Underlying grpc server. 74 address string // Actual listening address. 75 76 stopped chan struct{} // Closed when Stop() is called. 77 } 78 79 // NewServer creates a new Server with passed in params. Returns a non-nil error 80 // if the params are invalid. 81 func NewServer(params ServerParams) (*Server, error) { 82 var servers []*lbpb.Server 83 for _, addr := range params.BackendAddresses { 84 ipStr, portStr, err := net.SplitHostPort(addr) 85 if err != nil { 86 return nil, fmt.Errorf("failed to parse list of backend address %q: %v", addr, err) 87 } 88 ip := net.ParseIP(ipStr) 89 if ip == nil { 90 return nil, fmt.Errorf("failed to parse ip: %q", ipStr) 91 } 92 port, err := strconv.Atoi(portStr) 93 if err != nil { 94 return nil, fmt.Errorf("failed to convert port %q to int", portStr) 95 } 96 logger.Infof("Adding backend ip: %q, port: %d to server list", ip.String(), port) 97 servers = append(servers, &lbpb.Server{ 98 IpAddress: ip, 99 Port: int32(port), 100 }) 101 } 102 103 lis, err := net.Listen("tcp", "localhost:"+strconv.Itoa(params.ListenPort)) 104 if err != nil { 105 return nil, fmt.Errorf("failed to listen on port %q: %v", params.ListenPort, err) 106 } 107 108 return &Server{ 109 sOpts: params.ServerOptions, 110 serviceName: params.LoadBalancedServiceName, 111 servicePort: params.LoadBalancedServicePort, 112 shortStream: params.ShortStream, 113 backends: servers, 114 lis: lis, 115 address: lis.Addr().String(), 116 stopped: make(chan struct{}), 117 }, nil 118 } 119 120 // Serve starts serving the LoadBalancer service on a gRPC server. 121 // 122 // It returns early with a non-nil error if it is unable to start serving. 123 // Otherwise, it blocks until Stop() is called, at which point it returns the 124 // error returned by the underlying grpc.Server's Serve() method. 125 func (s *Server) Serve() error { 126 s.mu.Lock() 127 if s.grpcServer != nil { 128 s.mu.Unlock() 129 return errors.New("Serve() called multiple times") 130 } 131 132 server := grpc.NewServer(s.sOpts...) 133 s.grpcServer = server 134 s.mu.Unlock() 135 136 logger.Infof("Begin listening on %s", s.lis.Addr().String()) 137 lbgrpc.RegisterLoadBalancerServer(server, s) 138 return server.Serve(s.lis) // This call will block. 139 } 140 141 // Stop stops serving the LoadBalancer service and unblocks the preceding call 142 // to Serve(). 143 func (s *Server) Stop() { 144 defer close(s.stopped) 145 s.mu.Lock() 146 if s.grpcServer != nil { 147 s.grpcServer.Stop() 148 s.grpcServer = nil 149 } 150 s.mu.Unlock() 151 } 152 153 // Address returns the host:port on which the LoadBalancer service is serving. 154 func (s *Server) Address() string { 155 s.mu.Lock() 156 defer s.mu.Unlock() 157 return s.address 158 } 159 160 // BalanceLoad provides a fake implementation of the LoadBalancer service. 161 func (s *Server) BalanceLoad(stream lbgrpc.LoadBalancer_BalanceLoadServer) error { 162 logger.Info("New BalancerLoad stream started") 163 164 req, err := stream.Recv() 165 if err == io.EOF { 166 logger.Warning("Received EOF when reading from the stream") 167 return nil 168 } 169 if err != nil { 170 logger.Warning("Failed to read LoadBalanceRequest from stream: %v", err) 171 return err 172 } 173 logger.Infof("Received LoadBalancerRequest:\n%s", pretty.ToJSON(req)) 174 175 // Initial request contains the service being load balanced for. 176 initialReq := req.GetInitialRequest() 177 if initialReq == nil { 178 logger.Info("First message on the stream does not contain an InitialLoadBalanceRequest") 179 return status.Error(codes.Unknown, "First request not an InitialLoadBalanceRequest") 180 } 181 182 // Basic validation of the service name and port from the incoming request. 183 // 184 // Clients targeting service:port can sometimes include the ":port" suffix in 185 // their requested names; handle this case. 186 serviceName, port, err := net.SplitHostPort(initialReq.Name) 187 if err != nil { 188 // Requested name did not contain a port. So, use the name as is. 189 serviceName = initialReq.Name 190 } else { 191 p, err := strconv.Atoi(port) 192 if err != nil { 193 logger.Info("Failed to parse requested service port %q to integer", port) 194 return status.Error(codes.Unknown, "Bad requested service port number") 195 } 196 if p != s.servicePort { 197 logger.Info("Requested service port number %q does not match expected", port, s.servicePort) 198 return status.Error(codes.Unknown, "Bad requested service port number") 199 } 200 } 201 if serviceName != s.serviceName { 202 logger.Info("Requested service name %q does not match expected %q", serviceName, s.serviceName) 203 return status.Error(codes.NotFound, "Bad requested service name") 204 } 205 206 // Empty initial response disables stats reporting from the client. Stats 207 // reporting from the client is used to determine backend load and is not 208 // required for the purposes of this fake. 209 initResp := &lbpb.LoadBalanceResponse{ 210 LoadBalanceResponseType: &lbpb.LoadBalanceResponse_InitialResponse{ 211 InitialResponse: &lbpb.InitialLoadBalanceResponse{}, 212 }, 213 } 214 if err := stream.Send(initResp); err != nil { 215 logger.Warningf("Failed to send InitialLoadBalanceResponse on the stream: %v", err) 216 return err 217 } 218 219 resp := &lbpb.LoadBalanceResponse{ 220 LoadBalanceResponseType: &lbpb.LoadBalanceResponse_ServerList{ 221 ServerList: &lbpb.ServerList{Servers: s.backends}, 222 }, 223 } 224 logger.Infof("Sending response with server list: %s", pretty.ToJSON(resp)) 225 if err := stream.Send(resp); err != nil { 226 logger.Warningf("Failed to send InitialLoadBalanceResponse on the stream: %v", err) 227 return err 228 } 229 230 if s.shortStream { 231 logger.Info("Ending stream early as the short stream option was set") 232 return nil 233 } 234 235 for { 236 select { 237 case <-stream.Context().Done(): 238 return nil 239 case <-s.stopped: 240 return nil 241 case <-time.After(10 * time.Second): 242 logger.Infof("Sending response with server list: %s", pretty.ToJSON(resp)) 243 if err := stream.Send(resp); err != nil { 244 logger.Warningf("Failed to send InitialLoadBalanceResponse on the stream: %v", err) 245 return err 246 } 247 } 248 } 249 }