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