Skip to content

Commit 9d981b0

Browse files
authored
cherry-pick #6997 to 1.62.x release branch (#6979) (#7018)
1 parent 7c4b553 commit 9d981b0

File tree

7 files changed

+107
-12
lines changed

7 files changed

+107
-12
lines changed

Diff for: clientconn.go

+2
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,8 @@ func parseTarget(target string) (resolver.Target, error) {
17721772
return resolver.Target{URL: *u}, nil
17731773
}
17741774

1775+
// encodeAuthority escapes the authority string based on valid chars defined in
1776+
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.
17751777
func encodeAuthority(authority string) string {
17761778
const upperhex = "0123456789ABCDEF"
17771779

Diff for: internal/testutils/xds/e2e/clientresources.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,11 @@ func HTTPFilter(name string, config proto.Message) *v3httppb.HttpFilter {
365365
}
366366

367367
// DefaultRouteConfig returns a basic xds RouteConfig resource.
368-
func DefaultRouteConfig(routeName, ldsTarget, clusterName string) *v3routepb.RouteConfiguration {
368+
func DefaultRouteConfig(routeName, vhDomain, clusterName string) *v3routepb.RouteConfiguration {
369369
return &v3routepb.RouteConfiguration{
370370
Name: routeName,
371371
VirtualHosts: []*v3routepb.VirtualHost{{
372-
Domains: []string{ldsTarget},
372+
Domains: []string{vhDomain},
373373
Routes: []*v3routepb.Route{{
374374
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
375375
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{

Diff for: resolver/resolver.go

+3
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ type BuildOptions struct {
168168
// field. In most cases though, it is not appropriate, and this field may
169169
// be ignored.
170170
Dialer func(context.Context, string) (net.Conn, error)
171+
// Authority is the effective authority of the clientconn for which the
172+
// resolver is built.
173+
Authority string
171174
}
172175

173176
// An Endpoint is one network endpoint, or server, which may have multiple

Diff for: resolver_wrapper.go

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func (ccr *ccResolverWrapper) start() error {
7575
DialCreds: ccr.cc.dopts.copts.TransportCredentials,
7676
CredsBundle: ccr.cc.dopts.copts.CredsBundle,
7777
Dialer: ccr.cc.dopts.copts.Dialer,
78+
Authority: ccr.cc.authority,
7879
}
7980
var err error
8081
ccr.resolver, err = ccr.cc.resolverBuilder.Build(ccr.cc.parsedTarget, ccr, opts)

Diff for: test/xds/xds_client_federation_test.go

+85
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,91 @@ func (s) TestClientSideFederation(t *testing.T) {
140140
}
141141
}
142142

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+
143228
// TestFederation_UnknownAuthorityInDialTarget tests the case where a ClientConn
144229
// is created with a dial target containing an authority which is not specified
145230
// in the bootstrap configuration. The test verifies that RPCs on the ClientConn

Diff for: xds/internal/resolver/helpers_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package resolver_test
2121
import (
2222
"context"
2323
"fmt"
24+
"net/url"
2425
"strings"
2526
"testing"
2627
"time"
@@ -104,7 +105,9 @@ func buildResolverForTarget(t *testing.T, target resolver.Target) (chan resolver
104105
}
105106
}
106107
tcc := &testutils.ResolverClientConn{Logger: t, UpdateStateF: updateStateF, ReportErrorF: reportErrorF}
107-
r, err := builder.Build(target, tcc, resolver.BuildOptions{})
108+
r, err := builder.Build(target, tcc, resolver.BuildOptions{
109+
Authority: url.PathEscape(target.Endpoint()),
110+
})
108111
if err != nil {
109112
t.Fatalf("Failed to build xDS resolver for target %q: %v", target, err)
110113
}

Diff for: xds/internal/resolver/xds_resolver.go

+10-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ package resolver
2222
import (
2323
"context"
2424
"fmt"
25-
"strings"
2625
"sync/atomic"
2726

2827
"google.golang.org/grpc/internal"
@@ -114,12 +113,8 @@ func (b *xdsResolverBuilder) Build(target resolver.Target, cc resolver.ClientCon
114113
if err != nil {
115114
return nil, err
116115
}
117-
endpoint := target.URL.Path
118-
if endpoint == "" {
119-
endpoint = target.URL.Opaque
120-
}
121-
endpoint = strings.TrimPrefix(endpoint, "/")
122-
r.ldsResourceName = bootstrap.PopulateResourceTemplate(template, endpoint)
116+
r.dataplaneAuthority = opts.Authority
117+
r.ldsResourceName = bootstrap.PopulateResourceTemplate(template, target.Endpoint())
123118
r.listenerWatcher = newListenerWatcher(r.ldsResourceName, r)
124119
return r, nil
125120
}
@@ -190,6 +185,12 @@ type xdsResolver struct {
190185
serializer *grpcsync.CallbackSerializer
191186
serializerCancel context.CancelFunc
192187

188+
// dataplaneAuthority is the authority used for the data plane connections,
189+
// which is also used to select the VirtualHost within the xDS
190+
// RouteConfiguration. This is %-encoded to match with VirtualHost Domain
191+
// in xDS RouteConfiguration.
192+
dataplaneAuthority string
193+
193194
ldsResourceName string
194195
listenerWatcher *listenerWatcher
195196
listenerUpdateRecvd bool
@@ -413,9 +414,9 @@ func (r *xdsResolver) onResolutionComplete() {
413414
}
414415

415416
func (r *xdsResolver) applyRouteConfigUpdate(update xdsresource.RouteConfigUpdate) {
416-
matchVh := xdsresource.FindBestMatchingVirtualHost(r.ldsResourceName, update.VirtualHosts)
417+
matchVh := xdsresource.FindBestMatchingVirtualHost(r.dataplaneAuthority, update.VirtualHosts)
417418
if matchVh == nil {
418-
r.onError(fmt.Errorf("no matching virtual host found for %q", r.ldsResourceName))
419+
r.onError(fmt.Errorf("no matching virtual host found for %q", r.dataplaneAuthority))
419420
return
420421
}
421422
r.currentRouteConfig = update

0 commit comments

Comments
 (0)