google.golang.org/grpc@v1.62.1/test/xds/xds_client_federation_test.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 package xds_test 20 21 import ( 22 "context" 23 "fmt" 24 "strings" 25 "testing" 26 27 "github.com/google/uuid" 28 "google.golang.org/grpc" 29 "google.golang.org/grpc/codes" 30 "google.golang.org/grpc/credentials/insecure" 31 "google.golang.org/grpc/internal" 32 "google.golang.org/grpc/internal/stubserver" 33 "google.golang.org/grpc/internal/testutils" 34 "google.golang.org/grpc/internal/testutils/xds/bootstrap" 35 "google.golang.org/grpc/internal/testutils/xds/e2e" 36 "google.golang.org/grpc/resolver" 37 "google.golang.org/grpc/status" 38 39 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 40 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 41 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 42 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 43 testgrpc "google.golang.org/grpc/interop/grpc_testing" 44 testpb "google.golang.org/grpc/interop/grpc_testing" 45 ) 46 47 // TestClientSideFederation tests that federation is supported. 48 // 49 // In this test, some xDS responses contain resource names in another authority 50 // (in the new resource name style): 51 // - LDS: old style, no authority (default authority) 52 // - RDS: new style, in a different authority 53 // - CDS: old style, no authority (default authority) 54 // - EDS: new style, in a different authority 55 func (s) TestClientSideFederation(t *testing.T) { 56 // Start a management server as the default authority. 57 serverDefaultAuth, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) 58 if err != nil { 59 t.Fatalf("Failed to spin up the xDS management server: %v", err) 60 } 61 t.Cleanup(serverDefaultAuth.Stop) 62 63 // Start another management server as the other authority. 64 const nonDefaultAuth = "non-default-auth" 65 serverAnotherAuth, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) 66 if err != nil { 67 t.Fatalf("Failed to spin up the xDS management server: %v", err) 68 } 69 t.Cleanup(serverAnotherAuth.Stop) 70 71 // Create a bootstrap file in a temporary directory. 72 nodeID := uuid.New().String() 73 bootstrapContents, err := bootstrap.Contents(bootstrap.Options{ 74 NodeID: nodeID, 75 ServerURI: serverDefaultAuth.Address, 76 ServerListenerResourceNameTemplate: e2e.ServerListenerResourceNameTemplate, 77 // Specify the address of the non-default authority. 78 Authorities: map[string]string{nonDefaultAuth: serverAnotherAuth.Address}, 79 }) 80 if err != nil { 81 t.Fatalf("Failed to create bootstrap file: %v", err) 82 } 83 84 resolverBuilder := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error)) 85 resolver, err := resolverBuilder(bootstrapContents) 86 if err != nil { 87 t.Fatalf("Failed to create xDS resolver for testing: %v", err) 88 } 89 server := stubserver.StartTestService(t, nil) 90 defer server.Stop() 91 92 const serviceName = "my-service-client-side-xds" 93 // LDS is old style name. 94 ldsName := serviceName 95 // RDS is new style, with the non default authority. 96 rdsName := fmt.Sprintf("xdstp://%s/envoy.config.route.v3.RouteConfiguration/%s", nonDefaultAuth, "route-"+serviceName) 97 // CDS is old style name. 98 cdsName := "cluster-" + serviceName 99 // EDS is new style, with the non default authority. 100 edsName := fmt.Sprintf("xdstp://%s/envoy.config.route.v3.ClusterLoadAssignment/%s", nonDefaultAuth, "endpoints-"+serviceName) 101 102 // Split resources, put LDS/CDS in the default authority, and put RDS/EDS in 103 // the other authority. 104 resourcesDefault := e2e.UpdateOptions{ 105 NodeID: nodeID, 106 // This has only LDS and CDS. 107 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 108 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(cdsName, edsName, e2e.SecurityLevelNone)}, 109 SkipValidation: true, 110 } 111 resourcesAnother := e2e.UpdateOptions{ 112 NodeID: nodeID, 113 // This has only RDS and EDS. 114 Routes: []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(rdsName, ldsName, cdsName)}, 115 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(edsName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 116 SkipValidation: true, 117 } 118 119 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 120 defer cancel() 121 // This has only LDS and CDS. 122 if err := serverDefaultAuth.Update(ctx, resourcesDefault); err != nil { 123 t.Fatal(err) 124 } 125 // This has only RDS and EDS. 126 if err := serverAnotherAuth.Update(ctx, resourcesAnother); err != nil { 127 t.Fatal(err) 128 } 129 130 // Create a ClientConn and make a successful RPC. 131 cc, err := grpc.Dial(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver)) 132 if err != nil { 133 t.Fatalf("failed to dial local test server: %v", err) 134 } 135 defer cc.Close() 136 137 client := testgrpc.NewTestServiceClient(cc) 138 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 139 t.Fatalf("rpc EmptyCall() failed: %v", err) 140 } 141 } 142 143 // TestClientSideFederationWithOnlyXDSTPStyleLDS tests that federation is 144 // supported with new xdstp style names for LDS only while using the old style 145 // for other resources. This test in addition also checks that when service name 146 // contains escapable characters, we "fully" encode it for looking up 147 // VirtualHosts in xDS RouteConfigurtion. 148 func (s) TestClientSideFederationWithOnlyXDSTPStyleLDS(t *testing.T) { 149 // Start a management server as a sophisticated authority. 150 const authority = "traffic-manager.xds.notgoogleapis.com" 151 mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) 152 if err != nil { 153 t.Fatalf("Failed to spin up the xDS management server: %v", err) 154 } 155 t.Cleanup(mgmtServer.Stop) 156 157 // Create a bootstrap file in a temporary directory. 158 nodeID := uuid.New().String() 159 bootstrapContents, err := bootstrap.Contents(bootstrap.Options{ 160 NodeID: nodeID, 161 ServerURI: mgmtServer.Address, 162 ClientDefaultListenerResourceNameTemplate: fmt.Sprintf("xdstp://%s/envoy.config.listener.v3.Listener/%%s", authority), 163 Authorities: map[string]string{authority: mgmtServer.Address}, 164 }) 165 if err != nil { 166 t.Fatalf("Failed to create bootstrap file: %v", err) 167 } 168 169 resolverBuilder := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error)) 170 resolver, err := resolverBuilder(bootstrapContents) 171 if err != nil { 172 t.Fatalf("Failed to create xDS resolver for testing: %v", err) 173 } 174 server := stubserver.StartTestService(t, nil) 175 defer server.Stop() 176 177 // serviceName with escapable characters - ' ', and '/'. 178 const serviceName = "my-service-client-side-xds/2nd component" 179 180 // All other resources are with old style name. 181 const rdsName = "route-" + serviceName 182 const cdsName = "cluster-" + serviceName 183 const edsName = "endpoints-" + serviceName 184 185 // Resource update sent to go-control-plane mgmt server. 186 resourceUpdate := e2e.UpdateOptions{ 187 NodeID: nodeID, 188 Listeners: func() []*v3listenerpb.Listener { 189 // LDS is new style xdstp name. Since the LDS resource name is prefixed 190 // with xdstp, the string will be %-encoded excluding '/'s. See 191 // bootstrap.PopulateResourceTemplate(). 192 const specialEscapedServiceName = "my-service-client-side-xds/2nd%20component" // same as bootstrap.percentEncode(serviceName) 193 ldsName := fmt.Sprintf("xdstp://%s/envoy.config.listener.v3.Listener/%s", authority, specialEscapedServiceName) 194 return []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)} 195 }(), 196 Routes: func() []*v3routepb.RouteConfiguration { 197 // RouteConfiguration will has one entry in []VirutalHosts that contains the 198 // "fully" escaped service name in []Domains. This is to assert that gRPC 199 // uses the escaped service name to lookup VirtualHosts. RDS is also with 200 // old style name. 201 const fullyEscapedServiceName = "my-service-client-side-xds%2F2nd%20component" // same as url.PathEscape(serviceName) 202 return []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(rdsName, fullyEscapedServiceName, cdsName)} 203 }(), 204 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(cdsName, edsName, e2e.SecurityLevelNone)}, 205 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(edsName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 206 SkipValidation: true, 207 } 208 209 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 210 defer cancel() 211 if err := mgmtServer.Update(ctx, resourceUpdate); err != nil { 212 t.Fatal(err) 213 } 214 215 // Create a ClientConn and make a successful RPC. 216 cc, err := grpc.Dial(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver)) 217 if err != nil { 218 t.Fatalf("failed to dial local test server: %v", err) 219 } 220 defer cc.Close() 221 222 client := testgrpc.NewTestServiceClient(cc) 223 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 224 t.Fatalf("rpc EmptyCall() failed: %v", err) 225 } 226 } 227 228 // TestFederation_UnknownAuthorityInDialTarget tests the case where a ClientConn 229 // is created with a dial target containing an authority which is not specified 230 // in the bootstrap configuration. The test verifies that RPCs on the ClientConn 231 // fail with an appropriate error. 232 func (s) TestFederation_UnknownAuthorityInDialTarget(t *testing.T) { 233 // Setting up the management server is not *really* required for this test 234 // case. All we need is a bootstrap configuration which does not contain the 235 // authority mentioned in the dial target. But setting up the management 236 // server and actually making an RPC ensures that the xDS client is 237 // configured properly, and when we dial with an unknown authority in the 238 // next step, we can be sure that the error we receive is legitimate. 239 managementServer, nodeID, _, resolver, cleanup1 := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 240 defer cleanup1() 241 242 server := stubserver.StartTestService(t, nil) 243 defer server.Stop() 244 245 const serviceName = "my-service-client-side-xds" 246 resources := e2e.DefaultClientResources(e2e.ResourceParams{ 247 DialTarget: serviceName, 248 NodeID: nodeID, 249 Host: "localhost", 250 Port: testutils.ParsePort(t, server.Address), 251 SecLevel: e2e.SecurityLevelNone, 252 }) 253 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 254 defer cancel() 255 if err := managementServer.Update(ctx, resources); err != nil { 256 t.Fatal(err) 257 } 258 259 // Create a ClientConn and make a successful RPC. 260 target := fmt.Sprintf("xds:///%s", serviceName) 261 cc, err := grpc.Dial(target, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver)) 262 if err != nil { 263 t.Fatalf("Dialing target %q: %v", target, err) 264 } 265 defer cc.Close() 266 t.Log("Created ClientConn to test service") 267 268 client := testgrpc.NewTestServiceClient(cc) 269 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 270 t.Fatalf("EmptyCall() RPC: %v", err) 271 } 272 t.Log("Successfully performed an EmptyCall RPC") 273 274 target = fmt.Sprintf("xds://unknown-authority/%s", serviceName) 275 t.Logf("Dialing target %q with unknown authority which is expected to fail", target) 276 wantErr := fmt.Sprintf("authority \"unknown-authority\" specified in dial target %q is not found in the bootstrap file", target) 277 _, err = grpc.Dial(target, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver)) 278 if err == nil || !strings.Contains(err.Error(), wantErr) { 279 t.Fatalf("grpc.Dial(%q) returned %v, want: %s", target, err, wantErr) 280 } 281 } 282 283 // TestFederation_UnknownAuthorityInReceivedResponse tests the case where the 284 // LDS resource associated with the dial target contains an RDS resource name 285 // with an authority which is not specified in the bootstrap configuration. The 286 // test verifies that RPCs fail with an appropriate error. 287 func (s) TestFederation_UnknownAuthorityInReceivedResponse(t *testing.T) { 288 mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) 289 if err != nil { 290 t.Fatalf("Failed to spin up the xDS management server: %v", err) 291 } 292 defer mgmtServer.Stop() 293 294 nodeID := uuid.New().String() 295 bootstrapContents, err := bootstrap.Contents(bootstrap.Options{ 296 NodeID: nodeID, 297 ServerURI: mgmtServer.Address, 298 ServerListenerResourceNameTemplate: e2e.ServerListenerResourceNameTemplate, 299 }) 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 resolverBuilder := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error)) 305 resolver, err := resolverBuilder(bootstrapContents) 306 if err != nil { 307 t.Fatalf("Creating xDS resolver for testing: %v", err) 308 } 309 310 // LDS is old style name. 311 // RDS is new style, with an unknown authority. 312 const serviceName = "my-service-client-side-xds" 313 const unknownAuthority = "unknown-authority" 314 ldsName := serviceName 315 rdsName := fmt.Sprintf("xdstp://%s/envoy.config.route.v3.RouteConfiguration/%s", unknownAuthority, "route-"+serviceName) 316 317 resources := e2e.UpdateOptions{ 318 NodeID: nodeID, 319 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 320 Routes: []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(rdsName, ldsName, "cluster-"+serviceName)}, 321 SkipValidation: true, // This update has only LDS and RDS resources. 322 } 323 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 324 defer cancel() 325 if err := mgmtServer.Update(ctx, resources); err != nil { 326 t.Fatal(err) 327 } 328 329 target := fmt.Sprintf("xds:///%s", serviceName) 330 cc, err := grpc.Dial(target, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver)) 331 if err != nil { 332 t.Fatalf("Dialing target %q: %v", target, err) 333 } 334 defer cc.Close() 335 t.Log("Created ClientConn to test service") 336 337 client := testgrpc.NewTestServiceClient(cc) 338 _, err = client.EmptyCall(ctx, &testpb.Empty{}) 339 if err == nil { 340 t.Fatal("EmptyCall RPC succeeded for target with unknown authority when expected to fail") 341 } 342 if got, want := status.Code(err), codes.Unavailable; got != want { 343 t.Fatalf("EmptyCall RPC returned status code: %v, want %v", got, want) 344 } 345 if wantErr := `failed to find authority "unknown-authority"`; !strings.Contains(err.Error(), wantErr) { 346 t.Fatalf("EmptyCall RPC returned error: %v, want %v", err, wantErr) 347 } 348 }