google.golang.org/grpc@v1.74.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.ChildBalancer = 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 return bd.ChildBalancer.UpdateClientConnState(ccs) 218 }, 219 Close: func(bd *stub.BalancerData) { 220 bd.ChildBalancer.Close() 221 }, 222 }) 223 224 // Send a config update that changes the child policy configuration for one 225 // of the clusters to pickfirst. The endpoints resource is also changed here 226 // to ensure that we can verify that the new child policy 227 cluster2 := &v3clusterpb.Cluster{ 228 Name: clusterName2, 229 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 230 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 231 EdsConfig: &v3corepb.ConfigSource{ 232 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 233 Ads: &v3corepb.AggregatedConfigSource{}, 234 }, 235 }, 236 ServiceName: endpointsName3, 237 }, 238 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{ 239 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{ 240 { 241 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{ 242 TypedConfig: testutils.MarshalAny(t, &v3pickfirstpb.PickFirst{ 243 ShuffleAddressList: true, 244 }), 245 }, 246 }, 247 }, 248 }, 249 } 250 server3 := stubserver.StartTestService(t, nil) 251 defer server3.Stop() 252 endpoints3 := e2e.DefaultEndpoint(endpointsName3, "localhost", []uint32{testutils.ParsePort(t, server3.Address)}) 253 resources.Clusters[1] = cluster2 254 resources.Endpoints = append(resources.Endpoints, endpoints3) 255 if err := mgmtServer.Update(ctx, resources); err != nil { 256 t.Fatal(err) 257 } 258 259 select { 260 case <-ctx.Done(): 261 t.Fatalf("Timeout when waiting for configuration to be pushed to the new pickfirst child policy") 262 case <-lbCfgCh: 263 } 264 265 // Ensure RPCs are still succeeding. 266 267 // Make an EmptyCall RPC and verify that it is routed to cluster1. 268 if err := makeEmptyCallRPCAndVerifyPeer(ctx, client, server1.Address); err != nil { 269 t.Fatal(err) 270 } 271 272 // Make a UnaryCall RPC and verify that it is routed to cluster2, and the 273 // new endpoints resource. 274 for ; ctx.Err() != nil; <-time.After(defaultTestShortTimeout) { 275 if err := makeUnaryCallRPCAndVerifyPeer(ctx, client, server3.Address); err == nil { 276 break 277 } 278 t.Log(err) 279 } 280 if ctx.Err() != nil { 281 t.Fatal("Timeout when waiting for RPCs to cluster2 to be routed to the new endpoints resource") 282 } 283 284 // Send a config update that changes the child policy configuration for one 285 // of the clusters to an unsupported LB policy. This should result in 286 // failure of RPCs to that cluster. 287 cluster2 = &v3clusterpb.Cluster{ 288 Name: clusterName2, 289 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 290 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 291 EdsConfig: &v3corepb.ConfigSource{ 292 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 293 Ads: &v3corepb.AggregatedConfigSource{}, 294 }, 295 }, 296 ServiceName: endpointsName3, 297 }, 298 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{ 299 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{ 300 { 301 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{ 302 // The type not registered in gRPC Policy registry. 303 TypedConfig: testutils.MarshalAny(t, &v3xdsxdstypepb.TypedStruct{ 304 TypeUrl: "type.googleapis.com/myorg.ThisTypeDoesNotExist", 305 Value: &structpb.Struct{}, 306 }), 307 }, 308 }, 309 }, 310 }, 311 } 312 resources.Clusters[1] = cluster2 313 if err := mgmtServer.Update(ctx, resources); err != nil { 314 t.Fatal(err) 315 } 316 317 // At this point, RPCs to cluster1 should continue to succeed, while RPCs to 318 // cluster2 should start to fail. 319 320 // Make an EmptyCall RPC and verify that it is routed to cluster1. 321 if err := makeEmptyCallRPCAndVerifyPeer(ctx, client, server1.Address); err != nil { 322 t.Fatal(err) 323 } 324 325 // Make a UnaryCall RPC and verify that it starts to fail. 326 for ; ctx.Err() != nil; <-time.After(defaultTestShortTimeout) { 327 _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}) 328 got := status.Code(err) 329 if got == codes.Unavailable { 330 break 331 } 332 t.Logf("UnaryCall() returned code: %v, want %v", got, codes.Unavailable) 333 } 334 if ctx.Err() != nil { 335 t.Fatal("Timeout when waiting for RPCs to cluster2 to start failing") 336 } 337 338 // Channel should still be READY. 339 if got, want := cc.GetState(), connectivity.Ready; got != want { 340 t.Fatalf("grpc.ClientConn in state %v, want %v", got, want) 341 } 342 }