1/*
2 *
3 * Copyright 2019 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
19package edsbalancer
20
21import (
22	"bytes"
23	"context"
24	"encoding/json"
25	"fmt"
26	"reflect"
27	"testing"
28	"time"
29
30	"github.com/golang/protobuf/jsonpb"
31	wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
32	"github.com/google/go-cmp/cmp"
33	"google.golang.org/grpc/attributes"
34	xdsinternal "google.golang.org/grpc/xds/internal"
35
36	"google.golang.org/grpc/balancer"
37	"google.golang.org/grpc/connectivity"
38	"google.golang.org/grpc/internal/grpclog"
39	"google.golang.org/grpc/internal/grpctest"
40	scpb "google.golang.org/grpc/internal/proto/grpc_service_config"
41	"google.golang.org/grpc/internal/testutils"
42	"google.golang.org/grpc/resolver"
43	"google.golang.org/grpc/serviceconfig"
44	xdsclient "google.golang.org/grpc/xds/internal/client"
45	"google.golang.org/grpc/xds/internal/testutils/fakeclient"
46
47	_ "google.golang.org/grpc/xds/internal/client/v2" // V2 client registration.
48)
49
50const defaultTestTimeout = 1 * time.Second
51
52func init() {
53	balancer.Register(&edsBalancerBuilder{})
54}
55
56func subConnFromPicker(p balancer.Picker) func() balancer.SubConn {
57	return func() balancer.SubConn {
58		scst, _ := p.Pick(balancer.PickInfo{})
59		return scst.SubConn
60	}
61}
62
63type s struct {
64	grpctest.Tester
65}
66
67func Test(t *testing.T) {
68	grpctest.RunSubTests(t, s{})
69}
70
71const testBalancerNameFooBar = "foo.bar"
72
73func newNoopTestClientConn() *noopTestClientConn {
74	return &noopTestClientConn{}
75}
76
77// noopTestClientConn is used in EDS balancer config update tests that only
78// cover the config update handling, but not SubConn/load-balancing.
79type noopTestClientConn struct {
80	balancer.ClientConn
81}
82
83func (t *noopTestClientConn) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
84	return nil, nil
85}
86
87func (noopTestClientConn) Target() string { return testServiceName }
88
89type scStateChange struct {
90	sc    balancer.SubConn
91	state connectivity.State
92}
93
94type fakeEDSBalancer struct {
95	cc                 balancer.ClientConn
96	childPolicy        *testutils.Channel
97	subconnStateChange *testutils.Channel
98	edsUpdate          *testutils.Channel
99}
100
101func (f *fakeEDSBalancer) handleSubConnStateChange(sc balancer.SubConn, state connectivity.State) {
102	f.subconnStateChange.Send(&scStateChange{sc: sc, state: state})
103}
104
105func (f *fakeEDSBalancer) handleChildPolicy(name string, config json.RawMessage) {
106	f.childPolicy.Send(&loadBalancingConfig{Name: name, Config: config})
107}
108
109func (f *fakeEDSBalancer) handleEDSResponse(edsResp xdsclient.EndpointsUpdate) {
110	f.edsUpdate.Send(edsResp)
111}
112
113func (f *fakeEDSBalancer) updateState(priority priorityType, s balancer.State) {}
114
115func (f *fakeEDSBalancer) close() {}
116
117func (f *fakeEDSBalancer) waitForChildPolicy(wantPolicy *loadBalancingConfig) error {
118	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
119	defer cancel()
120
121	val, err := f.childPolicy.Receive(ctx)
122	if err != nil {
123		return fmt.Errorf("error waiting for childPolicy: %v", err)
124	}
125	gotPolicy := val.(*loadBalancingConfig)
126	if !cmp.Equal(gotPolicy, wantPolicy) {
127		return fmt.Errorf("got childPolicy %v, want %v", gotPolicy, wantPolicy)
128	}
129	return nil
130}
131
132func (f *fakeEDSBalancer) waitForSubConnStateChange(wantState *scStateChange) error {
133	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
134	defer cancel()
135
136	val, err := f.subconnStateChange.Receive(ctx)
137	if err != nil {
138		return fmt.Errorf("error waiting for subconnStateChange: %v", err)
139	}
140	gotState := val.(*scStateChange)
141	if !cmp.Equal(gotState, wantState, cmp.AllowUnexported(scStateChange{})) {
142		return fmt.Errorf("got subconnStateChange %v, want %v", gotState, wantState)
143	}
144	return nil
145}
146
147func (f *fakeEDSBalancer) waitForEDSResponse(wantUpdate xdsclient.EndpointsUpdate) error {
148	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
149	defer cancel()
150
151	val, err := f.edsUpdate.Receive(ctx)
152	if err != nil {
153		return fmt.Errorf("error waiting for edsUpdate: %v", err)
154	}
155	gotUpdate := val.(xdsclient.EndpointsUpdate)
156	if !reflect.DeepEqual(gotUpdate, wantUpdate) {
157		return fmt.Errorf("got edsUpdate %+v, want %+v", gotUpdate, wantUpdate)
158	}
159	return nil
160}
161
162func newFakeEDSBalancer(cc balancer.ClientConn) edsBalancerImplInterface {
163	return &fakeEDSBalancer{
164		cc:                 cc,
165		childPolicy:        testutils.NewChannelWithSize(10),
166		subconnStateChange: testutils.NewChannelWithSize(10),
167		edsUpdate:          testutils.NewChannelWithSize(10),
168	}
169}
170
171type fakeSubConn struct{}
172
173func (*fakeSubConn) UpdateAddresses([]resolver.Address) { panic("implement me") }
174func (*fakeSubConn) Connect()                           { panic("implement me") }
175
176// waitForNewEDSLB makes sure that a new edsLB is created by the top-level
177// edsBalancer.
178func waitForNewEDSLB(t *testing.T, ch *testutils.Channel) *fakeEDSBalancer {
179	t.Helper()
180
181	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
182	defer cancel()
183	val, err := ch.Receive(ctx)
184	if err != nil {
185		t.Fatalf("error when waiting for a new edsLB: %v", err)
186		return nil
187	}
188	return val.(*fakeEDSBalancer)
189}
190
191// setup overrides the functions which are used to create the xdsClient and the
192// edsLB, creates fake version of them and makes them available on the provided
193// channels. The returned cancel function should be called by the test for
194// cleanup.
195func setup(edsLBCh *testutils.Channel) func() {
196	origNewEDSBalancer := newEDSBalancer
197	newEDSBalancer = func(cc balancer.ClientConn, enqueue func(priorityType, balancer.State), _ *xdsClientWrapper, logger *grpclog.PrefixLogger) edsBalancerImplInterface {
198		edsLB := newFakeEDSBalancer(cc)
199		defer func() { edsLBCh.Send(edsLB) }()
200		return edsLB
201	}
202	return func() {
203		newEDSBalancer = origNewEDSBalancer
204	}
205}
206
207const (
208	fakeBalancerA = "fake_balancer_A"
209	fakeBalancerB = "fake_balancer_B"
210)
211
212// Install two fake balancers for service config update tests.
213//
214// ParseConfig only accepts the json if the balancer specified is registered.
215
216func init() {
217	balancer.Register(&fakeBalancerBuilder{name: fakeBalancerA})
218	balancer.Register(&fakeBalancerBuilder{name: fakeBalancerB})
219}
220
221type fakeBalancerBuilder struct {
222	name string
223}
224
225func (b *fakeBalancerBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
226	return &fakeBalancer{cc: cc}
227}
228
229func (b *fakeBalancerBuilder) Name() string {
230	return b.name
231}
232
233type fakeBalancer struct {
234	cc balancer.ClientConn
235}
236
237func (b *fakeBalancer) ResolverError(error) {
238	panic("implement me")
239}
240
241func (b *fakeBalancer) UpdateClientConnState(balancer.ClientConnState) error {
242	panic("implement me")
243}
244
245func (b *fakeBalancer) UpdateSubConnState(balancer.SubConn, balancer.SubConnState) {
246	panic("implement me")
247}
248
249func (b *fakeBalancer) Close() {}
250
251// TestXDSConnfigChildPolicyUpdate verifies scenarios where the childPolicy
252// section of the lbConfig is updated.
253//
254// The test does the following:
255// * Builds a new xds balancer.
256// * Pushes a new ClientConnState with a childPolicy set to fakeBalancerA.
257//   Verifies that a new xdsClient is created. It then pushes a new edsUpdate
258//   through the fakexds client. Verifies that a new edsLB is created and it
259//   receives the expected childPolicy.
260// * Pushes a new ClientConnState with a childPolicy set to fakeBalancerB.
261//   This time around, we expect no new xdsClient or edsLB to be created.
262//   Instead, we expect the existing edsLB to receive the new child policy.
263func (s) TestXDSConnfigChildPolicyUpdate(t *testing.T) {
264	xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar)
265	edsLBCh := testutils.NewChannel()
266	cancel := setup(edsLBCh)
267	defer cancel()
268
269	builder := balancer.Get(edsName)
270	cc := newNoopTestClientConn()
271	edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testServiceName}}).(*edsBalancer)
272	if !ok {
273		t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB)
274	}
275	defer edsB.Close()
276
277	edsB.UpdateClientConnState(balancer.ClientConnState{
278		ResolverState: resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)},
279		BalancerConfig: &EDSConfig{
280			ChildPolicy: &loadBalancingConfig{
281				Name:   fakeBalancerA,
282				Config: json.RawMessage("{}"),
283			},
284			EDSServiceName: testEDSClusterName,
285		},
286	})
287
288	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
289	defer ctxCancel()
290	if _, err := xdsC.WaitForWatchEDS(ctx); err != nil {
291		t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err)
292	}
293	xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil)
294	edsLB := waitForNewEDSLB(t, edsLBCh)
295	edsLB.waitForChildPolicy(&loadBalancingConfig{
296		Name:   string(fakeBalancerA),
297		Config: json.RawMessage(`{}`),
298	})
299
300	edsB.UpdateClientConnState(balancer.ClientConnState{
301		ResolverState: resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)},
302		BalancerConfig: &EDSConfig{
303			ChildPolicy: &loadBalancingConfig{
304				Name:   fakeBalancerB,
305				Config: json.RawMessage("{}"),
306			},
307			EDSServiceName: testEDSClusterName,
308		},
309	})
310	edsLB.waitForChildPolicy(&loadBalancingConfig{
311		Name:   string(fakeBalancerA),
312		Config: json.RawMessage(`{}`),
313	})
314}
315
316// TestXDSSubConnStateChange verifies if the top-level edsBalancer passes on
317// the subConnStateChange to appropriate child balancers.
318func (s) TestXDSSubConnStateChange(t *testing.T) {
319	xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar)
320	edsLBCh := testutils.NewChannel()
321	cancel := setup(edsLBCh)
322	defer cancel()
323
324	builder := balancer.Get(edsName)
325	cc := newNoopTestClientConn()
326	edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testEDSClusterName}}).(*edsBalancer)
327	if !ok {
328		t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB)
329	}
330	defer edsB.Close()
331
332	edsB.UpdateClientConnState(balancer.ClientConnState{
333		ResolverState:  resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)},
334		BalancerConfig: &EDSConfig{EDSServiceName: testEDSClusterName},
335	})
336
337	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
338	defer ctxCancel()
339	if _, err := xdsC.WaitForWatchEDS(ctx); err != nil {
340		t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err)
341	}
342	xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil)
343	edsLB := waitForNewEDSLB(t, edsLBCh)
344
345	fsc := &fakeSubConn{}
346	state := connectivity.Ready
347	edsB.UpdateSubConnState(fsc, balancer.SubConnState{ConnectivityState: state})
348	edsLB.waitForSubConnStateChange(&scStateChange{sc: fsc, state: state})
349}
350
351// TestErrorFromXDSClientUpdate verifies that errros from xdsClient update are
352// handled correctly.
353//
354// If it's resource-not-found, watch will NOT be canceled, the EDS impl will
355// receive an empty EDS update, and new RPCs will fail.
356//
357// If it's connection error, nothing will happen. This will need to change to
358// handle fallback.
359func (s) TestErrorFromXDSClientUpdate(t *testing.T) {
360	xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar)
361	edsLBCh := testutils.NewChannel()
362	cancel := setup(edsLBCh)
363	defer cancel()
364
365	builder := balancer.Get(edsName)
366	cc := newNoopTestClientConn()
367	edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testEDSClusterName}}).(*edsBalancer)
368	if !ok {
369		t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB)
370	}
371	defer edsB.Close()
372
373	if err := edsB.UpdateClientConnState(balancer.ClientConnState{
374		ResolverState:  resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)},
375		BalancerConfig: &EDSConfig{EDSServiceName: testEDSClusterName},
376	}); err != nil {
377		t.Fatal(err)
378	}
379
380	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
381	defer ctxCancel()
382	if _, err := xdsC.WaitForWatchEDS(ctx); err != nil {
383		t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err)
384	}
385	xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil)
386	edsLB := waitForNewEDSLB(t, edsLBCh)
387	if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil {
388		t.Fatalf("EDS impl got unexpected EDS response: %v", err)
389	}
390
391	connectionErr := xdsclient.NewErrorf(xdsclient.ErrorTypeConnection, "connection error")
392	xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, connectionErr)
393
394	if err := xdsC.WaitForCancelEDSWatch(ctx); err == nil {
395		t.Fatal("watch was canceled, want not canceled (timeout error)")
396	}
397	if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err == nil {
398		t.Fatal("eds impl got EDS resp, want timeout error")
399	}
400
401	resourceErr := xdsclient.NewErrorf(xdsclient.ErrorTypeResourceNotFound, "edsBalancer resource not found error")
402	xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, resourceErr)
403	// Even if error is resource not found, watch shouldn't be canceled, because
404	// this is an EDS resource removed (and xds client actually never sends this
405	// error, but we still handles it).
406	ctx, ctxCancel = context.WithTimeout(context.Background(), defaultTestTimeout)
407	defer ctxCancel()
408	if err := xdsC.WaitForCancelEDSWatch(ctx); err == nil {
409		t.Fatal("watch was canceled, want not canceled (timeout error)")
410	}
411	if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil {
412		t.Fatalf("eds impl expecting empty update, got %v", err)
413	}
414}
415
416// TestErrorFromResolver verifies that resolver errors are handled correctly.
417//
418// If it's resource-not-found, watch will be canceled, the EDS impl will receive
419// an empty EDS update, and new RPCs will fail.
420//
421// If it's connection error, nothing will happen. This will need to change to
422// handle fallback.
423func (s) TestErrorFromResolver(t *testing.T) {
424	xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar)
425	edsLBCh := testutils.NewChannel()
426	cancel := setup(edsLBCh)
427	defer cancel()
428
429	builder := balancer.Get(edsName)
430	cc := newNoopTestClientConn()
431	edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testEDSClusterName}}).(*edsBalancer)
432	if !ok {
433		t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB)
434	}
435	defer edsB.Close()
436
437	if err := edsB.UpdateClientConnState(balancer.ClientConnState{
438		ResolverState:  resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)},
439		BalancerConfig: &EDSConfig{EDSServiceName: testEDSClusterName},
440	}); err != nil {
441		t.Fatal(err)
442	}
443
444	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
445	defer ctxCancel()
446	if _, err := xdsC.WaitForWatchEDS(ctx); err != nil {
447		t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err)
448	}
449	xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil)
450	edsLB := waitForNewEDSLB(t, edsLBCh)
451	if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil {
452		t.Fatalf("EDS impl got unexpected EDS response: %v", err)
453	}
454
455	connectionErr := xdsclient.NewErrorf(xdsclient.ErrorTypeConnection, "connection error")
456	edsB.ResolverError(connectionErr)
457
458	if err := xdsC.WaitForCancelEDSWatch(ctx); err == nil {
459		t.Fatal("watch was canceled, want not canceled (timeout error)")
460	}
461	if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err == nil {
462		t.Fatal("eds impl got EDS resp, want timeout error")
463	}
464
465	resourceErr := xdsclient.NewErrorf(xdsclient.ErrorTypeResourceNotFound, "edsBalancer resource not found error")
466	edsB.ResolverError(resourceErr)
467	ctx, ctxCancel = context.WithTimeout(context.Background(), defaultTestTimeout)
468	defer ctxCancel()
469	if err := xdsC.WaitForCancelEDSWatch(ctx); err != nil {
470		t.Fatalf("want watch to be canceled, waitForCancel failed: %v", err)
471	}
472	if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil {
473		t.Fatalf("EDS impl got unexpected EDS response: %v", err)
474	}
475}
476
477func (s) TestXDSBalancerConfigParsing(t *testing.T) {
478	const testEDSName = "eds.service"
479	var testLRSName = "lrs.server"
480	b := bytes.NewBuffer(nil)
481	if err := (&jsonpb.Marshaler{}).Marshal(b, &scpb.XdsConfig{
482		ChildPolicy: []*scpb.LoadBalancingConfig{
483			{Policy: &scpb.LoadBalancingConfig_Xds{}},
484			{Policy: &scpb.LoadBalancingConfig_RoundRobin{
485				RoundRobin: &scpb.RoundRobinConfig{},
486			}},
487		},
488		FallbackPolicy: []*scpb.LoadBalancingConfig{
489			{Policy: &scpb.LoadBalancingConfig_Xds{}},
490			{Policy: &scpb.LoadBalancingConfig_PickFirst{
491				PickFirst: &scpb.PickFirstConfig{},
492			}},
493		},
494		EdsServiceName:             testEDSName,
495		LrsLoadReportingServerName: &wrapperspb.StringValue{Value: testLRSName},
496	}); err != nil {
497		t.Fatalf("%v", err)
498	}
499
500	tests := []struct {
501		name    string
502		js      json.RawMessage
503		want    serviceconfig.LoadBalancingConfig
504		wantErr bool
505	}{
506		{
507			name: "jsonpb-generated",
508			js:   b.Bytes(),
509			want: &EDSConfig{
510				ChildPolicy: &loadBalancingConfig{
511					Name:   "round_robin",
512					Config: json.RawMessage("{}"),
513				},
514				FallBackPolicy: &loadBalancingConfig{
515					Name:   "pick_first",
516					Config: json.RawMessage("{}"),
517				},
518				EDSServiceName:             testEDSName,
519				LrsLoadReportingServerName: &testLRSName,
520			},
521			wantErr: false,
522		},
523		{
524			// json with random balancers, and the first is not registered.
525			name: "manually-generated",
526			js: json.RawMessage(`
527{
528  "childPolicy": [
529    {"fake_balancer_C": {}},
530    {"fake_balancer_A": {}},
531    {"fake_balancer_B": {}}
532  ],
533  "fallbackPolicy": [
534    {"fake_balancer_C": {}},
535    {"fake_balancer_B": {}},
536    {"fake_balancer_A": {}}
537  ],
538  "edsServiceName": "eds.service",
539  "lrsLoadReportingServerName": "lrs.server"
540}`),
541			want: &EDSConfig{
542				ChildPolicy: &loadBalancingConfig{
543					Name:   "fake_balancer_A",
544					Config: json.RawMessage("{}"),
545				},
546				FallBackPolicy: &loadBalancingConfig{
547					Name:   "fake_balancer_B",
548					Config: json.RawMessage("{}"),
549				},
550				EDSServiceName:             testEDSName,
551				LrsLoadReportingServerName: &testLRSName,
552			},
553			wantErr: false,
554		},
555		{
556			// json with no lrs server name, LrsLoadReportingServerName should
557			// be nil (not an empty string).
558			name: "no-lrs-server-name",
559			js: json.RawMessage(`
560{
561  "edsServiceName": "eds.service"
562}`),
563			want: &EDSConfig{
564				EDSServiceName:             testEDSName,
565				LrsLoadReportingServerName: nil,
566			},
567			wantErr: false,
568		},
569	}
570	for _, tt := range tests {
571		t.Run(tt.name, func(t *testing.T) {
572			b := &edsBalancerBuilder{}
573			got, err := b.ParseConfig(tt.js)
574			if (err != nil) != tt.wantErr {
575				t.Errorf("edsBalancerBuilder.ParseConfig() error = %v, wantErr %v", err, tt.wantErr)
576				return
577			}
578			if !cmp.Equal(got, tt.want) {
579				t.Errorf(cmp.Diff(got, tt.want))
580			}
581		})
582	}
583}
584func (s) TestLoadbalancingConfigParsing(t *testing.T) {
585	tests := []struct {
586		name string
587		s    string
588		want *EDSConfig
589	}{
590		{
591			name: "empty",
592			s:    "{}",
593			want: &EDSConfig{},
594		},
595		{
596			name: "success1",
597			s:    `{"childPolicy":[{"pick_first":{}}]}`,
598			want: &EDSConfig{
599				ChildPolicy: &loadBalancingConfig{
600					Name:   "pick_first",
601					Config: json.RawMessage(`{}`),
602				},
603			},
604		},
605		{
606			name: "success2",
607			s:    `{"childPolicy":[{"round_robin":{}},{"pick_first":{}}]}`,
608			want: &EDSConfig{
609				ChildPolicy: &loadBalancingConfig{
610					Name:   "round_robin",
611					Config: json.RawMessage(`{}`),
612				},
613			},
614		},
615	}
616	for _, tt := range tests {
617		t.Run(tt.name, func(t *testing.T) {
618			var cfg EDSConfig
619			if err := json.Unmarshal([]byte(tt.s), &cfg); err != nil || !cmp.Equal(&cfg, tt.want) {
620				t.Errorf("test name: %s, parseFullServiceConfig() = %+v, err: %v, want %+v, <nil>", tt.name, cfg, err, tt.want)
621			}
622		})
623	}
624}
625
626func (s) TestEqualStringPointers(t *testing.T) {
627	var (
628		ta1 = "test-a"
629		ta2 = "test-a"
630		tb  = "test-b"
631	)
632	tests := []struct {
633		name string
634		a    *string
635		b    *string
636		want bool
637	}{
638		{"both-nil", nil, nil, true},
639		{"a-non-nil", &ta1, nil, false},
640		{"b-non-nil", nil, &tb, false},
641		{"equal", &ta1, &ta2, true},
642		{"different", &ta1, &tb, false},
643	}
644	for _, tt := range tests {
645		t.Run(tt.name, func(t *testing.T) {
646			if got := equalStringPointers(tt.a, tt.b); got != tt.want {
647				t.Errorf("equalStringPointers() = %v, want %v", got, tt.want)
648			}
649		})
650	}
651}
652