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