google.golang.org/grpc@v1.62.1/interop/xds/server/server.go (about)

     1  /*
     2   *
     3   * Copyright 2021 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  // Binary server is the server used for xDS interop tests.
    20  package main
    21  
    22  import (
    23  	"context"
    24  	"flag"
    25  	"fmt"
    26  	"log"
    27  	"net"
    28  	"os"
    29  	"strconv"
    30  	"strings"
    31  	"time"
    32  
    33  	"google.golang.org/grpc"
    34  	"google.golang.org/grpc/admin"
    35  	"google.golang.org/grpc/codes"
    36  	"google.golang.org/grpc/credentials/insecure"
    37  	"google.golang.org/grpc/grpclog"
    38  	"google.golang.org/grpc/health"
    39  	"google.golang.org/grpc/internal/status"
    40  	"google.golang.org/grpc/metadata"
    41  	"google.golang.org/grpc/reflection"
    42  	"google.golang.org/grpc/xds"
    43  
    44  	xdscreds "google.golang.org/grpc/credentials/xds"
    45  	healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
    46  	healthpb "google.golang.org/grpc/health/grpc_health_v1"
    47  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    48  	testpb "google.golang.org/grpc/interop/grpc_testing"
    49  )
    50  
    51  var (
    52  	port             = flag.Int("port", 8080, "Listening port for test service")
    53  	maintenancePort  = flag.Int("maintenance_port", 8081, "Listening port for maintenance services like health, reflection, channelz etc when -secure_mode is true. When -secure_mode is false, all these services will be registered on -port")
    54  	serverID         = flag.String("server_id", "go_server", "Server ID included in response")
    55  	secureMode       = flag.Bool("secure_mode", false, "If true, retrieve security configuration from the management server. Else, use insecure credentials.")
    56  	hostNameOverride = flag.String("host_name_override", "", "If set, use this as the hostname instead of the real hostname")
    57  
    58  	logger = grpclog.Component("interop")
    59  )
    60  
    61  const (
    62  	rpcBehaviorMDKey             = "rpc-behavior"
    63  	grpcPreviousRPCAttemptsMDKey = "grpc-previous-rpc-attempts"
    64  	sleepPfx                     = "sleep-"
    65  	keepOpenVal                  = "keep-open"
    66  	errorCodePfx                 = "error-code-"
    67  	succeedOnRetryPfx            = "succeed-on-retry-attempt-"
    68  	hostnamePfx                  = "hostname="
    69  )
    70  
    71  func getHostname() string {
    72  	if *hostNameOverride != "" {
    73  		return *hostNameOverride
    74  	}
    75  	hostname, err := os.Hostname()
    76  	if err != nil {
    77  		log.Fatalf("failed to get hostname: %v", err)
    78  	}
    79  	return hostname
    80  }
    81  
    82  // testServiceImpl provides an implementation of the TestService defined in
    83  // grpc.testing package.
    84  type testServiceImpl struct {
    85  	testgrpc.UnimplementedTestServiceServer
    86  	hostname string
    87  	serverID string
    88  }
    89  
    90  func (s *testServiceImpl) EmptyCall(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
    91  	grpc.SetHeader(ctx, metadata.Pairs("hostname", s.hostname))
    92  	return &testpb.Empty{}, nil
    93  }
    94  
    95  func (s *testServiceImpl) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
    96  	response := &testpb.SimpleResponse{ServerId: s.serverID, Hostname: s.hostname}
    97  
    98  forLoop:
    99  	for _, headerVal := range getRPCBehaviorMetadata(ctx) {
   100  		// A value can have a prefix "hostname=<string>" followed by a space.
   101  		// In that case, the rest of the value should only be applied
   102  		// if the specified hostname matches the server's hostname.
   103  		if strings.HasPrefix(headerVal, hostnamePfx) {
   104  			splitVal := strings.Split(headerVal, " ")
   105  			if len(splitVal) <= 1 {
   106  				return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'hostname=<string> <header>=<value>' instead", headerVal)
   107  			}
   108  
   109  			if s.hostname != splitVal[0][len(hostnamePfx):] {
   110  				continue forLoop
   111  			}
   112  			headerVal = splitVal[1]
   113  		}
   114  
   115  		switch {
   116  		// If the value matches "sleep-<int>", the server should wait
   117  		// the specified number of seconds before resuming
   118  		// behavior matching and RPC processing.
   119  		case strings.HasPrefix(headerVal, sleepPfx):
   120  			sleep, err := strconv.Atoi(headerVal[len(sleepPfx):])
   121  			if err != nil {
   122  				return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'sleep-<int>' instead", headerVal)
   123  			}
   124  			time.Sleep(time.Duration(sleep) * time.Second)
   125  
   126  		// If the value matches "keep-open", the server should
   127  		// never respond to the request and behavior matching ends.
   128  		case strings.HasPrefix(headerVal, keepOpenVal):
   129  			<-ctx.Done()
   130  			return nil, nil
   131  
   132  		// If the value matches "error-code-<int>", the server should
   133  		// respond with the specified status code and behavior matching ends.
   134  		case strings.HasPrefix(headerVal, errorCodePfx):
   135  			code, err := strconv.Atoi(headerVal[len(errorCodePfx):])
   136  			if err != nil {
   137  				return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'error-code-<int>' instead", headerVal)
   138  			}
   139  			return nil, status.Errorf(codes.Code(code), "rpc failed as per the rpc-behavior header value: %v", headerVal)
   140  
   141  		// If the value matches "success-on-retry-attempt-<int>", and the
   142  		// value of the "grpc-previous-rpc-attempts" metadata field is equal to
   143  		// the specified number, the normal RPC processing should resume
   144  		// and behavior matching ends.
   145  		case strings.HasPrefix(headerVal, succeedOnRetryPfx):
   146  			wantRetry, err := strconv.Atoi(headerVal[len(succeedOnRetryPfx):])
   147  			if err != nil {
   148  				return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'success-on-retry-attempt-<int>' instead", headerVal)
   149  			}
   150  
   151  			mdRetry := getMetadataValues(ctx, grpcPreviousRPCAttemptsMDKey)
   152  			curRetry, err := strconv.Atoi(mdRetry[0])
   153  			if err != nil {
   154  				return nil, status.Errorf(codes.InvalidArgument, "invalid format for grpc-previous-rpc-attempts header: %v", mdRetry[0])
   155  			}
   156  
   157  			if curRetry == wantRetry {
   158  				break forLoop
   159  			}
   160  		}
   161  	}
   162  
   163  	grpc.SetHeader(ctx, metadata.Pairs("hostname", s.hostname))
   164  	return response, status.Err(codes.OK, "")
   165  }
   166  
   167  func getRPCBehaviorMetadata(ctx context.Context) []string {
   168  	mdRPCBehavior := getMetadataValues(ctx, rpcBehaviorMDKey)
   169  	var rpcBehaviorMetadata []string
   170  	for _, mdVal := range mdRPCBehavior {
   171  		splitVals := strings.Split(mdVal, ",")
   172  
   173  		for _, val := range splitVals {
   174  			headerVal := strings.TrimSpace(val)
   175  			if headerVal == "" {
   176  				continue
   177  			}
   178  			rpcBehaviorMetadata = append(rpcBehaviorMetadata, headerVal)
   179  		}
   180  	}
   181  	return rpcBehaviorMetadata
   182  }
   183  
   184  func getMetadataValues(ctx context.Context, metadataKey string) []string {
   185  	md, ok := metadata.FromIncomingContext(ctx)
   186  	if !ok {
   187  		logger.Error("Failed to retrieve metadata from incoming RPC context")
   188  		return nil
   189  	}
   190  	return md.Get(metadataKey)
   191  }
   192  
   193  // xdsUpdateHealthServiceImpl provides an implementation of the
   194  // XdsUpdateHealthService defined in grpc.testing package.
   195  type xdsUpdateHealthServiceImpl struct {
   196  	testgrpc.UnimplementedXdsUpdateHealthServiceServer
   197  	healthServer *health.Server
   198  }
   199  
   200  func (x *xdsUpdateHealthServiceImpl) SetServing(_ context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
   201  	x.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
   202  	return &testpb.Empty{}, nil
   203  
   204  }
   205  
   206  func (x *xdsUpdateHealthServiceImpl) SetNotServing(_ context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
   207  	x.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING)
   208  	return &testpb.Empty{}, nil
   209  }
   210  
   211  func xdsServingModeCallback(addr net.Addr, args xds.ServingModeChangeArgs) {
   212  	logger.Infof("Serving mode callback for xDS server at %q invoked with mode: %q, err: %v", addr.String(), args.Mode, args.Err)
   213  }
   214  
   215  func main() {
   216  	flag.Parse()
   217  
   218  	if *secureMode && *port == *maintenancePort {
   219  		logger.Fatal("-port and -maintenance_port must be different when -secure_mode is set")
   220  	}
   221  
   222  	testService := &testServiceImpl{hostname: getHostname(), serverID: *serverID}
   223  	healthServer := health.NewServer()
   224  	updateHealthService := &xdsUpdateHealthServiceImpl{healthServer: healthServer}
   225  
   226  	// If -secure_mode is not set, expose all services on -port with a regular
   227  	// gRPC server.
   228  	if !*secureMode {
   229  		addr := fmt.Sprintf(":%d", *port)
   230  		lis, err := net.Listen("tcp4", addr)
   231  		if err != nil {
   232  			logger.Fatalf("net.Listen(%s) failed: %v", addr, err)
   233  		}
   234  
   235  		server := grpc.NewServer()
   236  		testgrpc.RegisterTestServiceServer(server, testService)
   237  		healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
   238  		healthgrpc.RegisterHealthServer(server, healthServer)
   239  		testgrpc.RegisterXdsUpdateHealthServiceServer(server, updateHealthService)
   240  		reflection.Register(server)
   241  		cleanup, err := admin.Register(server)
   242  		if err != nil {
   243  			logger.Fatalf("Failed to register admin services: %v", err)
   244  		}
   245  		defer cleanup()
   246  		if err := server.Serve(lis); err != nil {
   247  			logger.Errorf("Serve() failed: %v", err)
   248  		}
   249  		return
   250  	}
   251  
   252  	// Create a listener on -port to expose the test service.
   253  	addr := fmt.Sprintf(":%d", *port)
   254  	testLis, err := net.Listen("tcp4", addr)
   255  	if err != nil {
   256  		logger.Fatalf("net.Listen(%s) failed: %v", addr, err)
   257  	}
   258  
   259  	// Create server-side xDS credentials with a plaintext fallback.
   260  	creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{FallbackCreds: insecure.NewCredentials()})
   261  	if err != nil {
   262  		logger.Fatalf("Failed to create xDS credentials: %v", err)
   263  	}
   264  
   265  	// Create an xDS enabled gRPC server, register the test service
   266  	// implementation and start serving.
   267  	testServer, err := xds.NewGRPCServer(grpc.Creds(creds), xds.ServingModeCallback(xdsServingModeCallback))
   268  	if err != nil {
   269  		logger.Fatal("Failed to create an xDS enabled gRPC server: %v", err)
   270  	}
   271  	testgrpc.RegisterTestServiceServer(testServer, testService)
   272  	go func() {
   273  		if err := testServer.Serve(testLis); err != nil {
   274  			logger.Errorf("test server Serve() failed: %v", err)
   275  		}
   276  	}()
   277  	defer testServer.Stop()
   278  
   279  	// Create a listener on -maintenance_port to expose other services.
   280  	addr = fmt.Sprintf(":%d", *maintenancePort)
   281  	maintenanceLis, err := net.Listen("tcp4", addr)
   282  	if err != nil {
   283  		logger.Fatalf("net.Listen(%s) failed: %v", addr, err)
   284  	}
   285  
   286  	// Create a regular gRPC server and register the maintenance services on
   287  	// it and start serving.
   288  	maintenanceServer := grpc.NewServer()
   289  	healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
   290  	healthgrpc.RegisterHealthServer(maintenanceServer, healthServer)
   291  	testgrpc.RegisterXdsUpdateHealthServiceServer(maintenanceServer, updateHealthService)
   292  	reflection.Register(maintenanceServer)
   293  	cleanup, err := admin.Register(maintenanceServer)
   294  	if err != nil {
   295  		logger.Fatalf("Failed to register admin services: %v", err)
   296  	}
   297  	defer cleanup()
   298  	if err := maintenanceServer.Serve(maintenanceLis); err != nil {
   299  		logger.Errorf("maintenance server Serve() failed: %v", err)
   300  	}
   301  }