
This object will be used by a higher level xdsClient object, which will provide the watch API used by the xds resolver and balancer implementations.
271 lines
8.2 KiB
Go
271 lines
8.2 KiB
Go
/*
|
|
*
|
|
* Copyright 2019 gRPC authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
package client
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
"google.golang.org/grpc/xds/internal/client/fakexds"
|
|
)
|
|
|
|
func TestGetRouteConfigNameFromListener(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
lis *xdspb.Listener
|
|
wantRoute string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no-apiListener-field",
|
|
lis: &xdspb.Listener{},
|
|
wantRoute: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "badly-marshaled-apiListener",
|
|
lis: badAPIListener1,
|
|
wantRoute: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "wrong-type-in-apiListener",
|
|
lis: badResourceListener,
|
|
wantRoute: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty-httpConnMgr-in-apiListener",
|
|
lis: listenerWithEmptyHTTPConnMgr,
|
|
wantRoute: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "scopedRoutes-routeConfig-in-apiListener",
|
|
lis: listenerWithScopedRoutesRouteConfig,
|
|
wantRoute: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "goodListener1",
|
|
lis: goodListener1,
|
|
wantRoute: goodRouteName1,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
gotRoute, err := getRouteConfigNameFromListener(test.lis)
|
|
if gotRoute != test.wantRoute {
|
|
t.Errorf("%s: getRouteConfigNameFromListener(%+v) = %v, want %v", test.name, test.lis, gotRoute, test.wantRoute)
|
|
}
|
|
if (err != nil) != test.wantErr {
|
|
t.Errorf("%s: getRouteConfigNameFromListener(%+v) = %v, want %v", test.name, test.lis, err, test.wantErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestHandleLDSResponse starts a fake xDS server, makes a ClientConn to it,
|
|
// and creates a v2Client using it. Then, it registers a watchLDS and tests
|
|
// different LDS responses.
|
|
func TestHandleLDSResponse(t *testing.T) {
|
|
fakeServer, client, cleanup := fakexds.StartClientAndServer(t)
|
|
defer cleanup()
|
|
v2c := newV2Client(client, goodNodeProto, func(int) time.Duration { return 0 })
|
|
|
|
tests := []struct {
|
|
name string
|
|
ldsResponse *xdspb.DiscoveryResponse
|
|
wantErr bool
|
|
wantUpdate *ldsUpdate
|
|
wantUpdateErr bool
|
|
}{
|
|
// Badly marshaled LDS response.
|
|
{
|
|
name: "badly-marshaled-response",
|
|
ldsResponse: badlyMarshaledLDSResponse,
|
|
wantErr: true,
|
|
wantUpdate: nil,
|
|
wantUpdateErr: false,
|
|
},
|
|
// Response does not contain Listener proto.
|
|
{
|
|
name: "no-listener-proto-in-response",
|
|
ldsResponse: badResourceTypeInLDSResponse,
|
|
wantErr: true,
|
|
wantUpdate: nil,
|
|
wantUpdateErr: false,
|
|
},
|
|
// No APIListener in the response. Just one test case here for a bad
|
|
// ApiListener, since the others are covered in
|
|
// TestGetRouteConfigNameFromListener.
|
|
{
|
|
name: "no-apiListener-in-response",
|
|
ldsResponse: noAPIListenerLDSResponse,
|
|
wantErr: true,
|
|
wantUpdate: nil,
|
|
wantUpdateErr: false,
|
|
},
|
|
// Response contains one listener and it is good.
|
|
{
|
|
name: "one-good-listener",
|
|
ldsResponse: goodLDSResponse1,
|
|
wantErr: false,
|
|
wantUpdate: &ldsUpdate{routeName: goodRouteName1},
|
|
wantUpdateErr: false,
|
|
},
|
|
// Response contains multiple good listeners, including the one we are
|
|
// interested in.
|
|
{
|
|
name: "multiple-good-listener",
|
|
ldsResponse: ldsResponseWithMultipleResources,
|
|
wantErr: false,
|
|
wantUpdate: &ldsUpdate{routeName: goodRouteName1},
|
|
wantUpdateErr: false,
|
|
},
|
|
// Response contains two good listeners (one interesting and one
|
|
// uninteresting), and one badly marshaled listener.
|
|
{
|
|
name: "good-bad-ugly-listeners",
|
|
ldsResponse: goodBadUglyLDSResponse,
|
|
wantErr: false,
|
|
wantUpdate: &ldsUpdate{routeName: goodRouteName1},
|
|
wantUpdateErr: false,
|
|
},
|
|
// Response contains one listener, but we are not interested in it.
|
|
{
|
|
name: "one-uninteresting-listener",
|
|
ldsResponse: goodLDSResponse2,
|
|
wantErr: false,
|
|
wantUpdate: &ldsUpdate{routeName: ""},
|
|
wantUpdateErr: true,
|
|
},
|
|
// Response constains no resources. This is the case where the server
|
|
// does not know about the target we are interested in.
|
|
{
|
|
name: "empty-response",
|
|
ldsResponse: emptyLDSResponse,
|
|
wantErr: false,
|
|
wantUpdate: &ldsUpdate{routeName: ""},
|
|
wantUpdateErr: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
gotUpdateCh := make(chan ldsUpdate, 1)
|
|
gotUpdateErrCh := make(chan error, 1)
|
|
|
|
// Register a watcher, to trigger the v2Client to send an LDS request.
|
|
cancelWatch := v2c.watchLDS(goodLDSTarget1, func(u ldsUpdate, err error) {
|
|
t.Logf("%s: in v2c.watchLDS callback, ldsUpdate: %+v, err: %v", test.name, u, err)
|
|
gotUpdateCh <- u
|
|
gotUpdateErrCh <- err
|
|
})
|
|
|
|
// Wait till the request makes it to the fakeServer. This ensures that
|
|
// the watch request has been processed by the v2Client.
|
|
<-fakeServer.RequestChan
|
|
|
|
// Directly push the response through a call to handleLDSResponse,
|
|
// thereby bypassing the fakeServer.
|
|
if err := v2c.handleLDSResponse(test.ldsResponse); (err != nil) != test.wantErr {
|
|
t.Fatalf("%s: v2c.handleLDSResponse() returned err: %v, wantErr: %v", test.name, err, test.wantErr)
|
|
}
|
|
|
|
// If the test needs the callback to be invoked, verify the update and
|
|
// error pushed to the callback.
|
|
if test.wantUpdate != nil {
|
|
timer := time.NewTimer(defaultTestTimeout)
|
|
select {
|
|
case <-timer.C:
|
|
t.Fatal("Timeout when expecting LDS update")
|
|
case gotUpdate := <-gotUpdateCh:
|
|
timer.Stop()
|
|
if !reflect.DeepEqual(gotUpdate, *test.wantUpdate) {
|
|
t.Fatalf("%s: got LDS update : %+v, want %+v", test.name, gotUpdate, *test.wantUpdate)
|
|
}
|
|
}
|
|
// Since the callback that we registered pushes to both channels at
|
|
// the same time, this channel read should return immediately.
|
|
gotUpdateErr := <-gotUpdateErrCh
|
|
if (gotUpdateErr != nil) != test.wantUpdateErr {
|
|
t.Fatalf("%s: got LDS update error {%v}, wantErr: %v", test.name, gotUpdateErr, test.wantUpdateErr)
|
|
}
|
|
}
|
|
cancelWatch()
|
|
}
|
|
}
|
|
|
|
// TestHandleLDSResponseWithoutWatch tests the case where the v2Client receives
|
|
// an LDS response without a registered watcher.
|
|
func TestHandleLDSResponseWithoutWatch(t *testing.T) {
|
|
_, client, cleanup := fakexds.StartClientAndServer(t)
|
|
defer cleanup()
|
|
v2c := newV2Client(client, goodNodeProto, func(int) time.Duration { return 0 })
|
|
|
|
if v2c.handleLDSResponse(goodLDSResponse1) == nil {
|
|
t.Fatal("v2c.handleLDSResponse() succeeded, should have failed")
|
|
}
|
|
}
|
|
|
|
// TestLDSWatchExpiryTimer tests the case where the client does not receive an
|
|
// LDS response for the request that it sends out. We want the watch callback
|
|
// to be invoked with an error once the watchExpiryTimer fires.
|
|
func TestLDSWatchExpiryTimer(t *testing.T) {
|
|
oldWatchExpiryTimeout := defaultWatchExpiryTimeout
|
|
defaultWatchExpiryTimeout = 1 * time.Second
|
|
defer func() {
|
|
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
|
}()
|
|
|
|
fakeServer, client, cleanup := fakexds.StartClientAndServer(t)
|
|
defer cleanup()
|
|
v2c := newV2Client(client, goodNodeProto, func(int) time.Duration { return 0 })
|
|
|
|
// Wait till the request makes it to the fakeServer. This ensures that
|
|
// the watch request has been processed by the v2Client.
|
|
callbackCh := make(chan error, 1)
|
|
v2c.watchLDS(goodLDSTarget1, func(u ldsUpdate, err error) {
|
|
t.Logf("in v2c.watchLDS callback, ldsUpdate: %+v, err: %v", u, err)
|
|
if u.routeName != "" {
|
|
callbackCh <- fmt.Errorf("received routeName %v in ldsCallback, wanted empty string", u.routeName)
|
|
}
|
|
if err == nil {
|
|
callbackCh <- errors.New("received nil error in ldsCallback")
|
|
}
|
|
callbackCh <- nil
|
|
})
|
|
<-fakeServer.RequestChan
|
|
|
|
timer := time.NewTimer(2 * time.Second)
|
|
select {
|
|
case <-timer.C:
|
|
t.Fatalf("Timeout expired when expecting LDS update")
|
|
case err := <-callbackCh:
|
|
timer.Stop()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|