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 tests_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	xdsclient "google.golang.org/grpc/xds/internal/client"
43	"google.golang.org/grpc/xds/internal/client/bootstrap"
44	xdstestutils "google.golang.org/grpc/xds/internal/testutils"
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
89	// Expected unknown.
90	if err := compareDump(client.DumpLDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
91		t.Fatalf(err.Error())
92	}
93
94	wantRequested := make(map[string]xdsclient.UpdateWithMD)
95	for _, n := range ldsTargets {
96		cancel := client.WatchListener(n, func(update xdsclient.ListenerUpdate, err error) {})
97		defer cancel()
98		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
99	}
100	// Expected requested.
101	if err := compareDump(client.DumpLDS, "", wantRequested); err != nil {
102		t.Fatalf(err.Error())
103	}
104
105	update0 := make(map[string]xdsclient.ListenerUpdate)
106	want0 := make(map[string]xdsclient.UpdateWithMD)
107	for n, r := range listenerRaws {
108		update0[n] = xdsclient.ListenerUpdate{Raw: r}
109		want0[n] = xdsclient.UpdateWithMD{
110			MD:  xdsclient.UpdateMetadata{Version: testVersion},
111			Raw: r,
112		}
113	}
114	client.NewListeners(update0, xdsclient.UpdateMetadata{Version: testVersion})
115
116	// Expect ACK.
117	if err := compareDump(client.DumpLDS, testVersion, want0); err != nil {
118		t.Fatalf(err.Error())
119	}
120
121	const nackVersion = "lds-version-nack"
122	var nackErr = fmt.Errorf("lds nack error")
123	client.NewListeners(
124		map[string]xdsclient.ListenerUpdate{
125			ldsTargets[0]: {},
126		},
127		xdsclient.UpdateMetadata{
128			ErrState: &xdsclient.UpdateErrorMetadata{
129				Version: nackVersion,
130				Err:     nackErr,
131			},
132		},
133	)
134
135	// Expect NACK for [0], but old ACK for [1].
136	wantDump := make(map[string]xdsclient.UpdateWithMD)
137	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
138	// message, as well as the NACK error.
139	wantDump[ldsTargets[0]] = xdsclient.UpdateWithMD{
140		MD: xdsclient.UpdateMetadata{
141			Version: testVersion,
142			ErrState: &xdsclient.UpdateErrorMetadata{
143				Version: nackVersion,
144				Err:     nackErr,
145			},
146		},
147		Raw: listenerRaws[ldsTargets[0]],
148	}
149
150	wantDump[ldsTargets[1]] = xdsclient.UpdateWithMD{
151		MD:  xdsclient.UpdateMetadata{Version: testVersion},
152		Raw: listenerRaws[ldsTargets[1]],
153	}
154	if err := compareDump(client.DumpLDS, nackVersion, wantDump); err != nil {
155		t.Fatalf(err.Error())
156	}
157}
158
159func (s) TestRDSConfigDump(t *testing.T) {
160	const testVersion = "test-version-rds"
161	var (
162		listenerNames = []string{"lds.target.good:0000", "lds.target.good:1111"}
163		rdsTargets    = []string{"route-config-0", "route-config-1"}
164		clusterNames  = []string{"cluster-0", "cluster-1"}
165		routeRaws     = make(map[string]*anypb.Any, len(rdsTargets))
166	)
167
168	for i := range rdsTargets {
169		routeConfigT := &v3routepb.RouteConfiguration{
170			Name: rdsTargets[i],
171			VirtualHosts: []*v3routepb.VirtualHost{
172				{
173					Domains: []string{listenerNames[i]},
174					Routes: []*v3routepb.Route{{
175						Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: ""}},
176						Action: &v3routepb.Route_Route{
177							Route: &v3routepb.RouteAction{
178								ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterNames[i]},
179							},
180						},
181					}},
182				},
183			},
184		}
185
186		routeRaws[rdsTargets[i]] = testutils.MarshalAny(routeConfigT)
187	}
188
189	client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
190		BalancerName: testXDSServer,
191		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
192		NodeProto:    xdstestutils.EmptyNodeProtoV2,
193	}, defaultTestWatchExpiryTimeout)
194	if err != nil {
195		t.Fatalf("failed to create client: %v", err)
196	}
197	defer client.Close()
198
199	// Expected unknown.
200	if err := compareDump(client.DumpRDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
201		t.Fatalf(err.Error())
202	}
203
204	wantRequested := make(map[string]xdsclient.UpdateWithMD)
205	for _, n := range rdsTargets {
206		cancel := client.WatchRouteConfig(n, func(update xdsclient.RouteConfigUpdate, err error) {})
207		defer cancel()
208		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
209	}
210	// Expected requested.
211	if err := compareDump(client.DumpRDS, "", wantRequested); err != nil {
212		t.Fatalf(err.Error())
213	}
214
215	update0 := make(map[string]xdsclient.RouteConfigUpdate)
216	want0 := make(map[string]xdsclient.UpdateWithMD)
217	for n, r := range routeRaws {
218		update0[n] = xdsclient.RouteConfigUpdate{Raw: r}
219		want0[n] = xdsclient.UpdateWithMD{
220			MD:  xdsclient.UpdateMetadata{Version: testVersion},
221			Raw: r,
222		}
223	}
224	client.NewRouteConfigs(update0, xdsclient.UpdateMetadata{Version: testVersion})
225
226	// Expect ACK.
227	if err := compareDump(client.DumpRDS, testVersion, want0); err != nil {
228		t.Fatalf(err.Error())
229	}
230
231	const nackVersion = "rds-version-nack"
232	var nackErr = fmt.Errorf("rds nack error")
233	client.NewRouteConfigs(
234		map[string]xdsclient.RouteConfigUpdate{
235			rdsTargets[0]: {},
236		},
237		xdsclient.UpdateMetadata{
238			ErrState: &xdsclient.UpdateErrorMetadata{
239				Version: nackVersion,
240				Err:     nackErr,
241			},
242		},
243	)
244
245	// Expect NACK for [0], but old ACK for [1].
246	wantDump := make(map[string]xdsclient.UpdateWithMD)
247	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
248	// message, as well as the NACK error.
249	wantDump[rdsTargets[0]] = xdsclient.UpdateWithMD{
250		MD: xdsclient.UpdateMetadata{
251			Version: testVersion,
252			ErrState: &xdsclient.UpdateErrorMetadata{
253				Version: nackVersion,
254				Err:     nackErr,
255			},
256		},
257		Raw: routeRaws[rdsTargets[0]],
258	}
259	wantDump[rdsTargets[1]] = xdsclient.UpdateWithMD{
260		MD:  xdsclient.UpdateMetadata{Version: testVersion},
261		Raw: routeRaws[rdsTargets[1]],
262	}
263	if err := compareDump(client.DumpRDS, nackVersion, wantDump); err != nil {
264		t.Fatalf(err.Error())
265	}
266}
267
268func (s) TestCDSConfigDump(t *testing.T) {
269	const testVersion = "test-version-cds"
270	var (
271		cdsTargets   = []string{"cluster-0", "cluster-1"}
272		serviceNames = []string{"service-0", "service-1"}
273		clusterRaws  = make(map[string]*anypb.Any, len(cdsTargets))
274	)
275
276	for i := range cdsTargets {
277		clusterT := &v3clusterpb.Cluster{
278			Name:                 cdsTargets[i],
279			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
280			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
281				EdsConfig: &v3corepb.ConfigSource{
282					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
283						Ads: &v3corepb.AggregatedConfigSource{},
284					},
285				},
286				ServiceName: serviceNames[i],
287			},
288			LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
289			LrsServer: &v3corepb.ConfigSource{
290				ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
291					Self: &v3corepb.SelfConfigSource{},
292				},
293			},
294		}
295
296		clusterRaws[cdsTargets[i]] = testutils.MarshalAny(clusterT)
297	}
298
299	client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
300		BalancerName: testXDSServer,
301		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
302		NodeProto:    xdstestutils.EmptyNodeProtoV2,
303	}, defaultTestWatchExpiryTimeout)
304	if err != nil {
305		t.Fatalf("failed to create client: %v", err)
306	}
307	defer client.Close()
308
309	// Expected unknown.
310	if err := compareDump(client.DumpCDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
311		t.Fatalf(err.Error())
312	}
313
314	wantRequested := make(map[string]xdsclient.UpdateWithMD)
315	for _, n := range cdsTargets {
316		cancel := client.WatchCluster(n, func(update xdsclient.ClusterUpdate, err error) {})
317		defer cancel()
318		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
319	}
320	// Expected requested.
321	if err := compareDump(client.DumpCDS, "", wantRequested); err != nil {
322		t.Fatalf(err.Error())
323	}
324
325	update0 := make(map[string]xdsclient.ClusterUpdate)
326	want0 := make(map[string]xdsclient.UpdateWithMD)
327	for n, r := range clusterRaws {
328		update0[n] = xdsclient.ClusterUpdate{Raw: r}
329		want0[n] = xdsclient.UpdateWithMD{
330			MD:  xdsclient.UpdateMetadata{Version: testVersion},
331			Raw: r,
332		}
333	}
334	client.NewClusters(update0, xdsclient.UpdateMetadata{Version: testVersion})
335
336	// Expect ACK.
337	if err := compareDump(client.DumpCDS, testVersion, want0); err != nil {
338		t.Fatalf(err.Error())
339	}
340
341	const nackVersion = "cds-version-nack"
342	var nackErr = fmt.Errorf("cds nack error")
343	client.NewClusters(
344		map[string]xdsclient.ClusterUpdate{
345			cdsTargets[0]: {},
346		},
347		xdsclient.UpdateMetadata{
348			ErrState: &xdsclient.UpdateErrorMetadata{
349				Version: nackVersion,
350				Err:     nackErr,
351			},
352		},
353	)
354
355	// Expect NACK for [0], but old ACK for [1].
356	wantDump := make(map[string]xdsclient.UpdateWithMD)
357	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
358	// message, as well as the NACK error.
359	wantDump[cdsTargets[0]] = xdsclient.UpdateWithMD{
360		MD: xdsclient.UpdateMetadata{
361			Version: testVersion,
362			ErrState: &xdsclient.UpdateErrorMetadata{
363				Version: nackVersion,
364				Err:     nackErr,
365			},
366		},
367		Raw: clusterRaws[cdsTargets[0]],
368	}
369	wantDump[cdsTargets[1]] = xdsclient.UpdateWithMD{
370		MD:  xdsclient.UpdateMetadata{Version: testVersion},
371		Raw: clusterRaws[cdsTargets[1]],
372	}
373	if err := compareDump(client.DumpCDS, nackVersion, wantDump); err != nil {
374		t.Fatalf(err.Error())
375	}
376}
377
378func (s) TestEDSConfigDump(t *testing.T) {
379	const testVersion = "test-version-cds"
380	var (
381		edsTargets    = []string{"cluster-0", "cluster-1"}
382		localityNames = []string{"locality-0", "locality-1"}
383		addrs         = []string{"addr0:123", "addr1:456"}
384		endpointRaws  = make(map[string]*anypb.Any, len(edsTargets))
385	)
386
387	for i := range edsTargets {
388		clab0 := xdstestutils.NewClusterLoadAssignmentBuilder(edsTargets[i], nil)
389		clab0.AddLocality(localityNames[i], 1, 1, []string{addrs[i]}, nil)
390		claT := clab0.Build()
391
392		endpointRaws[edsTargets[i]] = testutils.MarshalAny(claT)
393	}
394
395	client, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
396		BalancerName: testXDSServer,
397		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
398		NodeProto:    xdstestutils.EmptyNodeProtoV2,
399	}, defaultTestWatchExpiryTimeout)
400	if err != nil {
401		t.Fatalf("failed to create client: %v", err)
402	}
403	defer client.Close()
404
405	// Expected unknown.
406	if err := compareDump(client.DumpEDS, "", map[string]xdsclient.UpdateWithMD{}); err != nil {
407		t.Fatalf(err.Error())
408	}
409
410	wantRequested := make(map[string]xdsclient.UpdateWithMD)
411	for _, n := range edsTargets {
412		cancel := client.WatchEndpoints(n, func(update xdsclient.EndpointsUpdate, err error) {})
413		defer cancel()
414		wantRequested[n] = xdsclient.UpdateWithMD{MD: xdsclient.UpdateMetadata{Status: xdsclient.ServiceStatusRequested}}
415	}
416	// Expected requested.
417	if err := compareDump(client.DumpEDS, "", wantRequested); err != nil {
418		t.Fatalf(err.Error())
419	}
420
421	update0 := make(map[string]xdsclient.EndpointsUpdate)
422	want0 := make(map[string]xdsclient.UpdateWithMD)
423	for n, r := range endpointRaws {
424		update0[n] = xdsclient.EndpointsUpdate{Raw: r}
425		want0[n] = xdsclient.UpdateWithMD{
426			MD:  xdsclient.UpdateMetadata{Version: testVersion},
427			Raw: r,
428		}
429	}
430	client.NewEndpoints(update0, xdsclient.UpdateMetadata{Version: testVersion})
431
432	// Expect ACK.
433	if err := compareDump(client.DumpEDS, testVersion, want0); err != nil {
434		t.Fatalf(err.Error())
435	}
436
437	const nackVersion = "eds-version-nack"
438	var nackErr = fmt.Errorf("eds nack error")
439	client.NewEndpoints(
440		map[string]xdsclient.EndpointsUpdate{
441			edsTargets[0]: {},
442		},
443		xdsclient.UpdateMetadata{
444			ErrState: &xdsclient.UpdateErrorMetadata{
445				Version: nackVersion,
446				Err:     nackErr,
447			},
448		},
449	)
450
451	// Expect NACK for [0], but old ACK for [1].
452	wantDump := make(map[string]xdsclient.UpdateWithMD)
453	// Though resource 0 was NACKed, the dump should show the previous ACKed raw
454	// message, as well as the NACK error.
455	wantDump[edsTargets[0]] = xdsclient.UpdateWithMD{
456		MD: xdsclient.UpdateMetadata{
457			Version: testVersion,
458			ErrState: &xdsclient.UpdateErrorMetadata{
459				Version: nackVersion,
460				Err:     nackErr,
461			},
462		},
463		Raw: endpointRaws[edsTargets[0]],
464	}
465	wantDump[edsTargets[1]] = xdsclient.UpdateWithMD{
466		MD:  xdsclient.UpdateMetadata{Version: testVersion},
467		Raw: endpointRaws[edsTargets[1]],
468	}
469	if err := compareDump(client.DumpEDS, nackVersion, wantDump); err != nil {
470		t.Fatalf(err.Error())
471	}
472}
473
474func compareDump(dumpFunc func() (string, map[string]xdsclient.UpdateWithMD), wantVersion string, wantDump interface{}) error {
475	v, dump := dumpFunc()
476	if v != wantVersion {
477		return fmt.Errorf("Dump() returned version %q, want %q", v, wantVersion)
478	}
479	cmpOpts := cmp.Options{
480		cmpopts.EquateEmpty(),
481		cmp.Comparer(func(a, b time.Time) bool { return true }),
482		cmp.Comparer(func(x, y error) bool {
483			if x == nil || y == nil {
484				return x == nil && y == nil
485			}
486			return x.Error() == y.Error()
487		}),
488		protocmp.Transform(),
489	}
490	if diff := cmp.Diff(dump, wantDump, cmpOpts); diff != "" {
491		return fmt.Errorf("Dump() returned unexpected dump, diff (-got +want): %s", diff)
492	}
493	return nil
494}
495