1// +build go1.12
2
3/*
4 *
5 * Copyright 2021 gRPC authors.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *     http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 */
20
21package xdsclient_test
22
23import (
24	"fmt"
25	"testing"
26	"time"
27
28	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
29	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
30	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
31	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
32	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
33	"github.com/google/go-cmp/cmp"
34	"github.com/google/go-cmp/cmp/cmpopts"
35	"google.golang.org/protobuf/testing/protocmp"
36	"google.golang.org/protobuf/types/known/anypb"
37	"google.golang.org/protobuf/types/known/durationpb"
38
39	"google.golang.org/grpc"
40	"google.golang.org/grpc/credentials/insecure"
41	"google.golang.org/grpc/internal/testutils"
42	xdstestutils "google.golang.org/grpc/xds/internal/testutils"
43	"google.golang.org/grpc/xds/internal/xdsclient"
44	"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
45)
46
47const defaultTestWatchExpiryTimeout = 500 * time.Millisecond
48
49func (s) TestLDSConfigDump(t *testing.T) {
50	const testVersion = "test-version-lds"
51	var (
52		ldsTargets       = []string{"lds.target.good:0000", "lds.target.good:1111"}
53		routeConfigNames = []string{"route-config-0", "route-config-1"}
54		listenerRaws     = make(map[string]*anypb.Any, len(ldsTargets))
55	)
56
57	for i := range ldsTargets {
58		listenersT := &v3listenerpb.Listener{
59			Name: ldsTargets[i],
60			ApiListener: &v3listenerpb.ApiListener{
61				ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
62					RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
63						Rds: &v3httppb.Rds{
64							ConfigSource: &v3corepb.ConfigSource{
65								ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
66							},
67							RouteConfigName: routeConfigNames[i],
68						},
69					},
70					CommonHttpProtocolOptions: &v3corepb.HttpProtocolOptions{
71						MaxStreamDuration: durationpb.New(time.Second),
72					},
73				}),
74			},
75		}
76		listenerRaws[ldsTargets[i]] = testutils.MarshalAny(listenersT)
77	}
78
79	client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
80		BalancerName: testXDSServer,
81		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
82		NodeProto:    xdstestutils.EmptyNodeProtoV2,
83	}, defaultTestWatchExpiryTimeout)
84	if err != nil {
85		t.Fatalf("failed to create client: %v", err)
86	}
87	defer client.Close()
88	updateHandler := client.(xdsclient.UpdateHandler)
89
90	// Expected unknown.
91	if err := compareDump(client.DumpLDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
92		t.Fatalf(err.Error())
93	}
94
95	wantRequested := make(map[string]xdsclient.UpdateWithMD)
96	for _, n := range ldsTargets {
97		cancel := client.WatchListener(n, func(update xdsclient.ListenerUpdate, err error) {})
98		defer cancel()
99		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
100	}
101	// Expected requested.
102	if err := compareDump(client.DumpLDS, "", wantRequested); err != nil {
103		t.Fatalf(err.Error())
104	}
105
106	update0 := make(map[string]xdsclient.ListenerUpdate)
107	want0 := make(map[string]xdsclient.UpdateWithMD)
108	for n, r := range listenerRaws {
109		update0[n] = xdsclient.ListenerUpdate{Raw: r}
110		want0[n] = xdsclient.UpdateWithMD{
111			MD:  xdsclient.UpdateMetadata{Version: testVersion},
112			Raw: r,
113		}
114	}
115	updateHandler.NewListeners(update0, xdsclient.UpdateMetadata{Version: testVersion})
116
117	// Expect ACK.
118	if err := compareDump(client.DumpLDS, testVersion, want0); err != nil {
119		t.Fatalf(err.Error())
120	}
121
122	const nackVersion = "lds-version-nack"
123	var nackErr = fmt.Errorf("lds nack error")
124	updateHandler.NewListeners(
125		map[string]xdsclient.ListenerUpdate{
126			ldsTargets[0]: {},
127		},
128		xdsclient.UpdateMetadata{
129			ErrState: &xdsclient.UpdateErrorMetadata{
130				Version: nackVersion,
131				Err:     nackErr,
132			},
133		},
134	)
135
136	// Expect NACK for [0], but old ACK for [1].
137	wantDump := make(map[string]xdsclient.UpdateWithMD)
138	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
139	// message, as well as the NACK error.
140	wantDump[ldsTargets[0]] = xdsclient.UpdateWithMD{
141		MD: xdsclient.UpdateMetadata{
142			Version: testVersion,
143			ErrState: &xdsclient.UpdateErrorMetadata{
144				Version: nackVersion,
145				Err:     nackErr,
146			},
147		},
148		Raw: listenerRaws[ldsTargets[0]],
149	}
150
151	wantDump[ldsTargets[1]] = xdsclient.UpdateWithMD{
152		MD:  xdsclient.UpdateMetadata{Version: testVersion},
153		Raw: listenerRaws[ldsTargets[1]],
154	}
155	if err := compareDump(client.DumpLDS, nackVersion, wantDump); err != nil {
156		t.Fatalf(err.Error())
157	}
158}
159
160func (s) TestRDSConfigDump(t *testing.T) {
161	const testVersion = "test-version-rds"
162	var (
163		listenerNames = []string{"lds.target.good:0000", "lds.target.good:1111"}
164		rdsTargets    = []string{"route-config-0", "route-config-1"}
165		clusterNames  = []string{"cluster-0", "cluster-1"}
166		routeRaws     = make(map[string]*anypb.Any, len(rdsTargets))
167	)
168
169	for i := range rdsTargets {
170		routeConfigT := &v3routepb.RouteConfiguration{
171			Name: rdsTargets[i],
172			VirtualHosts: []*v3routepb.VirtualHost{
173				{
174					Domains: []string{listenerNames[i]},
175					Routes: []*v3routepb.Route{{
176						Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: ""}},
177						Action: &v3routepb.Route_Route{
178							Route: &v3routepb.RouteAction{
179								ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterNames[i]},
180							},
181						},
182					}},
183				},
184			},
185		}
186
187		routeRaws[rdsTargets[i]] = testutils.MarshalAny(routeConfigT)
188	}
189
190	client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
191		BalancerName: testXDSServer,
192		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
193		NodeProto:    xdstestutils.EmptyNodeProtoV2,
194	}, defaultTestWatchExpiryTimeout)
195	if err != nil {
196		t.Fatalf("failed to create client: %v", err)
197	}
198	defer client.Close()
199	updateHandler := client.(xdsclient.UpdateHandler)
200
201	// Expected unknown.
202	if err := compareDump(client.DumpRDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
203		t.Fatalf(err.Error())
204	}
205
206	wantRequested := make(map[string]xdsclient.UpdateWithMD)
207	for _, n := range rdsTargets {
208		cancel := client.WatchRouteConfig(n, func(update xdsclient.RouteConfigUpdate, err error) {})
209		defer cancel()
210		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
211	}
212	// Expected requested.
213	if err := compareDump(client.DumpRDS, "", wantRequested); err != nil {
214		t.Fatalf(err.Error())
215	}
216
217	update0 := make(map[string]xdsclient.RouteConfigUpdate)
218	want0 := make(map[string]xdsclient.UpdateWithMD)
219	for n, r := range routeRaws {
220		update0[n] = xdsclient.RouteConfigUpdate{Raw: r}
221		want0[n] = xdsclient.UpdateWithMD{
222			MD:  xdsclient.UpdateMetadata{Version: testVersion},
223			Raw: r,
224		}
225	}
226	updateHandler.NewRouteConfigs(update0, xdsclient.UpdateMetadata{Version: testVersion})
227
228	// Expect ACK.
229	if err := compareDump(client.DumpRDS, testVersion, want0); err != nil {
230		t.Fatalf(err.Error())
231	}
232
233	const nackVersion = "rds-version-nack"
234	var nackErr = fmt.Errorf("rds nack error")
235	updateHandler.NewRouteConfigs(
236		map[string]xdsclient.RouteConfigUpdate{
237			rdsTargets[0]: {},
238		},
239		xdsclient.UpdateMetadata{
240			ErrState: &xdsclient.UpdateErrorMetadata{
241				Version: nackVersion,
242				Err:     nackErr,
243			},
244		},
245	)
246
247	// Expect NACK for [0], but old ACK for [1].
248	wantDump := make(map[string]xdsclient.UpdateWithMD)
249	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
250	// message, as well as the NACK error.
251	wantDump[rdsTargets[0]] = xdsclient.UpdateWithMD{
252		MD: xdsclient.UpdateMetadata{
253			Version: testVersion,
254			ErrState: &xdsclient.UpdateErrorMetadata{
255				Version: nackVersion,
256				Err:     nackErr,
257			},
258		},
259		Raw: routeRaws[rdsTargets[0]],
260	}
261	wantDump[rdsTargets[1]] = xdsclient.UpdateWithMD{
262		MD:  xdsclient.UpdateMetadata{Version: testVersion},
263		Raw: routeRaws[rdsTargets[1]],
264	}
265	if err := compareDump(client.DumpRDS, nackVersion, wantDump); err != nil {
266		t.Fatalf(err.Error())
267	}
268}
269
270func (s) TestCDSConfigDump(t *testing.T) {
271	const testVersion = "test-version-cds"
272	var (
273		cdsTargets   = []string{"cluster-0", "cluster-1"}
274		serviceNames = []string{"service-0", "service-1"}
275		clusterRaws  = make(map[string]*anypb.Any, len(cdsTargets))
276	)
277
278	for i := range cdsTargets {
279		clusterT := &v3clusterpb.Cluster{
280			Name:                 cdsTargets[i],
281			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
282			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
283				EdsConfig: &v3corepb.ConfigSource{
284					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
285						Ads: &v3corepb.AggregatedConfigSource{},
286					},
287				},
288				ServiceName: serviceNames[i],
289			},
290			LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
291			LrsServer: &v3corepb.ConfigSource{
292				ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
293					Self: &v3corepb.SelfConfigSource{},
294				},
295			},
296		}
297
298		clusterRaws[cdsTargets[i]] = testutils.MarshalAny(clusterT)
299	}
300
301	client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
302		BalancerName: testXDSServer,
303		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
304		NodeProto:    xdstestutils.EmptyNodeProtoV2,
305	}, defaultTestWatchExpiryTimeout)
306	if err != nil {
307		t.Fatalf("failed to create client: %v", err)
308	}
309	defer client.Close()
310	updateHandler := client.(xdsclient.UpdateHandler)
311
312	// Expected unknown.
313	if err := compareDump(client.DumpCDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
314		t.Fatalf(err.Error())
315	}
316
317	wantRequested := make(map[string]xdsclient.UpdateWithMD)
318	for _, n := range cdsTargets {
319		cancel := client.WatchCluster(n, func(update xdsclient.ClusterUpdate, err error) {})
320		defer cancel()
321		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
322	}
323	// Expected requested.
324	if err := compareDump(client.DumpCDS, "", wantRequested); err != nil {
325		t.Fatalf(err.Error())
326	}
327
328	update0 := make(map[string]xdsclient.ClusterUpdate)
329	want0 := make(map[string]xdsclient.UpdateWithMD)
330	for n, r := range clusterRaws {
331		update0[n] = xdsclient.ClusterUpdate{Raw: r}
332		want0[n] = xdsclient.UpdateWithMD{
333			MD:  xdsclient.UpdateMetadata{Version: testVersion},
334			Raw: r,
335		}
336	}
337	updateHandler.NewClusters(update0, xdsclient.UpdateMetadata{Version: testVersion})
338
339	// Expect ACK.
340	if err := compareDump(client.DumpCDS, testVersion, want0); err != nil {
341		t.Fatalf(err.Error())
342	}
343
344	const nackVersion = "cds-version-nack"
345	var nackErr = fmt.Errorf("cds nack error")
346	updateHandler.NewClusters(
347		map[string]xdsclient.ClusterUpdate{
348			cdsTargets[0]: {},
349		},
350		xdsclient.UpdateMetadata{
351			ErrState: &xdsclient.UpdateErrorMetadata{
352				Version: nackVersion,
353				Err:     nackErr,
354			},
355		},
356	)
357
358	// Expect NACK for [0], but old ACK for [1].
359	wantDump := make(map[string]xdsclient.UpdateWithMD)
360	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
361	// message, as well as the NACK error.
362	wantDump[cdsTargets[0]] = xdsclient.UpdateWithMD{
363		MD: xdsclient.UpdateMetadata{
364			Version: testVersion,
365			ErrState: &xdsclient.UpdateErrorMetadata{
366				Version: nackVersion,
367				Err:     nackErr,
368			},
369		},
370		Raw: clusterRaws[cdsTargets[0]],
371	}
372	wantDump[cdsTargets[1]] = xdsclient.UpdateWithMD{
373		MD:  xdsclient.UpdateMetadata{Version: testVersion},
374		Raw: clusterRaws[cdsTargets[1]],
375	}
376	if err := compareDump(client.DumpCDS, nackVersion, wantDump); err != nil {
377		t.Fatalf(err.Error())
378	}
379}
380
381func (s) TestEDSConfigDump(t *testing.T) {
382	const testVersion = "test-version-cds"
383	var (
384		edsTargets    = []string{"cluster-0", "cluster-1"}
385		localityNames = []string{"locality-0", "locality-1"}
386		addrs         = []string{"addr0:123", "addr1:456"}
387		endpointRaws  = make(map[string]*anypb.Any, len(edsTargets))
388	)
389
390	for i := range edsTargets {
391		clab0 := xdstestutils.NewClusterLoadAssignmentBuilder(edsTargets[i], nil)
392		clab0.AddLocality(localityNames[i], 1, 1, []string{addrs[i]}, nil)
393		claT := clab0.Build()
394
395		endpointRaws[edsTargets[i]] = testutils.MarshalAny(claT)
396	}
397
398	client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
399		BalancerName: testXDSServer,
400		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
401		NodeProto:    xdstestutils.EmptyNodeProtoV2,
402	}, defaultTestWatchExpiryTimeout)
403	if err != nil {
404		t.Fatalf("failed to create client: %v", err)
405	}
406	defer client.Close()
407	updateHandler := client.(xdsclient.UpdateHandler)
408
409	// Expected unknown.
410	if err := compareDump(client.DumpEDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
411		t.Fatalf(err.Error())
412	}
413
414	wantRequested := make(map[string]xdsclient.UpdateWithMD)
415	for _, n := range edsTargets {
416		cancel := client.WatchEndpoints(n, func(update xdsclient.EndpointsUpdate, err error) {})
417		defer cancel()
418		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
419	}
420	// Expected requested.
421	if err := compareDump(client.DumpEDS, "", wantRequested); err != nil {
422		t.Fatalf(err.Error())
423	}
424
425	update0 := make(map[string]xdsclient.EndpointsUpdate)
426	want0 := make(map[string]xdsclient.UpdateWithMD)
427	for n, r := range endpointRaws {
428		update0[n] = xdsclient.EndpointsUpdate{Raw: r}
429		want0[n] = xdsclient.UpdateWithMD{
430			MD:  xdsclient.UpdateMetadata{Version: testVersion},
431			Raw: r,
432		}
433	}
434	updateHandler.NewEndpoints(update0, xdsclient.UpdateMetadata{Version: testVersion})
435
436	// Expect ACK.
437	if err := compareDump(client.DumpEDS, testVersion, want0); err != nil {
438		t.Fatalf(err.Error())
439	}
440
441	const nackVersion = "eds-version-nack"
442	var nackErr = fmt.Errorf("eds nack error")
443	updateHandler.NewEndpoints(
444		map[string]xdsclient.EndpointsUpdate{
445			edsTargets[0]: {},
446		},
447		xdsclient.UpdateMetadata{
448			ErrState: &xdsclient.UpdateErrorMetadata{
449				Version: nackVersion,
450				Err:     nackErr,
451			},
452		},
453	)
454
455	// Expect NACK for [0], but old ACK for [1].
456	wantDump := make(map[string]xdsclient.UpdateWithMD)
457	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
458	// message, as well as the NACK error.
459	wantDump[edsTargets[0]] = xdsclient.UpdateWithMD{
460		MD: xdsclient.UpdateMetadata{
461			Version: testVersion,
462			ErrState: &xdsclient.UpdateErrorMetadata{
463				Version: nackVersion,
464				Err:     nackErr,
465			},
466		},
467		Raw: endpointRaws[edsTargets[0]],
468	}
469	wantDump[edsTargets[1]] = xdsclient.UpdateWithMD{
470		MD:  xdsclient.UpdateMetadata{Version: testVersion},
471		Raw: endpointRaws[edsTargets[1]],
472	}
473	if err := compareDump(client.DumpEDS, nackVersion, wantDump); err != nil {
474		t.Fatalf(err.Error())
475	}
476}
477
478func compareDump(dumpFunc func() (string, map[string]xdsclient.UpdateWithMD), wantVersion string, wantDump interface{}) error {
479	v, dump := dumpFunc()
480	if v != wantVersion {
481		return fmt.Errorf("Dump() returned version %q, want %q", v, wantVersion)
482	}
483	cmpOpts := cmp.Options{
484		cmpopts.EquateEmpty(),
485		cmp.Comparer(func(a, b time.Time) bool { return true }),
486		cmp.Comparer(func(x, y error) bool {
487			if x == nil || y == nil {
488				return x == nil && y == nil
489			}
490			return x.Error() == y.Error()
491		}),
492		protocmp.Transform(),
493	}
494	if diff := cmp.Diff(dump, wantDump, cmpOpts); diff != "" {
495		return fmt.Errorf("Dump() returned unexpected dump, diff (-got +want): %s", diff)
496	}
497	return nil
498}
499