google.golang.org/grpc@v1.72.2/xds/test/eds_resource_missing_test.go (about)

     1  /*
     2   *
     3   * Copyright 2025 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 xds_test
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/uuid"
    29  	"google.golang.org/grpc"
    30  	"google.golang.org/grpc/codes"
    31  	"google.golang.org/grpc/credentials/insecure"
    32  	"google.golang.org/grpc/internal"
    33  	"google.golang.org/grpc/internal/grpctest"
    34  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    35  	"google.golang.org/grpc/internal/testutils/xds/e2e/setup"
    36  	"google.golang.org/grpc/internal/xds/bootstrap"
    37  	"google.golang.org/grpc/resolver"
    38  	"google.golang.org/grpc/status"
    39  	"google.golang.org/grpc/xds/internal/xdsclient"
    40  
    41  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    42  	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    43  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    44  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    45  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    46  	testpb "google.golang.org/grpc/interop/grpc_testing"
    47  
    48  	_ "google.golang.org/grpc/xds" // To register the xDS resolver and LB policies.
    49  )
    50  
    51  const (
    52  	defaultTestWatchExpiryTimeout = 500 * time.Millisecond
    53  	defaultTestTimeout            = 5 * time.Second
    54  )
    55  
    56  type s struct {
    57  	grpctest.Tester
    58  }
    59  
    60  func Test(t *testing.T) {
    61  	grpctest.RunSubTests(t, s{})
    62  }
    63  
    64  // Test verifies the xDS-enabled gRPC channel's behavior when the management
    65  // server fails to send an EDS resource referenced by a Cluster resource. The
    66  // expected outcome is an RPC failure with a status code Unavailable and a
    67  // message indicating the absence of available targets.
    68  func (s) TestEDS_MissingResource(t *testing.T) {
    69  	// Start an xDS management server.
    70  	mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true})
    71  
    72  	// Create bootstrap configuration pointing to the above management server.
    73  	nodeID := uuid.New().String()
    74  	bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
    75  	config, err := bootstrap.NewConfigFromContents(bc)
    76  	if err != nil {
    77  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
    78  	}
    79  
    80  	// Create an xDS client with a short resource expiry timer.
    81  	pool := xdsclient.NewPool(config)
    82  	xdsC, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
    83  		Name:               t.Name(),
    84  		WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
    85  	})
    86  	if err != nil {
    87  		t.Fatalf("Failed to create xDS client: %v", err)
    88  	}
    89  	defer xdsClose()
    90  
    91  	// Create an xDS resolver for the test that uses the above xDS client.
    92  	resolverBuilder := internal.NewXDSResolverWithClientForTesting.(func(xdsclient.XDSClient) (resolver.Builder, error))
    93  	xdsResolver, err := resolverBuilder(xdsC)
    94  	if err != nil {
    95  		t.Fatalf("Failed to create xDS resolver for testing: %v", err)
    96  	}
    97  
    98  	// Create resources on the management server. No EDS resource is configured.
    99  	const serviceName = "my-service-client-side-xds"
   100  	const routeConfigName = "route-" + serviceName
   101  	const clusterName = "cluster-" + serviceName
   102  	const endpointsName = "endpoints-" + serviceName
   103  	resources := e2e.UpdateOptions{
   104  		NodeID:         nodeID,
   105  		SkipValidation: true, // Cluster resource refers to an EDS resource that is not configured.
   106  		Listeners:      []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)},
   107  		Routes:         []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, serviceName, clusterName)},
   108  		Clusters:       []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, endpointsName, e2e.SecurityLevelNone)},
   109  	}
   110  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   111  	defer cancel()
   112  	if err := mgmtServer.Update(ctx, resources); err != nil {
   113  		t.Fatal(err)
   114  	}
   115  
   116  	// Create a ClientConn with the xds:/// scheme.
   117  	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver))
   118  	if err != nil {
   119  		t.Fatalf("Failed to create a grpc channel: %v", err)
   120  	}
   121  	defer cc.Close()
   122  
   123  	// Make an RPC and verify that it fails with the expected error.
   124  	client := testgrpc.NewTestServiceClient(cc)
   125  	_, err = client.EmptyCall(ctx, &testpb.Empty{})
   126  	if err == nil {
   127  		t.Fatal("EmptyCall() succeeded, want failure")
   128  	}
   129  	if gotCode, wantCode := status.Code(err), codes.Unavailable; gotCode != wantCode {
   130  		t.Errorf("EmptyCall() failed with code = %v, want %s", gotCode, wantCode)
   131  	}
   132  	if gotMsg, wantMsg := err.Error(), "no targets to pick from"; !strings.Contains(gotMsg, wantMsg) {
   133  		t.Errorf("EmptyCall() failed with message = %q, want to contain %q", gotMsg, wantMsg)
   134  	}
   135  }
   136  
   137  // Test verifies the xDS-enabled gRPC channel's behavior when the management
   138  // server sends an EDS resource with no endpoints. The expected outcome is an
   139  // RPC failure with a status code Unavailable and a message indicating the
   140  // absence of available targets.
   141  func (s) TestEDS_NoEndpointsInResource(t *testing.T) {
   142  	managementServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)
   143  
   144  	// Create resources on the management server, with the EDS resource
   145  	// containing no endpoints.
   146  	const serviceName = "my-service-client-side-xds"
   147  	const routeConfigName = "route-" + serviceName
   148  	const clusterName = "cluster-" + serviceName
   149  	const endpointsName = "endpoints-" + serviceName
   150  	resources := e2e.UpdateOptions{
   151  		NodeID:         nodeID,
   152  		SkipValidation: true, // Cluster resource refers to an EDS resource that is not configured.
   153  		Listeners:      []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)},
   154  		Routes:         []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, serviceName, clusterName)},
   155  		Clusters:       []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, endpointsName, e2e.SecurityLevelNone)},
   156  		Endpoints: []*v3endpointpb.ClusterLoadAssignment{
   157  			e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
   158  				ClusterName: "endpoints-" + serviceName,
   159  				Host:        "localhost",
   160  			}),
   161  		},
   162  	}
   163  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   164  	defer cancel()
   165  	if err := managementServer.Update(ctx, resources); err != nil {
   166  		t.Fatal(err)
   167  	}
   168  
   169  	// Create a ClientConn with the xds:/// scheme.
   170  	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver))
   171  	if err != nil {
   172  		t.Fatalf("Failed to create a grpc channel: %v", err)
   173  	}
   174  	defer cc.Close()
   175  
   176  	// Make an RPC and verify that it fails with the expected error.
   177  	client := testgrpc.NewTestServiceClient(cc)
   178  	_, err = client.EmptyCall(ctx, &testpb.Empty{})
   179  	if err == nil {
   180  		t.Fatal("EmptyCall() succeeded, want failure")
   181  	}
   182  	if gotCode, wantCode := status.Code(err), codes.Unavailable; gotCode != wantCode {
   183  		t.Errorf("EmptyCall() failed with code = %v, want %s", gotCode, wantCode)
   184  	}
   185  	if gotMsg, wantMsg := err.Error(), "no targets to pick from"; !strings.Contains(gotMsg, wantMsg) {
   186  		t.Errorf("EmptyCall() failed with message = %q, want to contain %q", gotMsg, wantMsg)
   187  	}
   188  }