google.golang.org/grpc@v1.72.2/xds/internal/balancer/clustermanager/e2e_test/clustermanager_test.go (about) 1 /* 2 * 3 * Copyright 2024 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 e2e_test 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "testing" 26 "time" 27 28 "google.golang.org/grpc" 29 "google.golang.org/grpc/balancer" 30 "google.golang.org/grpc/balancer/pickfirst" 31 "google.golang.org/grpc/codes" 32 "google.golang.org/grpc/connectivity" 33 "google.golang.org/grpc/credentials/insecure" 34 "google.golang.org/grpc/internal" 35 "google.golang.org/grpc/internal/balancer/stub" 36 "google.golang.org/grpc/internal/grpctest" 37 "google.golang.org/grpc/internal/stubserver" 38 "google.golang.org/grpc/internal/testutils" 39 "google.golang.org/grpc/internal/testutils/xds/e2e" 40 "google.golang.org/grpc/peer" 41 "google.golang.org/grpc/resolver" 42 "google.golang.org/grpc/serviceconfig" 43 "google.golang.org/grpc/status" 44 "google.golang.org/protobuf/types/known/structpb" 45 46 v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3" 47 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 48 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 49 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 50 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 51 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 52 v3pickfirstpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/pick_first/v3" 53 "github.com/google/uuid" 54 testgrpc "google.golang.org/grpc/interop/grpc_testing" 55 testpb "google.golang.org/grpc/interop/grpc_testing" 56 57 _ "google.golang.org/grpc/xds" // Register the xDS name resolver and related LB policies. 58 ) 59 60 type s struct { 61 grpctest.Tester 62 } 63 64 func Test(t *testing.T) { 65 grpctest.RunSubTests(t, s{}) 66 } 67 68 const ( 69 defaultTestTimeout = 10 * time.Second 70 defaultTestShortTimeout = 10 * time.Millisecond 71 ) 72 73 func makeEmptyCallRPCAndVerifyPeer(ctx context.Context, client testgrpc.TestServiceClient, wantPeer string) error { 74 peer := &peer.Peer{} 75 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(peer)); err != nil { 76 return fmt.Errorf("EmptyCall() failed: %v", err) 77 } 78 if gotPeer := peer.Addr.String(); gotPeer != wantPeer { 79 return fmt.Errorf("EmptyCall() routed to %q, want to be routed to: %q", gotPeer, wantPeer) 80 } 81 return nil 82 } 83 84 func makeUnaryCallRPCAndVerifyPeer(ctx context.Context, client testgrpc.TestServiceClient, wantPeer string) error { 85 peer := &peer.Peer{} 86 if _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}, grpc.Peer(peer)); err != nil { 87 return fmt.Errorf("UnaryCall() failed: %v", err) 88 } 89 if gotPeer := peer.Addr.String(); gotPeer != wantPeer { 90 return fmt.Errorf("EmptyCall() routed to %q, want to be routed to: %q", gotPeer, wantPeer) 91 } 92 return nil 93 } 94 95 func (s) TestConfigUpdate_ChildPolicyChange(t *testing.T) { 96 // Spin up an xDS management server. 97 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 98 99 // Create bootstrap configuration pointing to the above management server. 100 nodeID := uuid.New().String() 101 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 102 testutils.CreateBootstrapFileForTesting(t, bc) 103 104 // Create an xDS resolver with the above bootstrap configuration. 105 var resolverBuilder resolver.Builder 106 var err error 107 if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil { 108 resolverBuilder, err = newResolver.(func([]byte) (resolver.Builder, error))(bc) 109 if err != nil { 110 t.Fatalf("Failed to create xDS resolver for testing: %v", err) 111 } 112 } 113 114 // Configure client side xDS resources on the management server. 115 const ( 116 serviceName = "my-service-client-side-xds" 117 routeConfigName = "route-" + serviceName 118 clusterName1 = "cluster1-" + serviceName 119 clusterName2 = "cluster2-" + serviceName 120 endpointsName1 = "endpoints1-" + serviceName 121 endpointsName2 = "endpoints2-" + serviceName 122 endpointsName3 = "endpoints3-" + serviceName 123 ) 124 // A single Listener resource pointing to the following Route 125 // configuration: 126 // - "/grpc.testing.TestService/EmptyCall" --> cluster1 127 // - "/grpc.testing.TestService/UnaryCall" --> cluster2 128 listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)} 129 routes := []*v3routepb.RouteConfiguration{{ 130 Name: routeConfigName, 131 VirtualHosts: []*v3routepb.VirtualHost{{ 132 Domains: []string{serviceName}, 133 Routes: []*v3routepb.Route{ 134 { 135 Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/EmptyCall"}}, 136 Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ 137 ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName1}, 138 }}, 139 }, 140 { 141 Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/UnaryCall"}}, 142 Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ 143 ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName2}, 144 }}, 145 }, 146 }, 147 }}, 148 }} 149 // Two cluster resources corresponding to the ones mentioned in the above 150 // route configuration resource. These are configured with round_robin as 151 // their endpoint picking policy. 152 clusters := []*v3clusterpb.Cluster{ 153 e2e.DefaultCluster(clusterName1, endpointsName1, e2e.SecurityLevelNone), 154 e2e.DefaultCluster(clusterName2, endpointsName2, e2e.SecurityLevelNone), 155 } 156 // Spin up two test backends, one for each cluster below. 157 server1 := stubserver.StartTestService(t, nil) 158 defer server1.Stop() 159 server2 := stubserver.StartTestService(t, nil) 160 defer server2.Stop() 161 // Two endpoints resources, each with one backend from above. 162 endpoints := []*v3endpointpb.ClusterLoadAssignment{ 163 e2e.DefaultEndpoint(endpointsName1, "localhost", []uint32{testutils.ParsePort(t, server1.Address)}), 164 e2e.DefaultEndpoint(endpointsName2, "localhost", []uint32{testutils.ParsePort(t, server2.Address)}), 165 } 166 resources := e2e.UpdateOptions{ 167 NodeID: nodeID, 168 Listeners: listeners, 169 Routes: routes, 170 Clusters: clusters, 171 Endpoints: endpoints, 172 SkipValidation: true, 173 } 174 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 175 defer cancel() 176 if err := mgmtServer.Update(ctx, resources); err != nil { 177 t.Fatal(err) 178 } 179 180 // Create a ClientConn. 181 cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolverBuilder)) 182 if err != nil { 183 t.Fatalf("failed to dial local test server: %v", err) 184 } 185 defer cc.Close() 186 187 // Make an EmptyCall RPC and verify that it is routed to cluster1. 188 client := testgrpc.NewTestServiceClient(cc) 189 if err := makeEmptyCallRPCAndVerifyPeer(ctx, client, server1.Address); err != nil { 190 t.Fatal(err) 191 } 192 193 // Make a UnaryCall RPC and verify that it is routed to cluster2. 194 if err := makeUnaryCallRPCAndVerifyPeer(ctx, client, server2.Address); err != nil { 195 t.Fatal(err) 196 } 197 198 // Create a wrapped pickfirst LB policy. When the endpoint picking policy on 199 // the cluster resource is changed to pickfirst, this will allow us to 200 // verify that load balancing configuration is pushed to it. 201 pfBuilder := balancer.Get(pickfirst.Name) 202 internal.BalancerUnregister(pfBuilder.Name()) 203 204 lbCfgCh := make(chan serviceconfig.LoadBalancingConfig, 1) 205 stub.Register(pfBuilder.Name(), stub.BalancerFuncs{ 206 ParseConfig: func(lbCfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 207 return pfBuilder.(balancer.ConfigParser).ParseConfig(lbCfg) 208 }, 209 Init: func(bd *stub.BalancerData) { 210 bd.Data = pfBuilder.Build(bd.ClientConn, bd.BuildOptions) 211 }, 212 UpdateClientConnState: func(bd *stub.BalancerData, ccs balancer.ClientConnState) error { 213 select { 214 case lbCfgCh <- ccs.BalancerConfig: 215 default: 216 } 217 bal := bd.Data.(balancer.Balancer) 218 return bal.UpdateClientConnState(ccs) 219 }, 220 Close: func(bd *stub.BalancerData) { 221 bal := bd.Data.(balancer.Balancer) 222 bal.Close() 223 }, 224 }) 225 226 // Send a config update that changes the child policy configuration for one 227 // of the clusters to pickfirst. The endpoints resource is also changed here 228 // to ensure that we can verify that the new child policy 229 cluster2 := &v3clusterpb.Cluster{ 230 Name: clusterName2, 231 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 232 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 233 EdsConfig: &v3corepb.ConfigSource{ 234 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 235 Ads: &v3corepb.AggregatedConfigSource{}, 236 }, 237 }, 238 ServiceName: endpointsName3, 239 }, 240 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{ 241 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{ 242 { 243 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{ 244 TypedConfig: testutils.MarshalAny(t, &v3pickfirstpb.PickFirst{ 245 ShuffleAddressList: true, 246 }), 247 }, 248 }, 249 }, 250 }, 251 } 252 server3 := stubserver.StartTestService(t, nil) 253 defer server3.Stop() 254 endpoints3 := e2e.DefaultEndpoint(endpointsName3, "localhost", []uint32{testutils.ParsePort(t, server3.Address)}) 255 resources.Clusters[1] = cluster2 256 resources.Endpoints = append(resources.Endpoints, endpoints3) 257 if err := mgmtServer.Update(ctx, resources); err != nil { 258 t.Fatal(err) 259 } 260 261 select { 262 case <-ctx.Done(): 263 t.Fatalf("Timeout when waiting for configuration to be pushed to the new pickfirst child policy") 264 case <-lbCfgCh: 265 } 266 267 // Ensure RPCs are still succeeding. 268 269 // Make an EmptyCall RPC and verify that it is routed to cluster1. 270 if err := makeEmptyCallRPCAndVerifyPeer(ctx, client, server1.Address); err != nil { 271 t.Fatal(err) 272 } 273 274 // Make a UnaryCall RPC and verify that it is routed to cluster2, and the 275 // new endpoints resource. 276 for ; ctx.Err() != nil; <-time.After(defaultTestShortTimeout) { 277 if err := makeUnaryCallRPCAndVerifyPeer(ctx, client, server3.Address); err == nil { 278 break 279 } 280 t.Log(err) 281 } 282 if ctx.Err() != nil { 283 t.Fatal("Timeout when waiting for RPCs to cluster2 to be routed to the new endpoints resource") 284 } 285 286 // Send a config update that changes the child policy configuration for one 287 // of the clusters to an unsupported LB policy. This should result in 288 // failure of RPCs to that cluster. 289 cluster2 = &v3clusterpb.Cluster{ 290 Name: clusterName2, 291 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 292 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 293 EdsConfig: &v3corepb.ConfigSource{ 294 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 295 Ads: &v3corepb.AggregatedConfigSource{}, 296 }, 297 }, 298 ServiceName: endpointsName3, 299 }, 300 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{ 301 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{ 302 { 303 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{ 304 // The type not registered in gRPC Policy registry. 305 TypedConfig: testutils.MarshalAny(t, &v3xdsxdstypepb.TypedStruct{ 306 TypeUrl: "type.googleapis.com/myorg.ThisTypeDoesNotExist", 307 Value: &structpb.Struct{}, 308 }), 309 }, 310 }, 311 }, 312 }, 313 } 314 resources.Clusters[1] = cluster2 315 if err := mgmtServer.Update(ctx, resources); err != nil { 316 t.Fatal(err) 317 } 318 319 // At this point, RPCs to cluster1 should continue to succeed, while RPCs to 320 // cluster2 should start to fail. 321 322 // Make an EmptyCall RPC and verify that it is routed to cluster1. 323 if err := makeEmptyCallRPCAndVerifyPeer(ctx, client, server1.Address); err != nil { 324 t.Fatal(err) 325 } 326 327 // Make a UnaryCall RPC and verify that it starts to fail. 328 for ; ctx.Err() != nil; <-time.After(defaultTestShortTimeout) { 329 _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}) 330 got := status.Code(err) 331 if got == codes.Unavailable { 332 break 333 } 334 t.Logf("UnaryCall() returned code: %v, want %v", got, codes.Unavailable) 335 } 336 if ctx.Err() != nil { 337 t.Fatal("Timeout when waiting for RPCs to cluster2 to start failing") 338 } 339 340 // Channel should still be READY. 341 if got, want := cc.GetState(), connectivity.Ready; got != want { 342 t.Fatalf("grpc.ClientConn in state %v, want %v", got, want) 343 } 344 }