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 }