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