Skip to content

Commit 3519e1d

Browse files
committed
address review comments: compare full structures in Equal and tests
1 parent 206b758 commit 3519e1d

5 files changed

Lines changed: 179 additions & 86 deletions

File tree

internal/xds/bootstrap/bootstrap_test.go

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,54 +1749,75 @@ func (s) TestAllowedGRPCServices_UnmarshalJSON(t *testing.T) {
17491749
tests := []struct {
17501750
name string
17511751
json string
1752-
// The identity of the built channel credentials must match the first
1753-
// supported channel-creds entry from the bootstrap JSON.
1754-
wantSelectedChannelCredsType string
1755-
wantSelectedChannelCredsConfig json.RawMessage
1756-
wantSideCallCreds int
1752+
// want carries the expected target and credential identities;
1753+
// comparisons use the Equal methods via cmp.Diff.
1754+
want AllowedGRPCServices
17571755
}{
17581756
{
1759-
name: "insecure_channel_creds",
1760-
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}]}}`,
1761-
wantSelectedChannelCredsType: "insecure",
1762-
wantSideCallCreds: 0,
1757+
name: "insecure_channel_creds",
1758+
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}]}}`,
1759+
want: AllowedGRPCServices{target: {
1760+
targetURI: target,
1761+
sideChannelCreds: xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "insecure"}, nil),
1762+
}},
17631763
},
17641764
{
1765-
name: "with_call_creds",
1766-
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}], "call_creds": [{"type": "jwt_token_file", "config": {"jwt_token_file": "/var/run/secrets/tokens/istio-token"}}]}}`,
1767-
wantSelectedChannelCredsType: "insecure",
1768-
// One call credential is built for the supported call-creds
1769-
// config.
1770-
wantSideCallCreds: 1,
1765+
name: "with_call_creds",
1766+
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}], "call_creds": [{"type": "jwt_token_file", "config": {"jwt_token_file": "/var/run/secrets/tokens/istio-token"}}]}}`,
1767+
want: AllowedGRPCServices{target: {
1768+
targetURI: target,
1769+
sideChannelCreds: xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "insecure"}, nil),
1770+
sideCallCreds: []*xdscreds.CallCreds{xdscreds.NewCallCreds(nil, xdscreds.Identity{
1771+
Type: "jwt_token_file",
1772+
Data: json.RawMessage(`{"jwt_token_file": "/var/run/secrets/tokens/istio-token"}`),
1773+
}, nil)},
1774+
}},
17711775
},
17721776
{
1773-
name: "unsupported_call_creds_skipped",
1774-
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}], "call_creds": [{"type": "unsupported_call_creds_type"}]}}`,
1775-
wantSelectedChannelCredsType: "insecure",
1776-
// Unsupported call-creds types are skipped without error, so
1777-
// no call credentials are built.
1778-
wantSideCallCreds: 0,
1777+
// Unsupported call-creds types are skipped without error, so no
1778+
// call credentials are built.
1779+
name: "unsupported_call_creds_skipped",
1780+
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}], "call_creds": [{"type": "unsupported_call_creds_type"}]}}`,
1781+
want: AllowedGRPCServices{target: {
1782+
targetURI: target,
1783+
sideChannelCreds: xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "insecure"}, nil),
1784+
}},
17791785
},
17801786
{
1781-
name: "multiple_supported_call_creds",
1782-
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}], "call_creds": [{"type": "jwt_token_file", "config": {"jwt_token_file": "/tokens/token-one"}}, {"type": "jwt_token_file", "config": {"jwt_token_file": "/tokens/token-two"}}]}}`,
1783-
wantSelectedChannelCredsType: "insecure",
17841787
// One call credential is built for each supported call-creds
1785-
// config.
1786-
wantSideCallCreds: 2,
1788+
// config, preserving order.
1789+
name: "multiple_supported_call_creds",
1790+
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "insecure"}], "call_creds": [{"type": "jwt_token_file", "config": {"jwt_token_file": "/tokens/token-one"}}, {"type": "jwt_token_file", "config": {"jwt_token_file": "/tokens/token-two"}}]}}`,
1791+
want: AllowedGRPCServices{target: {
1792+
targetURI: target,
1793+
sideChannelCreds: xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "insecure"}, nil),
1794+
sideCallCreds: []*xdscreds.CallCreds{
1795+
xdscreds.NewCallCreds(nil, xdscreds.Identity{
1796+
Type: "jwt_token_file",
1797+
Data: json.RawMessage(`{"jwt_token_file": "/tokens/token-one"}`),
1798+
}, nil),
1799+
xdscreds.NewCallCreds(nil, xdscreds.Identity{
1800+
Type: "jwt_token_file",
1801+
Data: json.RawMessage(`{"jwt_token_file": "/tokens/token-two"}`),
1802+
}, nil),
1803+
},
1804+
}},
17871805
},
17881806
{
1789-
name: "tls_channel_creds",
1790-
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "tls", "config": {}}]}}`,
1791-
wantSelectedChannelCredsType: "tls",
1792-
wantSelectedChannelCredsConfig: json.RawMessage("{}"),
1793-
wantSideCallCreds: 0,
1807+
name: "tls_channel_creds",
1808+
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "tls", "config": {}}]}}`,
1809+
want: AllowedGRPCServices{target: {
1810+
targetURI: target,
1811+
sideChannelCreds: xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "tls", Data: json.RawMessage("{}")}, nil),
1812+
}},
17941813
},
17951814
{
1796-
name: "skips_unsupported_channel_creds",
1797-
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "unsupported_cred_type"}, {"type": "insecure"}]}}`,
1798-
wantSelectedChannelCredsType: "insecure",
1799-
wantSideCallCreds: 0,
1815+
name: "skips_unsupported_channel_creds",
1816+
json: `{"dns:///sharding-service:443": {"channel_creds": [{"type": "unsupported_cred_type"}, {"type": "insecure"}]}}`,
1817+
want: AllowedGRPCServices{target: {
1818+
targetURI: target,
1819+
sideChannelCreds: xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "insecure"}, nil),
1820+
}},
18001821
},
18011822
}
18021823

@@ -1806,24 +1827,15 @@ func (s) TestAllowedGRPCServices_UnmarshalJSON(t *testing.T) {
18061827
if err := json.Unmarshal([]byte(test.json), &got); err != nil {
18071828
t.Fatalf("AllowedGRPCServices unmarshal failed: %v", err)
18081829
}
1809-
svc, ok := got[target]
1810-
if !ok {
1811-
t.Fatalf("AllowedGRPCServices missing key %q", target)
1812-
}
1813-
if svc.TargetURI() != target {
1814-
t.Errorf("TargetURI() = %q, want %q", svc.TargetURI(), target)
1830+
if diff := cmp.Diff(test.want, got); diff != "" {
1831+
t.Errorf("AllowedGRPCServices unmarshal returned unexpected diff (-want +got):\n%s", diff)
18151832
}
1816-
chanCreds, callCreds := svc.SideChannelCredentials()
1833+
// Equal compares credentials by identity only, so it cannot tell
1834+
// a built bundle from a nil one; verify the bundle was built.
1835+
chanCreds, _ := got[target].SideChannelCredentials()
18171836
if chanCreds == nil || chanCreds.Bundle() == nil {
18181837
t.Error("SideChannelCredentials() returned no built channel credentials")
18191838
}
1820-
wantIdentity := xdscreds.Identity{Type: test.wantSelectedChannelCredsType, Data: test.wantSelectedChannelCredsConfig}
1821-
if wantChanCreds := xdscreds.NewChannelCreds(nil, wantIdentity, nil); !chanCreds.Equal(wantChanCreds) {
1822-
t.Errorf("SideChannelCredentials() channel credentials = %+v, want identity %+v", chanCreds, wantIdentity)
1823-
}
1824-
if got := len(callCreds); got != test.wantSideCallCreds {
1825-
t.Errorf("len(SideChannelCredentials() call creds) = %d, want %d", got, test.wantSideCallCreds)
1826-
}
18271839
})
18281840
}
18291841
}

internal/xds/grpcservice/grpcservice.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package grpcservice
2222

2323
import (
2424
"fmt"
25+
"maps"
2526
"net/url"
2627
"slices"
2728
"strings"
@@ -62,15 +63,14 @@ type Config struct {
6263
CallCredentials []*xdscreds.CallCreds
6364
}
6465

65-
// Equal reports whether c and other describe the same side channel: the same
66-
// target with the same channel and call credential identities. Timeout and
67-
// initial metadata are applied per-RPC and intentionally do not affect
68-
// channel sharing.
66+
// Equal reports whether c and other are equal.
6967
func (c *Config) Equal(other *Config) bool {
7068
targetEqual := c.TargetURI == other.TargetURI
69+
timeoutEqual := c.Timeout == other.Timeout
70+
metadataEqual := maps.EqualFunc(c.InitialMetadata, other.InitialMetadata, slices.Equal)
7171
channelCredsEqual := c.ChannelCredentials.Equal(other.ChannelCredentials)
7272
callCredsEqual := slices.EqualFunc(c.CallCredentials, other.CallCredentials, (*xdscreds.CallCreds).Equal)
73-
return targetEqual && channelCredsEqual && callCredsEqual
73+
return targetEqual && timeoutEqual && metadataEqual && channelCredsEqual && callCredsEqual
7474
}
7575

7676
// Close releases the credentials owned by the config. It is idempotent, and a
@@ -84,10 +84,7 @@ func (c *Config) Close() {
8484
}
8585

8686
// Dial creates a channel to the side-channel service, using the channel and
87-
// call credentials from the config along with the provided dial options. The
88-
// call credentials are attached to the channel; since they are part of the
89-
// config's identity, configs whose call credentials differ do not share a
90-
// channel.
87+
// call credentials from the config along with the provided dial options.
9188
//
9289
// Dial does not take ownership of the config: the caller releases the
9390
// config's credentials via Close when the config is no longer needed, after

internal/xds/grpcservice/grpcservice_test.go

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/json"
2323
"strings"
2424
"testing"
25+
"time"
2526

2627
"github.com/google/go-cmp/cmp"
2728
"google.golang.org/grpc/internal/envconfig"
@@ -215,13 +216,43 @@ func (s) TestParse(t *testing.T) {
215216
config: bootstrapConfig(t, "{}"),
216217
wantErr: "target_uri must be non-empty",
217218
},
219+
{
220+
name: "valid_timeout",
221+
gs: googleGrpcService(target, []*anypb.Any{insecurePlugin}, nil, durationpb.New(10*time.Second)),
222+
sc: trustedServerConfig(t),
223+
config: bootstrapConfig(t, "{}"),
224+
want: &Config{
225+
TargetURI: target,
226+
Timeout: 10 * time.Second,
227+
ChannelCredentials: xdscreds.NewChannelCreds(nil, protoIdentity(insecurePlugin), nil),
228+
},
229+
},
218230
{
219231
name: "zero_timeout_rejected",
220232
gs: googleGrpcService(target, []*anypb.Any{insecurePlugin}, nil, durationpb.New(0)),
221233
sc: trustedServerConfig(t),
222234
config: bootstrapConfig(t, "{}"),
223235
wantErr: "timeout must be strictly positive",
224236
},
237+
{
238+
name: "initial_metadata",
239+
gs: func() *v3corepb.GrpcService {
240+
gs := googleGrpcService(target, []*anypb.Any{insecurePlugin}, nil, nil)
241+
gs.InitialMetadata = []*v3corepb.HeaderValue{
242+
{Key: "key-b", Value: "b"},
243+
// raw_value takes precedence over the legacy value field.
244+
{Key: "key-a", Value: "legacy", RawValue: []byte("raw-a")},
245+
}
246+
return gs
247+
}(),
248+
sc: trustedServerConfig(t),
249+
config: bootstrapConfig(t, "{}"),
250+
want: &Config{
251+
TargetURI: target,
252+
InitialMetadata: metadata.MD{"key-b": {"b"}, "key-a": {"raw-a"}},
253+
ChannelCredentials: xdscreds.NewChannelCreds(nil, protoIdentity(insecurePlugin), nil),
254+
},
255+
},
225256
}
226257

227258
for _, test := range tests {
@@ -266,22 +297,6 @@ func (s) TestConfigDial(t *testing.T) {
266297
}
267298
}
268299

269-
func (s) TestParseInitialMetadata(t *testing.T) {
270-
gs := googleGrpcService(target, []*anypb.Any{{TypeUrl: insecureCredsTypeURL}}, nil, nil)
271-
gs.InitialMetadata = []*v3corepb.HeaderValue{
272-
{Key: "key-b", Value: "b"},
273-
{Key: "key-a", Value: "legacy", RawValue: []byte("raw-a")},
274-
}
275-
got, err := Parse(gs, bootstrapConfig(t, "{}"), trustedServerConfig(t))
276-
if err != nil {
277-
t.Fatalf("Parse() returned unexpected error: %v", err)
278-
}
279-
want := metadata.MD{"key-b": []string{"b"}, "key-a": []string{"raw-a"}}
280-
if diff := cmp.Diff(want, got.InitialMetadata); diff != "" {
281-
t.Errorf("Parse() InitialMetadata mismatch (-want +got):\n%s", diff)
282-
}
283-
}
284-
285300
func (s) TestConfigEqual(t *testing.T) {
286301
insecurePlugin := &anypb.Any{TypeUrl: insecureCredsTypeURL}
287302
protoInsecure := xdscreds.NewChannelCreds(nil, protoIdentity(insecurePlugin), nil)
@@ -293,16 +308,22 @@ func (s) TestConfigEqual(t *testing.T) {
293308
want bool
294309
}{
295310
{
296-
name: "equal_identities_share",
297-
a: &Config{TargetURI: target, ChannelCredentials: protoInsecure},
298-
b: &Config{TargetURI: target, ChannelCredentials: xdscreds.NewChannelCreds(nil, protoIdentity(insecurePlugin), nil)},
311+
name: "equal",
312+
a: &Config{TargetURI: target, ChannelCredentials: protoInsecure, Timeout: 1, InitialMetadata: metadata.Pairs("k", "v")},
313+
b: &Config{TargetURI: target, ChannelCredentials: xdscreds.NewChannelCreds(nil, protoIdentity(insecurePlugin), nil), Timeout: 1, InitialMetadata: metadata.Pairs("k", "v")},
299314
want: true,
300315
},
301316
{
302-
name: "timeout_and_metadata_do_not_affect_sharing",
317+
name: "different_timeouts",
303318
a: &Config{TargetURI: target, ChannelCredentials: protoInsecure, Timeout: 1},
319+
b: &Config{TargetURI: target, ChannelCredentials: protoInsecure, Timeout: 2},
320+
want: false,
321+
},
322+
{
323+
name: "different_initial_metadata",
324+
a: &Config{TargetURI: target, ChannelCredentials: protoInsecure},
304325
b: &Config{TargetURI: target, ChannelCredentials: protoInsecure, InitialMetadata: metadata.Pairs("k", "v")},
305-
want: true,
326+
want: false,
306327
},
307328
{
308329
name: "different_targets",

internal/xds/httpfilter/extproc/config_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,55 @@ func overrideCreateExtProcChannel(t *testing.T, failTarget string) {
129129
t.Cleanup(func() { iextproc.CreateExtProcChannel = origCreateExtProcChannel })
130130
}
131131

132+
// Tests that channel sharing considers the target and the credential
133+
// identities, and ignores the per-RPC timeout and initial metadata.
134+
func (s) TestSharesChannel(t *testing.T) {
135+
insecureCreds := func() *xdscreds.ChannelCreds {
136+
return xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "insecure"}, nil)
137+
}
138+
const target = "dns:///proc-server:443"
139+
140+
tests := []struct {
141+
name string
142+
a, b *grpcservice.Config
143+
want bool
144+
}{
145+
{
146+
name: "equal_identities_share_despite_timeout_and_metadata",
147+
a: &grpcservice.Config{TargetURI: target, ChannelCredentials: insecureCreds(), Timeout: time.Second},
148+
b: &grpcservice.Config{TargetURI: target, ChannelCredentials: insecureCreds(), InitialMetadata: metadata.Pairs("k", "v")},
149+
want: true,
150+
},
151+
{
152+
name: "different_targets",
153+
a: &grpcservice.Config{TargetURI: target, ChannelCredentials: insecureCreds()},
154+
b: &grpcservice.Config{TargetURI: "dns:///other:443", ChannelCredentials: insecureCreds()},
155+
want: false,
156+
},
157+
{
158+
name: "different_channel_creds",
159+
a: &grpcservice.Config{TargetURI: target, ChannelCredentials: insecureCreds()},
160+
b: &grpcservice.Config{TargetURI: target, ChannelCredentials: xdscreds.NewChannelCreds(nil, xdscreds.Identity{Type: "other"}, nil)},
161+
want: false,
162+
},
163+
{
164+
name: "different_call_creds",
165+
a: &grpcservice.Config{TargetURI: target, ChannelCredentials: insecureCreds()},
166+
b: &grpcservice.Config{TargetURI: target, ChannelCredentials: insecureCreds(), CallCredentials: []*xdscreds.CallCreds{
167+
xdscreds.NewCallCreds(nil, xdscreds.Identity{Type: "access_token"}, nil),
168+
}},
169+
want: false,
170+
},
171+
}
172+
for _, tt := range tests {
173+
t.Run(tt.name, func(t *testing.T) {
174+
if got := sharesChannel(tt.a, tt.b); got != tt.want {
175+
t.Errorf("sharesChannel() = %v, want %v", got, tt.want)
176+
}
177+
})
178+
}
179+
}
180+
132181
var cmpOpts = []cmp.Option{
133182
cmp.AllowUnexported(
134183
baseConfig{},

internal/xds/httpfilter/extproc/ext_proc.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"google.golang.org/grpc/internal/grpcsync"
4040
"google.golang.org/grpc/internal/optional"
4141
"google.golang.org/grpc/internal/resolver"
42+
xdscreds "google.golang.org/grpc/internal/xds/credentials"
4243
"google.golang.org/grpc/internal/xds/grpcservice"
4344
"google.golang.org/grpc/internal/xds/httpfilter"
4445
iextproc "google.golang.org/grpc/internal/xds/httpfilter/extproc/internal"
@@ -270,28 +271,41 @@ type clientFilter struct {
270271

271272
func (*clientFilter) Close() {}
272273

273-
// getProcChannel returns an existing refcounted client for an equal server
274-
// config if present and its refcount is incremented successfully.
274+
// sharesChannel reports whether two GrpcService configs may share a channel
275+
// to the external processor server: the same target with the same channel
276+
// and call credential identities. Timeout and initial metadata are applied
277+
// per-RPC and do not affect sharing; call credentials do, because Dial
278+
// attaches them to the channel.
279+
func sharesChannel(a, b *grpcservice.Config) bool {
280+
targetEqual := a.TargetURI == b.TargetURI
281+
channelCredsEqual := a.ChannelCredentials.Equal(b.ChannelCredentials)
282+
callCredsEqual := slices.EqualFunc(a.CallCredentials, b.CallCredentials, (*xdscreds.CallCreds).Equal)
283+
return targetEqual && channelCredsEqual && callCredsEqual
284+
}
285+
286+
// getProcChannel returns an existing refcounted client for a channel-sharing
287+
// compatible server config if present and its refcount is incremented
288+
// successfully.
275289
func (cf *clientFilter) getProcChannel(server *grpcservice.Config) *grpcsync.RefCounted[v3procservicegrpc.ExternalProcessorClient] {
276290
cf.mu.Lock()
277291
defer cf.mu.Unlock()
278292
for _, e := range cf.procChannels {
279-
if e.server.Equal(server) && e.rc.TryIncrement() {
293+
if sharesChannel(&e.server, server) && e.rc.TryIncrement() {
280294
return e.rc
281295
}
282296
}
283297
return nil
284298
}
285299

286300
// storeProcChannel stores the created channel entry if no valid channel
287-
// exists for an equal server config. If another goroutine already stored one
288-
// while unlocked, it increments the existing channel's refcount and returns
289-
// it.
301+
// exists for a channel-sharing compatible server config. If another goroutine
302+
// already stored one while unlocked, it increments the existing channel's
303+
// refcount and returns it.
290304
func (cf *clientFilter) storeProcChannel(entry *procChannelEntry) *grpcsync.RefCounted[v3procservicegrpc.ExternalProcessorClient] {
291305
cf.mu.Lock()
292306
defer cf.mu.Unlock()
293307
for _, e := range cf.procChannels {
294-
if e.server.Equal(&entry.server) && e.rc.TryIncrement() {
308+
if sharesChannel(&e.server, &entry.server) && e.rc.TryIncrement() {
295309
return e.rc
296310
}
297311
}

0 commit comments

Comments
 (0)