1/*
2 *
3 * Copyright 2020 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 v2
20
21import (
22	"context"
23	"testing"
24	"time"
25
26	xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
27
28	xdsclient "google.golang.org/grpc/xds/internal/client"
29	"google.golang.org/grpc/xds/internal/testutils/fakeserver"
30)
31
32// doLDS makes a LDS watch, and waits for the response and ack to finish.
33//
34// This is called by RDS tests to start LDS first, because LDS is a
35// pre-requirement for RDS, and RDS handle would fail without an existing LDS
36// watch.
37func doLDS(ctx context.Context, t *testing.T, v2c xdsclient.APIClient, fakeServer *fakeserver.Server) {
38	v2c.AddWatch(xdsclient.ListenerResource, goodLDSTarget1)
39	if _, err := fakeServer.XDSRequestChan.Receive(ctx); err != nil {
40		t.Fatalf("Timeout waiting for LDS request: %v", err)
41	}
42}
43
44// TestRDSHandleResponseWithRouting starts a fake xDS server, makes a ClientConn
45// to it, and creates a v2Client using it. Then, it registers an LDS and RDS
46// watcher and tests different RDS responses.
47func (s) TestRDSHandleResponseWithRouting(t *testing.T) {
48	tests := []struct {
49		name          string
50		rdsResponse   *xdspb.DiscoveryResponse
51		wantErr       bool
52		wantUpdate    map[string]xdsclient.RouteConfigUpdate
53		wantUpdateMD  xdsclient.UpdateMetadata
54		wantUpdateErr bool
55	}{
56		// Badly marshaled RDS response.
57		{
58			name:        "badly-marshaled-response",
59			rdsResponse: badlyMarshaledRDSResponse,
60			wantErr:     true,
61			wantUpdate:  nil,
62			wantUpdateMD: xdsclient.UpdateMetadata{
63				Status: xdsclient.ServiceStatusNACKed,
64				ErrState: &xdsclient.UpdateErrorMetadata{
65					Err: errPlaceHolder,
66				},
67			},
68			wantUpdateErr: false,
69		},
70		// Response does not contain RouteConfiguration proto.
71		{
72			name:        "no-route-config-in-response",
73			rdsResponse: badResourceTypeInRDSResponse,
74			wantErr:     true,
75			wantUpdate:  nil,
76			wantUpdateMD: xdsclient.UpdateMetadata{
77				Status: xdsclient.ServiceStatusNACKed,
78				ErrState: &xdsclient.UpdateErrorMetadata{
79					Err: errPlaceHolder,
80				},
81			},
82			wantUpdateErr: false,
83		},
84		// No VirtualHosts in the response. Just one test case here for a bad
85		// RouteConfiguration, since the others are covered in
86		// TestGetClusterFromRouteConfiguration.
87		{
88			name:        "no-virtual-hosts-in-response",
89			rdsResponse: noVirtualHostsInRDSResponse,
90			wantErr:     false,
91			wantUpdate: map[string]xdsclient.RouteConfigUpdate{
92				goodRouteName1: {
93					VirtualHosts: nil,
94					Raw:          marshaledNoVirtualHostsRouteConfig,
95				},
96			},
97			wantUpdateMD: xdsclient.UpdateMetadata{
98				Status: xdsclient.ServiceStatusACKed,
99			},
100			wantUpdateErr: false,
101		},
102		// Response contains one good RouteConfiguration, uninteresting though.
103		{
104			name:        "one-uninteresting-route-config",
105			rdsResponse: goodRDSResponse2,
106			wantErr:     false,
107			wantUpdate: map[string]xdsclient.RouteConfigUpdate{
108				goodRouteName2: {
109					VirtualHosts: []*xdsclient.VirtualHost{
110						{
111							Domains: []string{uninterestingDomain},
112							Routes:  []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}}},
113						},
114						{
115							Domains: []string{goodLDSTarget1},
116							Routes:  []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName2: {Weight: 1}}}},
117						},
118					},
119					Raw: marshaledGoodRouteConfig2,
120				},
121			},
122			wantUpdateMD: xdsclient.UpdateMetadata{
123				Status: xdsclient.ServiceStatusACKed,
124			},
125			wantUpdateErr: false,
126		},
127		// Response contains one good interesting RouteConfiguration.
128		{
129			name:        "one-good-route-config",
130			rdsResponse: goodRDSResponse1,
131			wantErr:     false,
132			wantUpdate: map[string]xdsclient.RouteConfigUpdate{
133				goodRouteName1: {
134					VirtualHosts: []*xdsclient.VirtualHost{
135						{
136							Domains: []string{uninterestingDomain},
137							Routes:  []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}}},
138						},
139						{
140							Domains: []string{goodLDSTarget1},
141							Routes:  []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName1: {Weight: 1}}}},
142						},
143					},
144					Raw: marshaledGoodRouteConfig1,
145				},
146			},
147			wantUpdateMD: xdsclient.UpdateMetadata{
148				Status: xdsclient.ServiceStatusACKed,
149			},
150			wantUpdateErr: false,
151		},
152	}
153	for _, test := range tests {
154		t.Run(test.name, func(t *testing.T) {
155			testWatchHandle(t, &watchHandleTestcase{
156				rType:            xdsclient.RouteConfigResource,
157				resourceName:     goodRouteName1,
158				responseToHandle: test.rdsResponse,
159				wantHandleErr:    test.wantErr,
160				wantUpdate:       test.wantUpdate,
161				wantUpdateMD:     test.wantUpdateMD,
162				wantUpdateErr:    test.wantUpdateErr,
163			})
164		})
165	}
166}
167
168// TestRDSHandleResponseWithoutRDSWatch tests the case where the v2Client
169// receives an RDS response without a registered RDS watcher.
170func (s) TestRDSHandleResponseWithoutRDSWatch(t *testing.T) {
171	fakeServer, cc, cleanup := startServerAndGetCC(t)
172	defer cleanup()
173
174	v2c, err := newV2Client(&testUpdateReceiver{
175		f: func(xdsclient.ResourceType, map[string]interface{}, xdsclient.UpdateMetadata) {},
176	}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
177	if err != nil {
178		t.Fatal(err)
179	}
180	defer v2c.Close()
181
182	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
183	defer cancel()
184	doLDS(ctx, t, v2c, fakeServer)
185
186	if v2c.handleRDSResponse(badResourceTypeInRDSResponse) == nil {
187		t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
188	}
189
190	if v2c.handleRDSResponse(goodRDSResponse1) != nil {
191		t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
192	}
193}
194