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/golang/protobuf/ptypes"
27	anypb "github.com/golang/protobuf/ptypes/any"
28	"google.golang.org/grpc/xds/internal"
29	xdsclient "google.golang.org/grpc/xds/internal/client"
30	"google.golang.org/grpc/xds/internal/testutils"
31	"google.golang.org/grpc/xds/internal/version"
32)
33
34var (
35	badlyMarshaledEDSResponse = &v2xdspb.DiscoveryResponse{
36		Resources: []*anypb.Any{
37			{
38				TypeUrl: version.V2EndpointsURL,
39				Value:   []byte{1, 2, 3, 4},
40			},
41		},
42		TypeUrl: version.V2EndpointsURL,
43	}
44	badResourceTypeInEDSResponse = &v2xdspb.DiscoveryResponse{
45		Resources: []*anypb.Any{
46			{
47				TypeUrl: httpConnManagerURL,
48				Value:   marshaledConnMgr1,
49			},
50		},
51		TypeUrl: version.V2EndpointsURL,
52	}
53	marshaledGoodCLA1 = func() *anypb.Any {
54		clab0 := testutils.NewClusterLoadAssignmentBuilder(goodEDSName, nil)
55		clab0.AddLocality("locality-1", 1, 1, []string{"addr1:314"}, nil)
56		clab0.AddLocality("locality-2", 1, 0, []string{"addr2:159"}, nil)
57		a, _ := ptypes.MarshalAny(clab0.Build())
58		return a
59	}()
60	goodEDSResponse1 = &v2xdspb.DiscoveryResponse{
61		Resources: []*anypb.Any{
62			marshaledGoodCLA1,
63		},
64		TypeUrl: version.V2EndpointsURL,
65	}
66	marshaledGoodCLA2 = func() *anypb.Any {
67		clab0 := testutils.NewClusterLoadAssignmentBuilder("not-goodEDSName", nil)
68		clab0.AddLocality("locality-1", 1, 0, []string{"addr1:314"}, nil)
69		a, _ := ptypes.MarshalAny(clab0.Build())
70		return a
71	}()
72	goodEDSResponse2 = &v2xdspb.DiscoveryResponse{
73		Resources: []*anypb.Any{
74			marshaledGoodCLA2,
75		},
76		TypeUrl: version.V2EndpointsURL,
77	}
78)
79
80func (s) TestEDSHandleResponse(t *testing.T) {
81	tests := []struct {
82		name          string
83		edsResponse   *v2xdspb.DiscoveryResponse
84		wantErr       bool
85		wantUpdate    map[string]xdsclient.EndpointsUpdate
86		wantUpdateMD  xdsclient.UpdateMetadata
87		wantUpdateErr bool
88	}{
89		// Any in resource is badly marshaled.
90		{
91			name:        "badly-marshaled_response",
92			edsResponse: badlyMarshaledEDSResponse,
93			wantErr:     true,
94			wantUpdate:  nil,
95			wantUpdateMD: xdsclient.UpdateMetadata{
96				Status: xdsclient.ServiceStatusNACKed,
97				ErrState: &xdsclient.UpdateErrorMetadata{
98					Err: errPlaceHolder,
99				},
100			},
101			wantUpdateErr: false,
102		},
103		// Response doesn't contain resource with the right type.
104		{
105			name:        "no-config-in-response",
106			edsResponse: badResourceTypeInEDSResponse,
107			wantErr:     true,
108			wantUpdate:  nil,
109			wantUpdateMD: xdsclient.UpdateMetadata{
110				Status: xdsclient.ServiceStatusNACKed,
111				ErrState: &xdsclient.UpdateErrorMetadata{
112					Err: errPlaceHolder,
113				},
114			},
115			wantUpdateErr: false,
116		},
117		// Response contains one uninteresting ClusterLoadAssignment.
118		{
119			name:        "one-uninterestring-assignment",
120			edsResponse: goodEDSResponse2,
121			wantErr:     false,
122			wantUpdate: map[string]xdsclient.EndpointsUpdate{
123				"not-goodEDSName": {
124					Localities: []xdsclient.Locality{
125						{
126							Endpoints: []xdsclient.Endpoint{{Address: "addr1:314"}},
127							ID:        internal.LocalityID{SubZone: "locality-1"},
128							Priority:  0,
129							Weight:    1,
130						},
131					},
132					Raw: marshaledGoodCLA2,
133				},
134			},
135			wantUpdateMD: xdsclient.UpdateMetadata{
136				Status: xdsclient.ServiceStatusACKed,
137			},
138			wantUpdateErr: false,
139		},
140		// Response contains one good ClusterLoadAssignment.
141		{
142			name:        "one-good-assignment",
143			edsResponse: goodEDSResponse1,
144			wantErr:     false,
145			wantUpdate: map[string]xdsclient.EndpointsUpdate{
146				goodEDSName: {
147					Localities: []xdsclient.Locality{
148						{
149							Endpoints: []xdsclient.Endpoint{{Address: "addr1:314"}},
150							ID:        internal.LocalityID{SubZone: "locality-1"},
151							Priority:  1,
152							Weight:    1,
153						},
154						{
155							Endpoints: []xdsclient.Endpoint{{Address: "addr2:159"}},
156							ID:        internal.LocalityID{SubZone: "locality-2"},
157							Priority:  0,
158							Weight:    1,
159						},
160					},
161					Raw: marshaledGoodCLA1,
162				},
163			},
164			wantUpdateMD: xdsclient.UpdateMetadata{
165				Status: xdsclient.ServiceStatusACKed,
166			},
167			wantUpdateErr: false,
168		},
169	}
170	for _, test := range tests {
171		t.Run(test.name, func(t *testing.T) {
172			testWatchHandle(t, &watchHandleTestcase{
173				rType:            xdsclient.EndpointsResource,
174				resourceName:     goodEDSName,
175				responseToHandle: test.edsResponse,
176				wantHandleErr:    test.wantErr,
177				wantUpdate:       test.wantUpdate,
178				wantUpdateMD:     test.wantUpdateMD,
179				wantUpdateErr:    test.wantUpdateErr,
180			})
181		})
182	}
183}
184
185// TestEDSHandleResponseWithoutWatch tests the case where the v2Client
186// receives an EDS response without a registered EDS watcher.
187func (s) TestEDSHandleResponseWithoutWatch(t *testing.T) {
188	_, cc, cleanup := startServerAndGetCC(t)
189	defer cleanup()
190
191	v2c, err := newV2Client(&testUpdateReceiver{
192		f: func(xdsclient.ResourceType, map[string]interface{}, xdsclient.UpdateMetadata) {},
193	}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
194	if err != nil {
195		t.Fatal(err)
196	}
197	defer v2c.Close()
198
199	if v2c.handleEDSResponse(badResourceTypeInEDSResponse) == nil {
200		t.Fatal("v2c.handleEDSResponse() succeeded, should have failed")
201	}
202
203	if v2c.handleEDSResponse(goodEDSResponse1) != nil {
204		t.Fatal("v2c.handleEDSResponse() succeeded, should have failed")
205	}
206}
207