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  }