Skip to content

Commit 90b1784

Browse files
yousukseungjanardhankrishna-sai
authored andcommitted
xds: add xDS transport custom dial options support (grpc#7997)
1 parent 408b04e commit 90b1784

File tree

2 files changed

+181
-7
lines changed

2 files changed

+181
-7
lines changed

internal/xds/bootstrap/bootstrap.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ type ServerConfig struct {
179179
// As part of unmarshalling the JSON config into this struct, we ensure that
180180
// the credentials config is valid by building an instance of the specified
181181
// credentials and store it here for easy access.
182-
selectedCreds ChannelCreds
183-
credsDialOption grpc.DialOption
184-
dialerOption grpc.DialOption
182+
selectedCreds ChannelCreds
183+
credsDialOption grpc.DialOption
184+
extraDialOptions []grpc.DialOption
185185

186186
cleanups []func()
187187
}
@@ -224,8 +224,8 @@ func (sc *ServerConfig) ServerFeaturesIgnoreResourceDeletion() bool {
224224
// server.
225225
func (sc *ServerConfig) DialOptions() []grpc.DialOption {
226226
dopts := []grpc.DialOption{sc.credsDialOption}
227-
if sc.dialerOption != nil {
228-
dopts = append(dopts, sc.dialerOption)
227+
if sc.extraDialOptions != nil {
228+
dopts = append(dopts, sc.extraDialOptions...)
229229
}
230230
return dopts
231231
}
@@ -283,11 +283,18 @@ func (sc *ServerConfig) MarshalJSON() ([]byte, error) {
283283
}
284284

285285
// dialer captures the Dialer method specified via the credentials bundle.
286+
// Deprecated: use extradDialOptions. Will take precedence over this.
286287
type dialer interface {
287288
// Dialer specifies how to dial the xDS server.
288289
Dialer(context.Context, string) (net.Conn, error)
289290
}
290291

292+
// extraDialOptions captures custom dial options specified via
293+
// credentials.Bundle.
294+
type extraDialOptions interface {
295+
DialOptions() []grpc.DialOption
296+
}
297+
291298
// UnmarshalJSON takes the json data (a server) and unmarshals it to the struct.
292299
func (sc *ServerConfig) UnmarshalJSON(data []byte) error {
293300
server := serverConfigJSON{}
@@ -311,8 +318,10 @@ func (sc *ServerConfig) UnmarshalJSON(data []byte) error {
311318
}
312319
sc.selectedCreds = cc
313320
sc.credsDialOption = grpc.WithCredentialsBundle(bundle)
314-
if d, ok := bundle.(dialer); ok {
315-
sc.dialerOption = grpc.WithContextDialer(d.Dialer)
321+
if d, ok := bundle.(extraDialOptions); ok {
322+
sc.extraDialOptions = d.DialOptions()
323+
} else if d, ok := bundle.(dialer); ok {
324+
sc.extraDialOptions = []grpc.DialOption{grpc.WithContextDialer(d.Dialer)}
316325
}
317326
sc.cleanups = append(sc.cleanups, cancel)
318327
break
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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 xdsclient_test
20+
21+
import (
22+
"context"
23+
"encoding/json"
24+
"fmt"
25+
"testing"
26+
27+
"github.com/google/go-cmp/cmp"
28+
"github.com/google/uuid"
29+
"google.golang.org/grpc"
30+
"google.golang.org/grpc/credentials"
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+
internalbootstrap "google.golang.org/grpc/internal/xds/bootstrap"
37+
testgrpc "google.golang.org/grpc/interop/grpc_testing"
38+
testpb "google.golang.org/grpc/interop/grpc_testing"
39+
"google.golang.org/grpc/resolver"
40+
"google.golang.org/grpc/xds/bootstrap"
41+
xci "google.golang.org/grpc/xds/internal/xdsclient/internal"
42+
)
43+
44+
// nopDialOption is a no-op grpc.DialOption with a name.
45+
type nopDialOption struct {
46+
grpc.EmptyDialOption
47+
name string
48+
}
49+
50+
// testCredsBundle implements `credentials.Bundle` and `extraDialOptions`.
51+
type testCredsBundle struct {
52+
credentials.Bundle
53+
testDialOptNames []string
54+
}
55+
56+
func (t *testCredsBundle) DialOptions() []grpc.DialOption {
57+
var opts []grpc.DialOption
58+
for _, name := range t.testDialOptNames {
59+
opts = append(opts, &nopDialOption{name: name})
60+
}
61+
return opts
62+
}
63+
64+
type testCredsBuilder struct {
65+
testDialOptNames []string
66+
}
67+
68+
func (t *testCredsBuilder) Build(config json.RawMessage) (credentials.Bundle, func(), error) {
69+
return &testCredsBundle{
70+
Bundle: insecure.NewBundle(),
71+
testDialOptNames: t.testDialOptNames,
72+
}, func() {}, nil
73+
}
74+
75+
func (t *testCredsBuilder) Name() string {
76+
return "test_dialer_creds"
77+
}
78+
79+
func (s) TestClientCustomDialOptsFromCredentialsBundle(t *testing.T) {
80+
// Create and register the credentials bundle builder.
81+
credsBuilder := &testCredsBuilder{
82+
testDialOptNames: []string{"opt1", "opt2", "opt3"},
83+
}
84+
bootstrap.RegisterCredentials(credsBuilder)
85+
86+
// Start an xDS management server.
87+
mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
88+
89+
// Create bootstrap configuration pointing to the above management server.
90+
nodeID := uuid.New().String()
91+
bc, err := internalbootstrap.NewContentsForTesting(internalbootstrap.ConfigOptionsForTesting{
92+
Servers: []byte(fmt.Sprintf(`[{
93+
"server_uri": %q,
94+
"channel_creds": [{
95+
"type": %q,
96+
"config": {"mgmt_server_address": %q}
97+
}]
98+
}]`, mgmtServer.Address, credsBuilder.Name(), mgmtServer.Address)),
99+
Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)),
100+
})
101+
if err != nil {
102+
t.Fatalf("Failed to create bootstrap configuration: %v", err)
103+
}
104+
105+
// Create an xDS resolver with the above bootstrap configuration.
106+
var resolverBuilder resolver.Builder
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+
// Spin up a test backend.
115+
server := stubserver.StartTestService(t, nil)
116+
defer server.Stop()
117+
118+
// Configure client side xDS resources on the management server.
119+
const serviceName = "my-service-client-side-xds"
120+
resources := e2e.DefaultClientResources(e2e.ResourceParams{
121+
DialTarget: serviceName,
122+
NodeID: nodeID,
123+
Host: "localhost",
124+
Port: testutils.ParsePort(t, server.Address),
125+
SecLevel: e2e.SecurityLevelNone,
126+
})
127+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
128+
defer cancel()
129+
if err := mgmtServer.Update(ctx, resources); err != nil {
130+
t.Fatal(err)
131+
}
132+
133+
// Intercept a grpc.NewClient call from the xds client to validate DialOptions.
134+
xci.GRPCNewClient = func(target string, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
135+
got := map[string]int{}
136+
for _, opt := range opts {
137+
if mo, ok := opt.(*nopDialOption); ok {
138+
got[mo.name]++
139+
}
140+
}
141+
want := map[string]int{}
142+
for _, name := range credsBuilder.testDialOptNames {
143+
want[name]++
144+
}
145+
if !cmp.Equal(got, want) {
146+
t.Errorf("grpc.NewClient() was called with unexpected DialOptions: got %v, want %v", got, want)
147+
}
148+
return grpc.NewClient(target, opts...)
149+
}
150+
defer func() { xci.GRPCNewClient = grpc.NewClient }()
151+
152+
// Create a ClientConn and make a successful RPC. The insecure transport
153+
// credentials passed into the gRPC.NewClient is the credentials for the
154+
// data plane communication with the test backend.
155+
cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolverBuilder))
156+
if err != nil {
157+
t.Fatalf("Failed to dial local test server: %v", err)
158+
}
159+
160+
client := testgrpc.NewTestServiceClient(cc)
161+
if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
162+
t.Fatalf("EmptyCall() failed: %v", err)
163+
}
164+
cc.Close()
165+
}

0 commit comments

Comments
 (0)