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 v2
20
21import (
22	"testing"
23	"time"
24
25	v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
26	"github.com/google/go-cmp/cmp/cmpopts"
27
28	"google.golang.org/grpc/xds/internal/xdsclient"
29)
30
31// TestLDSHandleResponse starts a fake xDS server, makes a ClientConn to it,
32// and creates a client using it. Then, it registers a watchLDS and tests
33// different LDS responses.
34func (s) TestLDSHandleResponse(t *testing.T) {
35	tests := []struct {
36		name          string
37		ldsResponse   *v2xdspb.DiscoveryResponse
38		wantErr       bool
39		wantUpdate    map[string]xdsclient.ListenerUpdateErrTuple
40		wantUpdateMD  xdsclient.UpdateMetadata
41		wantUpdateErr bool
42	}{
43		// Badly marshaled LDS response.
44		{
45			name:        "badly-marshaled-response",
46			ldsResponse: badlyMarshaledLDSResponse,
47			wantErr:     true,
48			wantUpdate:  nil,
49			wantUpdateMD: xdsclient.UpdateMetadata{
50				Status: xdsclient.ServiceStatusNACKed,
51				ErrState: &xdsclient.UpdateErrorMetadata{
52					Err: cmpopts.AnyError,
53				},
54			},
55			wantUpdateErr: false,
56		},
57		// Response does not contain Listener proto.
58		{
59			name:        "no-listener-proto-in-response",
60			ldsResponse: badResourceTypeInLDSResponse,
61			wantErr:     true,
62			wantUpdate:  nil,
63			wantUpdateMD: xdsclient.UpdateMetadata{
64				Status: xdsclient.ServiceStatusNACKed,
65				ErrState: &xdsclient.UpdateErrorMetadata{
66					Err: cmpopts.AnyError,
67				},
68			},
69			wantUpdateErr: false,
70		},
71		// No APIListener in the response. Just one test case here for a bad
72		// ApiListener, since the others are covered in
73		// TestGetRouteConfigNameFromListener.
74		{
75			name:        "no-apiListener-in-response",
76			ldsResponse: noAPIListenerLDSResponse,
77			wantErr:     true,
78			wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{
79				goodLDSTarget1: {Err: cmpopts.AnyError},
80			},
81			wantUpdateMD: xdsclient.UpdateMetadata{
82				Status: xdsclient.ServiceStatusNACKed,
83				ErrState: &xdsclient.UpdateErrorMetadata{
84					Err: cmpopts.AnyError,
85				},
86			},
87			wantUpdateErr: false,
88		},
89		// Response contains one listener and it is good.
90		{
91			name:        "one-good-listener",
92			ldsResponse: goodLDSResponse1,
93			wantErr:     false,
94			wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{
95				goodLDSTarget1: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener1}},
96			},
97			wantUpdateMD: xdsclient.UpdateMetadata{
98				Status: xdsclient.ServiceStatusACKed,
99			},
100			wantUpdateErr: false,
101		},
102		// Response contains multiple good listeners, including the one we are
103		// interested in.
104		{
105			name:        "multiple-good-listener",
106			ldsResponse: ldsResponseWithMultipleResources,
107			wantErr:     false,
108			wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{
109				goodLDSTarget1: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener1}},
110				goodLDSTarget2: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener2}},
111			},
112			wantUpdateMD: xdsclient.UpdateMetadata{
113				Status: xdsclient.ServiceStatusACKed,
114			},
115			wantUpdateErr: false,
116		},
117		// Response contains two good listeners (one interesting and one
118		// uninteresting), and one badly marshaled listener. This will cause a
119		// nack because the uninteresting listener will still be parsed.
120		{
121			name:        "good-bad-ugly-listeners",
122			ldsResponse: goodBadUglyLDSResponse,
123			wantErr:     true,
124			wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{
125				goodLDSTarget1: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener1}},
126				goodLDSTarget2: {Err: cmpopts.AnyError},
127			},
128			wantUpdateMD: xdsclient.UpdateMetadata{
129				Status: xdsclient.ServiceStatusNACKed,
130				ErrState: &xdsclient.UpdateErrorMetadata{
131					Err: cmpopts.AnyError,
132				},
133			},
134			wantUpdateErr: false,
135		},
136		// Response contains one listener, but we are not interested in it.
137		{
138			name:        "one-uninteresting-listener",
139			ldsResponse: goodLDSResponse2,
140			wantErr:     false,
141			wantUpdate: map[string]xdsclient.ListenerUpdateErrTuple{
142				goodLDSTarget2: {Update: xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1, Raw: marshaledListener2}},
143			},
144			wantUpdateMD: xdsclient.UpdateMetadata{
145				Status: xdsclient.ServiceStatusACKed,
146			},
147			wantUpdateErr: false,
148		},
149		// Response constains no resources. This is the case where the server
150		// does not know about the target we are interested in.
151		{
152			name:        "empty-response",
153			ldsResponse: emptyLDSResponse,
154			wantErr:     false,
155			wantUpdate:  nil,
156			wantUpdateMD: xdsclient.UpdateMetadata{
157				Status: xdsclient.ServiceStatusACKed,
158			},
159			wantUpdateErr: false,
160		},
161	}
162
163	for _, test := range tests {
164		t.Run(test.name, func(t *testing.T) {
165			testWatchHandle(t, &watchHandleTestcase{
166				rType:            xdsclient.ListenerResource,
167				resourceName:     goodLDSTarget1,
168				responseToHandle: test.ldsResponse,
169				wantHandleErr:    test.wantErr,
170				wantUpdate:       test.wantUpdate,
171				wantUpdateMD:     test.wantUpdateMD,
172				wantUpdateErr:    test.wantUpdateErr,
173			})
174		})
175	}
176}
177
178// TestLDSHandleResponseWithoutWatch tests the case where the client receives
179// an LDS response without a registered watcher.
180func (s) TestLDSHandleResponseWithoutWatch(t *testing.T) {
181	_, cc, cleanup := startServerAndGetCC(t)
182	defer cleanup()
183
184	v2c, err := newV2Client(&testUpdateReceiver{
185		f: func(xdsclient.ResourceType, map[string]interface{}, xdsclient.UpdateMetadata) {},
186	}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
187	if err != nil {
188		t.Fatal(err)
189	}
190	defer v2c.Close()
191
192	if v2c.handleLDSResponse(badResourceTypeInLDSResponse) == nil {
193		t.Fatal("v2c.handleLDSResponse() succeeded, should have failed")
194	}
195
196	if v2c.handleLDSResponse(goodLDSResponse1) != nil {
197		t.Fatal("v2c.handleLDSResponse() succeeded, should have failed")
198	}
199}
200