mirror of https://github.com/grpc/grpc-go.git
xds: Client refactor in preparation for xDS v3 support (#3743)
This commit is contained in:
parent
d6c4e49aab
commit
97c30a1419
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"google.golang.org/grpc/balancer"
|
"google.golang.org/grpc/balancer"
|
||||||
"google.golang.org/grpc/connectivity"
|
"google.golang.org/grpc/connectivity"
|
||||||
xdsclient "google.golang.org/grpc/xds/internal/client"
|
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -39,10 +38,10 @@ func (s) TestEDSPriority_HighPriorityReady(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with priorities [0, 1], each with one backend.
|
// Two localities, with priorities [0, 1], each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs1 := <-cc.NewSubConnAddrsCh
|
addrs1 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs1[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs1[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -62,11 +61,11 @@ func (s) TestEDSPriority_HighPriorityReady(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add p2, it shouldn't cause any udpates.
|
// Add p2, it shouldn't cause any udpates.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
clab2.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
clab2.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-cc.NewPickerCh:
|
case <-cc.NewPickerCh:
|
||||||
|
|
@ -79,10 +78,10 @@ func (s) TestEDSPriority_HighPriorityReady(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove p2, no updates.
|
// Remove p2, no updates.
|
||||||
clab3 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab3 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab3.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab3.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab3.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab3.Build()))
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-cc.NewPickerCh:
|
case <-cc.NewPickerCh:
|
||||||
|
|
@ -105,10 +104,10 @@ func (s) TestEDSPriority_SwitchPriority(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with priorities [0, 1], each with one backend.
|
// Two localities, with priorities [0, 1], each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs0 := <-cc.NewSubConnAddrsCh
|
addrs0 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -147,11 +146,11 @@ func (s) TestEDSPriority_SwitchPriority(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add p2, it shouldn't cause any udpates.
|
// Add p2, it shouldn't cause any udpates.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
clab2.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
clab2.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-cc.NewPickerCh:
|
case <-cc.NewPickerCh:
|
||||||
|
|
@ -183,10 +182,10 @@ func (s) TestEDSPriority_SwitchPriority(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove 2, use 1.
|
// Remove 2, use 1.
|
||||||
clab3 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab3 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab3.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab3.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab3.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab3.Build()))
|
||||||
|
|
||||||
// p2 SubConns are removed.
|
// p2 SubConns are removed.
|
||||||
scToRemove := <-cc.RemoveSubConnCh
|
scToRemove := <-cc.RemoveSubConnCh
|
||||||
|
|
@ -212,10 +211,10 @@ func (s) TestEDSPriority_HigherDownWhileAddingLower(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with different priorities, each with one backend.
|
// Two localities, with different priorities, each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs0 := <-cc.NewSubConnAddrsCh
|
addrs0 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -242,11 +241,11 @@ func (s) TestEDSPriority_HigherDownWhileAddingLower(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add p2, it should create a new SubConn.
|
// Add p2, it should create a new SubConn.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
clab2.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
clab2.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
addrs2 := <-cc.NewSubConnAddrsCh
|
addrs2 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs2[0].Addr, testEndpointAddrs[2]; got != want {
|
if got, want := addrs2[0].Addr, testEndpointAddrs[2]; got != want {
|
||||||
|
|
@ -277,11 +276,11 @@ func (s) TestEDSPriority_HigherReadyCloseAllLower(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with priorities [0,1,2], each with one backend.
|
// Two localities, with priorities [0,1,2], each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
clab1.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
clab1.AddLocality(testSubZones[2], 1, 2, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs0 := <-cc.NewSubConnAddrsCh
|
addrs0 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -359,10 +358,10 @@ func (s) TestEDSPriority_InitTimeout(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with different priorities, each with one backend.
|
// Two localities, with different priorities, each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs0 := <-cc.NewSubConnAddrsCh
|
addrs0 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -409,10 +408,10 @@ func (s) TestEDSPriority_MultipleLocalities(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with different priorities, each with one backend.
|
// Two localities, with different priorities, each with one backend.
|
||||||
clab0 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab0 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab0.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab0.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab0.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab0.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab0.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab0.Build()))
|
||||||
|
|
||||||
addrs0 := <-cc.NewSubConnAddrsCh
|
addrs0 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -463,12 +462,12 @@ func (s) TestEDSPriority_MultipleLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add two localities, with two priorities, with one backend.
|
// Add two localities, with two priorities, with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
clab1.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:3], nil)
|
clab1.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
clab1.AddLocality(testSubZones[3], 1, 1, testEndpointAddrs[3:4], nil)
|
clab1.AddLocality(testSubZones[3], 1, 1, testEndpointAddrs[3:4], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs2 := <-cc.NewSubConnAddrsCh
|
addrs2 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs2[0].Addr, testEndpointAddrs[2]; got != want {
|
if got, want := addrs2[0].Addr, testEndpointAddrs[2]; got != want {
|
||||||
|
|
@ -520,10 +519,10 @@ func (s) TestEDSPriority_RemovesAllLocalities(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with different priorities, each with one backend.
|
// Two localities, with different priorities, each with one backend.
|
||||||
clab0 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab0 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab0.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab0.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab0.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab0.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab0.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab0.Build()))
|
||||||
|
|
||||||
addrs0 := <-cc.NewSubConnAddrsCh
|
addrs0 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs0[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -541,8 +540,8 @@ func (s) TestEDSPriority_RemovesAllLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all priorities.
|
// Remove all priorities.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
// p0 subconn should be removed.
|
// p0 subconn should be removed.
|
||||||
scToRemove := <-cc.RemoveSubConnCh
|
scToRemove := <-cc.RemoveSubConnCh
|
||||||
|
|
@ -559,10 +558,10 @@ func (s) TestEDSPriority_RemovesAllLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-add two localities, with previous priorities, but different backends.
|
// Re-add two localities, with previous priorities, but different backends.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[3:4], nil)
|
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[3:4], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
addrs01 := <-cc.NewSubConnAddrsCh
|
addrs01 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs01[0].Addr, testEndpointAddrs[2]; got != want {
|
if got, want := addrs01[0].Addr, testEndpointAddrs[2]; got != want {
|
||||||
|
|
@ -591,9 +590,9 @@ func (s) TestEDSPriority_RemovesAllLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove p1 from EDS, to fallback to p0.
|
// Remove p1 from EDS, to fallback to p0.
|
||||||
clab3 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab3 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab3.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab3.Build()))
|
||||||
|
|
||||||
// p1 subconn should be removed.
|
// p1 subconn should be removed.
|
||||||
scToRemove1 := <-cc.RemoveSubConnCh
|
scToRemove1 := <-cc.RemoveSubConnCh
|
||||||
|
|
@ -664,10 +663,10 @@ func (s) TestEDSPriority_HighPriorityNoEndpoints(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with priorities [0, 1], each with one backend.
|
// Two localities, with priorities [0, 1], each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs1 := <-cc.NewSubConnAddrsCh
|
addrs1 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs1[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs1[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -687,10 +686,10 @@ func (s) TestEDSPriority_HighPriorityNoEndpoints(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove addresses from priority 0, should use p1.
|
// Remove addresses from priority 0, should use p1.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, nil, nil)
|
clab2.AddLocality(testSubZones[0], 1, 0, nil, nil)
|
||||||
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
// p0 will remove the subconn, and ClientConn will send a sc update to
|
// p0 will remove the subconn, and ClientConn will send a sc update to
|
||||||
// shutdown.
|
// shutdown.
|
||||||
|
|
@ -723,10 +722,10 @@ func (s) TestEDSPriority_HighPriorityAllUnhealthy(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, with priorities [0, 1], each with one backend.
|
// Two localities, with priorities [0, 1], each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
addrs1 := <-cc.NewSubConnAddrsCh
|
addrs1 := <-cc.NewSubConnAddrsCh
|
||||||
if got, want := addrs1[0].Addr, testEndpointAddrs[0]; got != want {
|
if got, want := addrs1[0].Addr, testEndpointAddrs[0]; got != want {
|
||||||
|
|
@ -746,12 +745,12 @@ func (s) TestEDSPriority_HighPriorityAllUnhealthy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set priority 0 endpoints to all unhealthy, should use p1.
|
// Set priority 0 endpoints to all unhealthy, should use p1.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], &xdsclient.AddLocalityOptions{
|
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], &testutils.AddLocalityOptions{
|
||||||
Health: []corepb.HealthStatus{corepb.HealthStatus_UNHEALTHY},
|
Health: []corepb.HealthStatus{corepb.HealthStatus_UNHEALTHY},
|
||||||
})
|
})
|
||||||
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
clab2.AddLocality(testSubZones[1], 1, 1, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
// p0 will remove the subconn, and ClientConn will send a sc update to
|
// p0 will remove the subconn, and ClientConn will send a sc update to
|
||||||
// transient failure.
|
// transient failure.
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,9 @@ func (s) TestEDS_OneLocality(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// One locality with one backend.
|
// One locality with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
sc1 := <-cc.NewSubConnCh
|
sc1 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
||||||
|
|
@ -78,9 +78,9 @@ func (s) TestEDS_OneLocality(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same locality, add one more backend.
|
// The same locality, add one more backend.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:2], nil)
|
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
sc2 := <-cc.NewSubConnCh
|
sc2 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
||||||
|
|
@ -94,9 +94,9 @@ func (s) TestEDS_OneLocality(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same locality, delete first backend.
|
// The same locality, delete first backend.
|
||||||
clab3 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab3 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[1:2], nil)
|
clab3.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab3.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab3.Build()))
|
||||||
|
|
||||||
scToRemove := <-cc.RemoveSubConnCh
|
scToRemove := <-cc.RemoveSubConnCh
|
||||||
if !cmp.Equal(scToRemove, sc1, cmp.AllowUnexported(testutils.TestSubConn{})) {
|
if !cmp.Equal(scToRemove, sc1, cmp.AllowUnexported(testutils.TestSubConn{})) {
|
||||||
|
|
@ -114,9 +114,9 @@ func (s) TestEDS_OneLocality(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same locality, replace backend.
|
// The same locality, replace backend.
|
||||||
clab4 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab4 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab4.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
clab4.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab4.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab4.Build()))
|
||||||
|
|
||||||
sc3 := <-cc.NewSubConnCh
|
sc3 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc3, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc3, connectivity.Connecting)
|
||||||
|
|
@ -137,9 +137,9 @@ func (s) TestEDS_OneLocality(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same locality, different drop rate, dropping 50%.
|
// The same locality, different drop rate, dropping 50%.
|
||||||
clab5 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], []uint32{50})
|
clab5 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], []uint32{50})
|
||||||
clab5.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
clab5.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab5.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab5.Build()))
|
||||||
|
|
||||||
// Picks with drops.
|
// Picks with drops.
|
||||||
p5 := <-cc.NewPickerCh
|
p5 := <-cc.NewPickerCh
|
||||||
|
|
@ -155,9 +155,9 @@ func (s) TestEDS_OneLocality(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same locality, remove drops.
|
// The same locality, remove drops.
|
||||||
clab6 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab6 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab6.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
clab6.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab6.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab6.Build()))
|
||||||
|
|
||||||
// Pick without drops.
|
// Pick without drops.
|
||||||
p6 := <-cc.NewPickerCh
|
p6 := <-cc.NewPickerCh
|
||||||
|
|
@ -181,9 +181,9 @@ func (s) TestEDS_TwoLocalities(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, each with one backend.
|
// Two localities, each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
sc1 := <-cc.NewSubConnCh
|
sc1 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
||||||
edsb.handleSubConnStateChange(sc1, connectivity.Ready)
|
edsb.handleSubConnStateChange(sc1, connectivity.Ready)
|
||||||
|
|
@ -192,7 +192,7 @@ func (s) TestEDS_TwoLocalities(t *testing.T) {
|
||||||
// locality. Otherwise the test is flaky because of a map is used in EDS to
|
// locality. Otherwise the test is flaky because of a map is used in EDS to
|
||||||
// keep localities.
|
// keep localities.
|
||||||
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
sc2 := <-cc.NewSubConnCh
|
sc2 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
||||||
edsb.handleSubConnStateChange(sc2, connectivity.Ready)
|
edsb.handleSubConnStateChange(sc2, connectivity.Ready)
|
||||||
|
|
@ -205,11 +205,11 @@ func (s) TestEDS_TwoLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add another locality, with one backend.
|
// Add another locality, with one backend.
|
||||||
clab2 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab2 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab2.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab2.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
clab2.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
||||||
clab2.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:3], nil)
|
clab2.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab2.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab2.Build()))
|
||||||
|
|
||||||
sc3 := <-cc.NewSubConnCh
|
sc3 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc3, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc3, connectivity.Connecting)
|
||||||
|
|
@ -223,10 +223,10 @@ func (s) TestEDS_TwoLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove first locality.
|
// Remove first locality.
|
||||||
clab3 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab3 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab3.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
clab3.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
||||||
clab3.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:3], nil)
|
clab3.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:3], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab3.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab3.Build()))
|
||||||
|
|
||||||
scToRemove := <-cc.RemoveSubConnCh
|
scToRemove := <-cc.RemoveSubConnCh
|
||||||
if !cmp.Equal(scToRemove, sc1, cmp.AllowUnexported(testutils.TestSubConn{})) {
|
if !cmp.Equal(scToRemove, sc1, cmp.AllowUnexported(testutils.TestSubConn{})) {
|
||||||
|
|
@ -242,10 +242,10 @@ func (s) TestEDS_TwoLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a backend to the last locality.
|
// Add a backend to the last locality.
|
||||||
clab4 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab4 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab4.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
clab4.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
||||||
clab4.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:4], nil)
|
clab4.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:4], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab4.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab4.Build()))
|
||||||
|
|
||||||
sc4 := <-cc.NewSubConnCh
|
sc4 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc4, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc4, connectivity.Connecting)
|
||||||
|
|
@ -262,10 +262,10 @@ func (s) TestEDS_TwoLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change weight of the locality[1].
|
// Change weight of the locality[1].
|
||||||
clab5 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab5 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab5.AddLocality(testSubZones[1], 2, 0, testEndpointAddrs[1:2], nil)
|
clab5.AddLocality(testSubZones[1], 2, 0, testEndpointAddrs[1:2], nil)
|
||||||
clab5.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:4], nil)
|
clab5.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:4], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab5.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab5.Build()))
|
||||||
|
|
||||||
// Test pick with two subconns different locality weight.
|
// Test pick with two subconns different locality weight.
|
||||||
p5 := <-cc.NewPickerCh
|
p5 := <-cc.NewPickerCh
|
||||||
|
|
@ -278,10 +278,10 @@ func (s) TestEDS_TwoLocalities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change weight of the locality[1] to 0, it should never be picked.
|
// Change weight of the locality[1] to 0, it should never be picked.
|
||||||
clab6 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab6 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab6.AddLocality(testSubZones[1], 0, 0, testEndpointAddrs[1:2], nil)
|
clab6.AddLocality(testSubZones[1], 0, 0, testEndpointAddrs[1:2], nil)
|
||||||
clab6.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:4], nil)
|
clab6.AddLocality(testSubZones[2], 1, 0, testEndpointAddrs[2:4], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab6.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab6.Build()))
|
||||||
|
|
||||||
// Changing weight of locality[1] to 0 caused it to be removed. It's subconn
|
// Changing weight of locality[1] to 0 caused it to be removed. It's subconn
|
||||||
// should also be removed.
|
// should also be removed.
|
||||||
|
|
@ -312,8 +312,8 @@ func (s) TestEDS_EndpointsHealth(t *testing.T) {
|
||||||
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
edsb.enqueueChildBalancerStateUpdate = edsb.updateState
|
||||||
|
|
||||||
// Two localities, each 3 backend, one Healthy, one Unhealthy, one Unknown.
|
// Two localities, each 3 backend, one Healthy, one Unhealthy, one Unknown.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:6], &xdsclient.AddLocalityOptions{
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:6], &testutils.AddLocalityOptions{
|
||||||
Health: []corepb.HealthStatus{
|
Health: []corepb.HealthStatus{
|
||||||
corepb.HealthStatus_HEALTHY,
|
corepb.HealthStatus_HEALTHY,
|
||||||
corepb.HealthStatus_UNHEALTHY,
|
corepb.HealthStatus_UNHEALTHY,
|
||||||
|
|
@ -323,7 +323,7 @@ func (s) TestEDS_EndpointsHealth(t *testing.T) {
|
||||||
corepb.HealthStatus_DEGRADED,
|
corepb.HealthStatus_DEGRADED,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[6:12], &xdsclient.AddLocalityOptions{
|
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[6:12], &testutils.AddLocalityOptions{
|
||||||
Health: []corepb.HealthStatus{
|
Health: []corepb.HealthStatus{
|
||||||
corepb.HealthStatus_HEALTHY,
|
corepb.HealthStatus_HEALTHY,
|
||||||
corepb.HealthStatus_UNHEALTHY,
|
corepb.HealthStatus_UNHEALTHY,
|
||||||
|
|
@ -333,7 +333,7 @@ func (s) TestEDS_EndpointsHealth(t *testing.T) {
|
||||||
corepb.HealthStatus_DEGRADED,
|
corepb.HealthStatus_DEGRADED,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
readySCs []balancer.SubConn
|
readySCs []balancer.SubConn
|
||||||
|
|
@ -406,9 +406,9 @@ func (s) TestEDS_EmptyUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// One locality with one backend.
|
// One locality with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
sc1 := <-cc.NewSubConnCh
|
sc1 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
||||||
|
|
@ -434,7 +434,7 @@ func (s) TestEDS_EmptyUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle another update with priorities and localities.
|
// Handle another update with priorities and localities.
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
sc2 := <-cc.NewSubConnCh
|
sc2 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
||||||
|
|
@ -462,10 +462,10 @@ func (s) TestEDS_UpdateSubBalancerName(t *testing.T) {
|
||||||
edsb.handleChildPolicy("test-const-balancer", nil)
|
edsb.handleChildPolicy("test-const-balancer", nil)
|
||||||
|
|
||||||
// Two localities, each with one backend.
|
// Two localities, each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
sc := <-cc.NewSubConnCh
|
sc := <-cc.NewSubConnCh
|
||||||
|
|
@ -601,9 +601,9 @@ func (s) TestEDS_ChildPolicyUpdatePickerInline(t *testing.T) {
|
||||||
|
|
||||||
edsb.handleChildPolicy("test-inline-update-balancer", nil)
|
edsb.handleChildPolicy("test-inline-update-balancer", nil)
|
||||||
|
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
|
|
||||||
p0 := <-cc.NewPickerCh
|
p0 := <-cc.NewPickerCh
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
|
|
@ -689,9 +689,9 @@ func (s) TestEDS_LoadReport(t *testing.T) {
|
||||||
backendToBalancerID := make(map[balancer.SubConn]internal.LocalityID)
|
backendToBalancerID := make(map[balancer.SubConn]internal.LocalityID)
|
||||||
|
|
||||||
// Two localities, each with one backend.
|
// Two localities, each with one backend.
|
||||||
clab1 := xdsclient.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
clab1 := testutils.NewClusterLoadAssignmentBuilder(testClusterNames[0], nil)
|
||||||
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
clab1.AddLocality(testSubZones[0], 1, 0, testEndpointAddrs[:1], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
sc1 := <-cc.NewSubConnCh
|
sc1 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc1, connectivity.Connecting)
|
||||||
edsb.handleSubConnStateChange(sc1, connectivity.Ready)
|
edsb.handleSubConnStateChange(sc1, connectivity.Ready)
|
||||||
|
|
@ -703,7 +703,7 @@ func (s) TestEDS_LoadReport(t *testing.T) {
|
||||||
// locality. Otherwise the test is flaky because of a map is used in EDS to
|
// locality. Otherwise the test is flaky because of a map is used in EDS to
|
||||||
// keep localities.
|
// keep localities.
|
||||||
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
clab1.AddLocality(testSubZones[1], 1, 0, testEndpointAddrs[1:2], nil)
|
||||||
edsb.handleEDSResponse(xdsclient.ParseEDSRespProtoForTesting(clab1.Build()))
|
edsb.handleEDSResponse(parseEDSRespProtoForTesting(clab1.Build()))
|
||||||
sc2 := <-cc.NewSubConnCh
|
sc2 := <-cc.NewSubConnCh
|
||||||
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
edsb.handleSubConnStateChange(sc2, connectivity.Connecting)
|
||||||
edsb.handleSubConnStateChange(sc2, connectivity.Ready)
|
edsb.handleSubConnStateChange(sc2, connectivity.Ready)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ import (
|
||||||
"google.golang.org/grpc/xds/internal/client/bootstrap"
|
"google.golang.org/grpc/xds/internal/client/bootstrap"
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeclient"
|
"google.golang.org/grpc/xds/internal/testutils/fakeclient"
|
||||||
|
|
||||||
|
_ "google.golang.org/grpc/xds/internal/client/v2" // V2 client registration.
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
*
|
* Copyright 2020 gRPC authors.
|
||||||
* Copyright 2019 gRPC authors.
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -15,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package edsbalancer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -26,15 +25,61 @@ import (
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
endpointpb "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
|
endpointpb "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
|
||||||
typepb "github.com/envoyproxy/go-control-plane/envoy/type"
|
typepb "github.com/envoyproxy/go-control-plane/envoy/type"
|
||||||
"github.com/golang/protobuf/ptypes"
|
|
||||||
"google.golang.org/grpc/xds/internal"
|
"google.golang.org/grpc/xds/internal"
|
||||||
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// parseEDSRespProtoForTesting parses EDS response, and panic if parsing fails.
|
||||||
|
//
|
||||||
|
// TODO: delete this. The EDS balancer tests should build an EndpointsUpdate
|
||||||
|
// directly, instead of building and parsing a proto message.
|
||||||
|
func parseEDSRespProtoForTesting(m *xdspb.ClusterLoadAssignment) xdsclient.EndpointsUpdate {
|
||||||
|
u, err := parseEDSRespProto(m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEDSRespProto turns EDS response proto message to EndpointsUpdate.
|
||||||
|
func parseEDSRespProto(m *xdspb.ClusterLoadAssignment) (xdsclient.EndpointsUpdate, error) {
|
||||||
|
ret := xdsclient.EndpointsUpdate{}
|
||||||
|
for _, dropPolicy := range m.GetPolicy().GetDropOverloads() {
|
||||||
|
ret.Drops = append(ret.Drops, parseDropPolicy(dropPolicy))
|
||||||
|
}
|
||||||
|
priorities := make(map[uint32]struct{})
|
||||||
|
for _, locality := range m.Endpoints {
|
||||||
|
l := locality.GetLocality()
|
||||||
|
if l == nil {
|
||||||
|
return xdsclient.EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality)
|
||||||
|
}
|
||||||
|
lid := internal.LocalityID{
|
||||||
|
Region: l.Region,
|
||||||
|
Zone: l.Zone,
|
||||||
|
SubZone: l.SubZone,
|
||||||
|
}
|
||||||
|
priority := locality.GetPriority()
|
||||||
|
priorities[priority] = struct{}{}
|
||||||
|
ret.Localities = append(ret.Localities, xdsclient.Locality{
|
||||||
|
ID: lid,
|
||||||
|
Endpoints: parseEndpoints(locality.GetLbEndpoints()),
|
||||||
|
Weight: locality.GetLoadBalancingWeight().GetValue(),
|
||||||
|
Priority: priority,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := 0; i < len(priorities); i++ {
|
||||||
|
if _, ok := priorities[uint32(i)]; !ok {
|
||||||
|
return xdsclient.EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseAddress(socketAddress *corepb.SocketAddress) string {
|
func parseAddress(socketAddress *corepb.SocketAddress) string {
|
||||||
return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue())))
|
return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue())))
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDropPolicy(dropPolicy *xdspb.ClusterLoadAssignment_Policy_DropOverload) OverloadDropConfig {
|
func parseDropPolicy(dropPolicy *xdspb.ClusterLoadAssignment_Policy_DropOverload) xdsclient.OverloadDropConfig {
|
||||||
percentage := dropPolicy.GetDropPercentage()
|
percentage := dropPolicy.GetDropPercentage()
|
||||||
var (
|
var (
|
||||||
numerator = percentage.GetNumerator()
|
numerator = percentage.GetNumerator()
|
||||||
|
|
@ -48,96 +93,21 @@ func parseDropPolicy(dropPolicy *xdspb.ClusterLoadAssignment_Policy_DropOverload
|
||||||
case typepb.FractionalPercent_MILLION:
|
case typepb.FractionalPercent_MILLION:
|
||||||
denominator = 1000000
|
denominator = 1000000
|
||||||
}
|
}
|
||||||
return OverloadDropConfig{
|
return xdsclient.OverloadDropConfig{
|
||||||
Category: dropPolicy.GetCategory(),
|
Category: dropPolicy.GetCategory(),
|
||||||
Numerator: numerator,
|
Numerator: numerator,
|
||||||
Denominator: denominator,
|
Denominator: denominator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseEndpoints(lbEndpoints []*endpointpb.LbEndpoint) []Endpoint {
|
func parseEndpoints(lbEndpoints []*endpointpb.LbEndpoint) []xdsclient.Endpoint {
|
||||||
endpoints := make([]Endpoint, 0, len(lbEndpoints))
|
endpoints := make([]xdsclient.Endpoint, 0, len(lbEndpoints))
|
||||||
for _, lbEndpoint := range lbEndpoints {
|
for _, lbEndpoint := range lbEndpoints {
|
||||||
endpoints = append(endpoints, Endpoint{
|
endpoints = append(endpoints, xdsclient.Endpoint{
|
||||||
HealthStatus: EndpointHealthStatus(lbEndpoint.GetHealthStatus()),
|
HealthStatus: xdsclient.EndpointHealthStatus(lbEndpoint.GetHealthStatus()),
|
||||||
Address: parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress()),
|
Address: parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress()),
|
||||||
Weight: lbEndpoint.GetLoadBalancingWeight().GetValue(),
|
Weight: lbEndpoint.GetLoadBalancingWeight().GetValue(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return endpoints
|
return endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEDSRespProto turns EDS response proto message to EndpointsUpdate.
|
|
||||||
//
|
|
||||||
// This is temporarily exported to be used in eds balancer, before it switches
|
|
||||||
// to use xds client. TODO: unexport.
|
|
||||||
func ParseEDSRespProto(m *xdspb.ClusterLoadAssignment) (EndpointsUpdate, error) {
|
|
||||||
ret := EndpointsUpdate{}
|
|
||||||
for _, dropPolicy := range m.GetPolicy().GetDropOverloads() {
|
|
||||||
ret.Drops = append(ret.Drops, parseDropPolicy(dropPolicy))
|
|
||||||
}
|
|
||||||
priorities := make(map[uint32]struct{})
|
|
||||||
for _, locality := range m.Endpoints {
|
|
||||||
l := locality.GetLocality()
|
|
||||||
if l == nil {
|
|
||||||
return EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality)
|
|
||||||
}
|
|
||||||
lid := internal.LocalityID{
|
|
||||||
Region: l.Region,
|
|
||||||
Zone: l.Zone,
|
|
||||||
SubZone: l.SubZone,
|
|
||||||
}
|
|
||||||
priority := locality.GetPriority()
|
|
||||||
priorities[priority] = struct{}{}
|
|
||||||
ret.Localities = append(ret.Localities, Locality{
|
|
||||||
ID: lid,
|
|
||||||
Endpoints: parseEndpoints(locality.GetLbEndpoints()),
|
|
||||||
Weight: locality.GetLoadBalancingWeight().GetValue(),
|
|
||||||
Priority: priority,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for i := 0; i < len(priorities); i++ {
|
|
||||||
if _, ok := priorities[uint32(i)]; !ok {
|
|
||||||
return EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseEDSRespProtoForTesting parses EDS response, and panic if parsing fails.
|
|
||||||
// This is used by EDS balancer tests.
|
|
||||||
//
|
|
||||||
// TODO: delete this. The EDS balancer tests should build an EndpointsUpdate directly,
|
|
||||||
// instead of building and parsing a proto message.
|
|
||||||
func ParseEDSRespProtoForTesting(m *xdspb.ClusterLoadAssignment) EndpointsUpdate {
|
|
||||||
u, err := ParseEDSRespProto(m)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v2c *v2Client) handleEDSResponse(resp *xdspb.DiscoveryResponse) error {
|
|
||||||
returnUpdate := make(map[string]EndpointsUpdate)
|
|
||||||
for _, r := range resp.GetResources() {
|
|
||||||
var resource ptypes.DynamicAny
|
|
||||||
if err := ptypes.UnmarshalAny(r, &resource); err != nil {
|
|
||||||
return fmt.Errorf("xds: failed to unmarshal resource in EDS response: %v", err)
|
|
||||||
}
|
|
||||||
cla, ok := resource.Message.(*xdspb.ClusterLoadAssignment)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("xds: unexpected resource type: %T in EDS response", resource.Message)
|
|
||||||
}
|
|
||||||
v2c.logger.Infof("Resource with name: %v, type: %T, contains: %v", cla.GetClusterName(), cla, cla)
|
|
||||||
|
|
||||||
u, err := ParseEDSRespProto(cla)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
returnUpdate[cla.GetClusterName()] = u
|
|
||||||
}
|
|
||||||
|
|
||||||
v2c.parent.newEDSUpdate(returnUpdate)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -36,10 +36,7 @@ import (
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeclient"
|
"google.golang.org/grpc/xds/internal/testutils/fakeclient"
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
||||||
)
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
|
||||||
const (
|
|
||||||
edsType = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -135,7 +132,7 @@ func (s) TestClientWrapperWatchEDS(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wantReq := &xdspb.DiscoveryRequest{
|
wantReq := &xdspb.DiscoveryRequest{
|
||||||
TypeUrl: edsType,
|
TypeUrl: version.V2EndpointsURL,
|
||||||
ResourceNames: []string{test.wantResourceName},
|
ResourceNames: []string{test.wantResourceName},
|
||||||
Node: testutils.EmptyNodeProtoV2,
|
Node: testutils.EmptyNodeProtoV2,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,204 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/internal/backoff"
|
"google.golang.org/grpc/internal/backoff"
|
||||||
"google.golang.org/grpc/internal/buffer"
|
"google.golang.org/grpc/internal/buffer"
|
||||||
"google.golang.org/grpc/internal/grpclog"
|
"google.golang.org/grpc/internal/grpclog"
|
||||||
"google.golang.org/grpc/internal/grpcsync"
|
"google.golang.org/grpc/internal/grpcsync"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
"google.golang.org/grpc/xds/internal"
|
||||||
"google.golang.org/grpc/xds/internal/client/bootstrap"
|
"google.golang.org/grpc/xds/internal/client/bootstrap"
|
||||||
"google.golang.org/grpc/xds/internal/version"
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
m = make(map[version.TransportAPI]APIClientBuilder)
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterAPIClientBuilder registers a client builder for xDS transport protocol
|
||||||
|
// version specified by b.Version().
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple builders are
|
||||||
|
// registered for the same version, the one registered last will take effect.
|
||||||
|
func RegisterAPIClientBuilder(b APIClientBuilder) {
|
||||||
|
m[b.Version()] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPIClientBuilder returns the client builder registered for the provided
|
||||||
|
// xDS transport API version.
|
||||||
|
func getAPIClientBuilder(version version.TransportAPI) APIClientBuilder {
|
||||||
|
if b, ok := m[version]; ok {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildOptions contains options to be passed to client builders.
|
||||||
|
type BuildOptions struct {
|
||||||
|
// Parent is a top-level xDS client or server which has the intelligence to
|
||||||
|
// take appropriate action based on xDS responses received from the
|
||||||
|
// management server.
|
||||||
|
Parent UpdateHandler
|
||||||
|
// NodeProto contains the Node proto to be used in xDS requests. The actual
|
||||||
|
// type depends on the transport protocol version used.
|
||||||
|
NodeProto proto.Message
|
||||||
|
// Backoff returns the amount of time to backoff before retrying broken
|
||||||
|
// streams.
|
||||||
|
Backoff func(int) time.Duration
|
||||||
|
// Logger provides enhanced logging capabilities.
|
||||||
|
Logger *grpclog.PrefixLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIClientBuilder creates an xDS client for a specific xDS transport protocol
|
||||||
|
// version.
|
||||||
|
type APIClientBuilder interface {
|
||||||
|
// Build builds a transport protocol specific implementation of the xDS
|
||||||
|
// client based on the provided clientConn to the management server and the
|
||||||
|
// provided options.
|
||||||
|
Build(*grpc.ClientConn, BuildOptions) (APIClient, error)
|
||||||
|
// Version returns the xDS transport protocol version used by clients build
|
||||||
|
// using this builder.
|
||||||
|
Version() version.TransportAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIClient represents the functionality provided by transport protocol
|
||||||
|
// version specific implementations of the xDS client.
|
||||||
|
type APIClient interface {
|
||||||
|
// AddWatch adds a watch for an xDS resource given its type and name.
|
||||||
|
AddWatch(resourceType, resourceName string)
|
||||||
|
// RemoveWatch cancels an already registered watch for an xDS resource
|
||||||
|
// given its type and name.
|
||||||
|
RemoveWatch(resourceType, resourceName string)
|
||||||
|
// Close cleans up resources allocated by the API client.
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHandler receives and processes (by taking appropriate actions) xDS
|
||||||
|
// resource updates from an APIClient for a specific version.
|
||||||
|
type UpdateHandler interface {
|
||||||
|
// NewListeners handles updates to xDS listener resources.
|
||||||
|
NewListeners(map[string]ListenerUpdate)
|
||||||
|
// NewRouteConfigs handles updates to xDS RouteConfiguration resources.
|
||||||
|
NewRouteConfigs(map[string]RouteConfigUpdate)
|
||||||
|
// NewClusters handles updates to xDS Cluster resources.
|
||||||
|
NewClusters(map[string]ClusterUpdate)
|
||||||
|
// NewEndpoints handles updates to xDS ClusterLoadAssignment (or tersely
|
||||||
|
// referred to as Endpoints) resources.
|
||||||
|
NewEndpoints(map[string]EndpointsUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenerUpdate contains information received in an LDS response, which is of
|
||||||
|
// interest to the registered LDS watcher.
|
||||||
|
type ListenerUpdate struct {
|
||||||
|
// RouteConfigName is the route configuration name corresponding to the
|
||||||
|
// target which is being watched through LDS.
|
||||||
|
RouteConfigName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteConfigUpdate contains information received in an RDS response, which is
|
||||||
|
// of interest to the registered RDS watcher.
|
||||||
|
type RouteConfigUpdate struct {
|
||||||
|
// Routes contains a list of routes, each containing matchers and
|
||||||
|
// corresponding action.
|
||||||
|
Routes []*Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route is both a specification of how to match a request as well as an
|
||||||
|
// indication of the action to take upon match.
|
||||||
|
type Route struct {
|
||||||
|
Path, Prefix, Regex *string
|
||||||
|
Headers []*HeaderMatcher
|
||||||
|
Fraction *uint32
|
||||||
|
Action map[string]uint32 // action is weighted clusters.
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderMatcher represents header matchers.
|
||||||
|
type HeaderMatcher struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
InvertMatch *bool `json:"invertMatch,omitempty"`
|
||||||
|
ExactMatch *string `json:"exactMatch,omitempty"`
|
||||||
|
RegexMatch *string `json:"regexMatch,omitempty"`
|
||||||
|
PrefixMatch *string `json:"prefixMatch,omitempty"`
|
||||||
|
SuffixMatch *string `json:"suffixMatch,omitempty"`
|
||||||
|
RangeMatch *Int64Range `json:"rangeMatch,omitempty"`
|
||||||
|
PresentMatch *bool `json:"presentMatch,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Range is a range for header range match.
|
||||||
|
type Int64Range struct {
|
||||||
|
Start int64 `json:"start"`
|
||||||
|
End int64 `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceUpdate contains information received from LDS and RDS responses,
|
||||||
|
// which is of interest to the registered service watcher.
|
||||||
|
type ServiceUpdate struct {
|
||||||
|
// Routes contain matchers+actions to route RPCs.
|
||||||
|
Routes []*Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClusterUpdate contains information from a received CDS response, which is of
|
||||||
|
// interest to the registered CDS watcher.
|
||||||
|
type ClusterUpdate struct {
|
||||||
|
// ServiceName is the service name corresponding to the clusterName which
|
||||||
|
// is being watched for through CDS.
|
||||||
|
ServiceName string
|
||||||
|
// EnableLRS indicates whether or not load should be reported through LRS.
|
||||||
|
EnableLRS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverloadDropConfig contains the config to drop overloads.
|
||||||
|
type OverloadDropConfig struct {
|
||||||
|
Category string
|
||||||
|
Numerator uint32
|
||||||
|
Denominator uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointHealthStatus represents the health status of an endpoint.
|
||||||
|
type EndpointHealthStatus int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EndpointHealthStatusUnknown represents HealthStatus UNKNOWN.
|
||||||
|
EndpointHealthStatusUnknown EndpointHealthStatus = iota
|
||||||
|
// EndpointHealthStatusHealthy represents HealthStatus HEALTHY.
|
||||||
|
EndpointHealthStatusHealthy
|
||||||
|
// EndpointHealthStatusUnhealthy represents HealthStatus UNHEALTHY.
|
||||||
|
EndpointHealthStatusUnhealthy
|
||||||
|
// EndpointHealthStatusDraining represents HealthStatus DRAINING.
|
||||||
|
EndpointHealthStatusDraining
|
||||||
|
// EndpointHealthStatusTimeout represents HealthStatus TIMEOUT.
|
||||||
|
EndpointHealthStatusTimeout
|
||||||
|
// EndpointHealthStatusDegraded represents HealthStatus DEGRADED.
|
||||||
|
EndpointHealthStatusDegraded
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint contains information of an endpoint.
|
||||||
|
type Endpoint struct {
|
||||||
|
Address string
|
||||||
|
HealthStatus EndpointHealthStatus
|
||||||
|
Weight uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locality contains information of a locality.
|
||||||
|
type Locality struct {
|
||||||
|
Endpoints []Endpoint
|
||||||
|
ID internal.LocalityID
|
||||||
|
Priority uint32
|
||||||
|
Weight uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointsUpdate contains an EDS update.
|
||||||
|
type EndpointsUpdate struct {
|
||||||
|
Drops []OverloadDropConfig
|
||||||
|
Localities []Locality
|
||||||
|
}
|
||||||
|
|
||||||
// Options provides all parameters required for the creation of an xDS client.
|
// Options provides all parameters required for the creation of an xDS client.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// Config contains a fully populated bootstrap config. It is the
|
// Config contains a fully populated bootstrap config. It is the
|
||||||
|
|
@ -49,16 +236,13 @@ type Options struct {
|
||||||
TargetName string
|
TargetName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface to be overridden in tests.
|
|
||||||
type xdsv2Client interface {
|
|
||||||
addWatch(resourceType, resourceName string)
|
|
||||||
removeWatch(resourceType, resourceName string)
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to be overridden in tests.
|
// Function to be overridden in tests.
|
||||||
var newXDSV2Client = func(parent *Client, cc *grpc.ClientConn, nodeProto *corepb.Node, backoff func(int) time.Duration, logger *grpclog.PrefixLogger) xdsv2Client {
|
var newAPIClient = func(apiVersion version.TransportAPI, cc *grpc.ClientConn, opts BuildOptions) (APIClient, error) {
|
||||||
return newV2Client(parent, cc, nodeProto, backoff, logger)
|
cb := getAPIClientBuilder(apiVersion)
|
||||||
|
if cb == nil {
|
||||||
|
return nil, fmt.Errorf("no client builder for xDS API version: %v", apiVersion)
|
||||||
|
}
|
||||||
|
return cb.Build(cc, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a full fledged gRPC client which queries a set of discovery APIs
|
// Client is a full fledged gRPC client which queries a set of discovery APIs
|
||||||
|
|
@ -68,20 +252,25 @@ var newXDSV2Client = func(parent *Client, cc *grpc.ClientConn, nodeProto *corepb
|
||||||
// A single client object will be shared by the xds resolver and balancer
|
// A single client object will be shared by the xds resolver and balancer
|
||||||
// implementations. But the same client can only be shared by the same parent
|
// implementations. But the same client can only be shared by the same parent
|
||||||
// ClientConn.
|
// ClientConn.
|
||||||
|
//
|
||||||
|
// Implements UpdateHandler interface.
|
||||||
|
// TODO(easwars): Make a wrapper struct which implements this interface in the
|
||||||
|
// style of ccBalancerWrapper so that the Client type does not implement these
|
||||||
|
// exported methods.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
done *grpcsync.Event
|
done *grpcsync.Event
|
||||||
opts Options
|
opts Options
|
||||||
cc *grpc.ClientConn // Connection to the xDS server
|
cc *grpc.ClientConn // Connection to the xDS server
|
||||||
v2c xdsv2Client // Actual xDS client implementation using the v2 API
|
apiClient APIClient
|
||||||
|
|
||||||
logger *grpclog.PrefixLogger
|
logger *grpclog.PrefixLogger
|
||||||
|
|
||||||
updateCh *buffer.Unbounded // chan *watcherInfoWithUpdate
|
updateCh *buffer.Unbounded // chan *watcherInfoWithUpdate
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
ldsWatchers map[string]map[*watchInfo]bool
|
ldsWatchers map[string]map[*watchInfo]bool
|
||||||
ldsCache map[string]ldsUpdate
|
ldsCache map[string]ListenerUpdate
|
||||||
rdsWatchers map[string]map[*watchInfo]bool
|
rdsWatchers map[string]map[*watchInfo]bool
|
||||||
rdsCache map[string]rdsUpdate
|
rdsCache map[string]RouteConfigUpdate
|
||||||
cdsWatchers map[string]map[*watchInfo]bool
|
cdsWatchers map[string]map[*watchInfo]bool
|
||||||
cdsCache map[string]ClusterUpdate
|
cdsCache map[string]ClusterUpdate
|
||||||
edsWatchers map[string]map[*watchInfo]bool
|
edsWatchers map[string]map[*watchInfo]bool
|
||||||
|
|
@ -99,6 +288,17 @@ func New(opts Options) (*Client, error) {
|
||||||
return nil, errors.New("xds: no node_proto provided in options")
|
return nil, errors.New("xds: no node_proto provided in options")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch opts.Config.TransportAPI {
|
||||||
|
case version.TransportV2:
|
||||||
|
if _, ok := opts.Config.NodeProto.(*v2corepb.Node); !ok {
|
||||||
|
return nil, fmt.Errorf("xds: Node proto type (%T) does not match API version: %v", opts.Config.NodeProto, opts.Config.TransportAPI)
|
||||||
|
}
|
||||||
|
case version.TransportV3:
|
||||||
|
if _, ok := opts.Config.NodeProto.(*v3corepb.Node); !ok {
|
||||||
|
return nil, fmt.Errorf("xds: Node proto type (%T) does not match API version: %v", opts.Config.NodeProto, opts.Config.TransportAPI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dopts := []grpc.DialOption{
|
dopts := []grpc.DialOption{
|
||||||
opts.Config.Creds,
|
opts.Config.Creds,
|
||||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||||
|
|
@ -114,9 +314,9 @@ func New(opts Options) (*Client, error) {
|
||||||
|
|
||||||
updateCh: buffer.NewUnbounded(),
|
updateCh: buffer.NewUnbounded(),
|
||||||
ldsWatchers: make(map[string]map[*watchInfo]bool),
|
ldsWatchers: make(map[string]map[*watchInfo]bool),
|
||||||
ldsCache: make(map[string]ldsUpdate),
|
ldsCache: make(map[string]ListenerUpdate),
|
||||||
rdsWatchers: make(map[string]map[*watchInfo]bool),
|
rdsWatchers: make(map[string]map[*watchInfo]bool),
|
||||||
rdsCache: make(map[string]rdsUpdate),
|
rdsCache: make(map[string]RouteConfigUpdate),
|
||||||
cdsWatchers: make(map[string]map[*watchInfo]bool),
|
cdsWatchers: make(map[string]map[*watchInfo]bool),
|
||||||
cdsCache: make(map[string]ClusterUpdate),
|
cdsCache: make(map[string]ClusterUpdate),
|
||||||
edsWatchers: make(map[string]map[*watchInfo]bool),
|
edsWatchers: make(map[string]map[*watchInfo]bool),
|
||||||
|
|
@ -132,13 +332,16 @@ func New(opts Options) (*Client, error) {
|
||||||
c.logger = prefixLogger((c))
|
c.logger = prefixLogger((c))
|
||||||
c.logger.Infof("Created ClientConn to xDS server: %s", opts.Config.BalancerName)
|
c.logger.Infof("Created ClientConn to xDS server: %s", opts.Config.BalancerName)
|
||||||
|
|
||||||
if opts.Config.TransportAPI == version.TransportV2 {
|
apiClient, err := newAPIClient(opts.Config.TransportAPI, cc, BuildOptions{
|
||||||
c.v2c = newXDSV2Client(c, cc, opts.Config.NodeProto.(*corepb.Node), backoff.DefaultExponential.Backoff, c.logger)
|
Parent: c,
|
||||||
} else {
|
NodeProto: opts.Config.NodeProto,
|
||||||
// TODO(easwars): Remove this once v3Client is ready.
|
Backoff: backoff.DefaultExponential.Backoff,
|
||||||
return nil, errors.New("xds v3 client is not yet supported")
|
Logger: c.logger,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
c.apiClient = apiClient
|
||||||
c.logger.Infof("Created")
|
c.logger.Infof("Created")
|
||||||
go c.run()
|
go c.run()
|
||||||
return c, nil
|
return c, nil
|
||||||
|
|
@ -173,7 +376,7 @@ func (c *Client) Close() {
|
||||||
c.done.Fire()
|
c.done.Fire()
|
||||||
// TODO: Should we invoke the registered callbacks here with an error that
|
// TODO: Should we invoke the registered callbacks here with an error that
|
||||||
// the client is closed?
|
// the client is closed?
|
||||||
c.v2c.close()
|
c.apiClient.Close()
|
||||||
c.cc.Close()
|
c.cc.Close()
|
||||||
c.logger.Infof("Shutdown")
|
c.logger.Infof("Shutdown")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
|
import "google.golang.org/grpc/xds/internal/version"
|
||||||
|
|
||||||
type watcherInfoWithUpdate struct {
|
type watcherInfoWithUpdate struct {
|
||||||
wi *watchInfo
|
wi *watchInfo
|
||||||
update interface{}
|
update interface{}
|
||||||
|
|
@ -45,19 +47,19 @@ func (c *Client) callCallback(wiu *watcherInfoWithUpdate) {
|
||||||
// canceled, and the user needs to take care of it.
|
// canceled, and the user needs to take care of it.
|
||||||
var ccb func()
|
var ccb func()
|
||||||
switch wiu.wi.typeURL {
|
switch wiu.wi.typeURL {
|
||||||
case ldsURL:
|
case version.V2ListenerURL:
|
||||||
if s, ok := c.ldsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
if s, ok := c.ldsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
||||||
ccb = func() { wiu.wi.ldsCallback(wiu.update.(ldsUpdate), wiu.err) }
|
ccb = func() { wiu.wi.ldsCallback(wiu.update.(ListenerUpdate), wiu.err) }
|
||||||
}
|
}
|
||||||
case rdsURL:
|
case version.V2RouteConfigURL:
|
||||||
if s, ok := c.rdsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
if s, ok := c.rdsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
||||||
ccb = func() { wiu.wi.rdsCallback(wiu.update.(rdsUpdate), wiu.err) }
|
ccb = func() { wiu.wi.rdsCallback(wiu.update.(RouteConfigUpdate), wiu.err) }
|
||||||
}
|
}
|
||||||
case cdsURL:
|
case version.V2ClusterURL:
|
||||||
if s, ok := c.cdsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
if s, ok := c.cdsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
||||||
ccb = func() { wiu.wi.cdsCallback(wiu.update.(ClusterUpdate), wiu.err) }
|
ccb = func() { wiu.wi.cdsCallback(wiu.update.(ClusterUpdate), wiu.err) }
|
||||||
}
|
}
|
||||||
case edsURL:
|
case version.V2EndpointsURL:
|
||||||
if s, ok := c.edsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
if s, ok := c.edsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
|
||||||
ccb = func() { wiu.wi.edsCallback(wiu.update.(EndpointsUpdate), wiu.err) }
|
ccb = func() { wiu.wi.edsCallback(wiu.update.(EndpointsUpdate), wiu.err) }
|
||||||
}
|
}
|
||||||
|
|
@ -69,12 +71,12 @@ func (c *Client) callCallback(wiu *watcherInfoWithUpdate) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newLDSUpdate is called by the underlying xdsv2Client when it receives an xDS
|
// NewListeners is called by the underlying xdsAPIClient when it receives an
|
||||||
// response.
|
// xDS response.
|
||||||
//
|
//
|
||||||
// A response can contain multiple resources. They will be parsed and put in a
|
// A response can contain multiple resources. They will be parsed and put in a
|
||||||
// map from resource name to the resource content.
|
// map from resource name to the resource content.
|
||||||
func (c *Client) newLDSUpdate(updates map[string]ldsUpdate) {
|
func (c *Client) NewListeners(updates map[string]ListenerUpdate) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -104,12 +106,12 @@ func (c *Client) newLDSUpdate(updates map[string]ldsUpdate) {
|
||||||
// last watch is canceled.
|
// last watch is canceled.
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRDSUpdate is called by the underlying xdsv2Client when it receives an xDS
|
// NewRouteConfigs is called by the underlying xdsAPIClient when it receives an
|
||||||
// response.
|
// xDS response.
|
||||||
//
|
//
|
||||||
// A response can contain multiple resources. They will be parsed and put in a
|
// A response can contain multiple resources. They will be parsed and put in a
|
||||||
// map from resource name to the resource content.
|
// map from resource name to the resource content.
|
||||||
func (c *Client) newRDSUpdate(updates map[string]rdsUpdate) {
|
func (c *Client) NewRouteConfigs(updates map[string]RouteConfigUpdate) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -125,12 +127,12 @@ func (c *Client) newRDSUpdate(updates map[string]rdsUpdate) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCDSUpdate is called by the underlying xdsv2Client when it receives an xDS
|
// NewClusters is called by the underlying xdsAPIClient when it receives an xDS
|
||||||
// response.
|
// response.
|
||||||
//
|
//
|
||||||
// A response can contain multiple resources. They will be parsed and put in a
|
// A response can contain multiple resources. They will be parsed and put in a
|
||||||
// map from resource name to the resource content.
|
// map from resource name to the resource content.
|
||||||
func (c *Client) newCDSUpdate(updates map[string]ClusterUpdate) {
|
func (c *Client) NewClusters(updates map[string]ClusterUpdate) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -160,12 +162,12 @@ func (c *Client) newCDSUpdate(updates map[string]ClusterUpdate) {
|
||||||
// last watch is canceled.
|
// last watch is canceled.
|
||||||
}
|
}
|
||||||
|
|
||||||
// newEDSUpdate is called by the underlying xdsv2Client when it receives an xDS
|
// NewEndpoints is called by the underlying xdsAPIClient when it receives an
|
||||||
// response.
|
// xDS response.
|
||||||
//
|
//
|
||||||
// A response can contain multiple resources. They will be parsed and put in a
|
// A response can contain multiple resources. They will be parsed and put in a
|
||||||
// map from resource name to the resource content.
|
// map from resource name to the resource content.
|
||||||
func (c *Client) newEDSUpdate(updates map[string]EndpointsUpdate) {
|
func (c *Client) NewEndpoints(updates map[string]EndpointsUpdate) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,312 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
|
v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
|
v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||||
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s) TestValidateCluster(t *testing.T) {
|
||||||
|
const (
|
||||||
|
clusterName = "clusterName"
|
||||||
|
serviceName = "service"
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
emptyUpdate = ClusterUpdate{ServiceName: "", EnableLRS: false}
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cluster *v3clusterpb.Cluster
|
||||||
|
wantUpdate ClusterUpdate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-eds-cluster-type",
|
||||||
|
cluster: &v3clusterpb.Cluster{
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC},
|
||||||
|
EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
|
||||||
|
EdsConfig: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
|
||||||
|
Ads: &v3corepb.AggregatedConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
|
||||||
|
},
|
||||||
|
wantUpdate: emptyUpdate,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-eds-config",
|
||||||
|
cluster: &v3clusterpb.Cluster{
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
|
},
|
||||||
|
wantUpdate: emptyUpdate,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-ads-config-source",
|
||||||
|
cluster: &v3clusterpb.Cluster{
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
|
||||||
|
EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
|
},
|
||||||
|
wantUpdate: emptyUpdate,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-round-robin-lb-policy",
|
||||||
|
cluster: &v3clusterpb.Cluster{
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
|
||||||
|
EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
|
||||||
|
EdsConfig: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
|
||||||
|
Ads: &v3corepb.AggregatedConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
|
||||||
|
},
|
||||||
|
wantUpdate: emptyUpdate,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy-case-no-service-name-no-lrs",
|
||||||
|
cluster: &v3clusterpb.Cluster{
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
|
||||||
|
EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
|
||||||
|
EdsConfig: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
|
||||||
|
Ads: &v3corepb.AggregatedConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
|
},
|
||||||
|
wantUpdate: emptyUpdate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy-case-no-lrs",
|
||||||
|
cluster: &v3clusterpb.Cluster{
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
|
||||||
|
EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
|
||||||
|
EdsConfig: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
|
||||||
|
Ads: &v3corepb.AggregatedConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServiceName: serviceName,
|
||||||
|
},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
|
},
|
||||||
|
wantUpdate: ClusterUpdate{ServiceName: serviceName, EnableLRS: false},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happiest-case",
|
||||||
|
cluster: &v3clusterpb.Cluster{
|
||||||
|
Name: clusterName,
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
|
||||||
|
EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
|
||||||
|
EdsConfig: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
|
||||||
|
Ads: &v3corepb.AggregatedConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServiceName: serviceName,
|
||||||
|
},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
|
LrsServer: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
|
||||||
|
Self: &v3corepb.SelfConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: ClusterUpdate{ServiceName: serviceName, EnableLRS: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
update, err := validateCluster(test.cluster)
|
||||||
|
if ((err != nil) != test.wantErr) || !cmp.Equal(update, test.wantUpdate, cmpopts.EquateEmpty()) {
|
||||||
|
t.Errorf("validateCluster(%+v) = (%v, %v), wantErr: (%v, %v)", test.cluster, update, err, test.wantUpdate, test.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestUnmarshalCluster(t *testing.T) {
|
||||||
|
const (
|
||||||
|
v2ClusterName = "v2clusterName"
|
||||||
|
v3ClusterName = "v3clusterName"
|
||||||
|
v2Service = "v2Service"
|
||||||
|
v3Service = "v2Service"
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
v2Cluster = &v2xdspb.Cluster{
|
||||||
|
Name: v2ClusterName,
|
||||||
|
ClusterDiscoveryType: &v2xdspb.Cluster_Type{Type: v2xdspb.Cluster_EDS},
|
||||||
|
EdsClusterConfig: &v2xdspb.Cluster_EdsClusterConfig{
|
||||||
|
EdsConfig: &v2corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v2corepb.ConfigSource_Ads{
|
||||||
|
Ads: &v2corepb.AggregatedConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServiceName: v2Service,
|
||||||
|
},
|
||||||
|
LbPolicy: v2xdspb.Cluster_ROUND_ROBIN,
|
||||||
|
LrsServer: &v2corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v2corepb.ConfigSource_Self{
|
||||||
|
Self: &v2corepb.SelfConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
v3Cluster = &v3clusterpb.Cluster{
|
||||||
|
Name: v3ClusterName,
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
|
||||||
|
EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
|
||||||
|
EdsConfig: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
|
||||||
|
Ads: &v3corepb.AggregatedConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServiceName: v3Service,
|
||||||
|
},
|
||||||
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
|
LrsServer: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
|
||||||
|
Self: &v3corepb.SelfConfigSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
resources []*anypb.Any
|
||||||
|
wantUpdate map[string]ClusterUpdate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-cluster resource type",
|
||||||
|
resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "badly marshaled cluster resource",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3ClusterURL,
|
||||||
|
Value: []byte{1, 2, 3, 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad cluster resource",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3ClusterURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cl := &v3clusterpb.Cluster{
|
||||||
|
ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC},
|
||||||
|
}
|
||||||
|
mcl, _ := proto.Marshal(cl)
|
||||||
|
return mcl
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v2 cluster",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3ClusterURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
mcl, _ := proto.Marshal(v2Cluster)
|
||||||
|
return mcl
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: map[string]ClusterUpdate{
|
||||||
|
v2ClusterName: {ServiceName: v2Service, EnableLRS: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v3 cluster",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3ClusterURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
mcl, _ := proto.Marshal(v3Cluster)
|
||||||
|
return mcl
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: map[string]ClusterUpdate{
|
||||||
|
v3ClusterName: {ServiceName: v3Service, EnableLRS: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple clusters",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3ClusterURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
mcl, _ := proto.Marshal(v2Cluster)
|
||||||
|
return mcl
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3ClusterURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
mcl, _ := proto.Marshal(v3Cluster)
|
||||||
|
return mcl
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: map[string]ClusterUpdate{
|
||||||
|
v2ClusterName: {ServiceName: v2Service, EnableLRS: true},
|
||||||
|
v3ClusterName: {ServiceName: v3Service, EnableLRS: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
update, err := UnmarshalCluster(test.resources, nil)
|
||||||
|
if ((err != nil) != test.wantErr) || !cmp.Equal(update, test.wantUpdate, cmpopts.EquateEmpty()) {
|
||||||
|
t.Errorf("UnmarshalCluster(%v) = (%+v, %v) want (%+v, %v)", test.resources, update, err, test.wantUpdate, test.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,312 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
||||||
|
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
|
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"google.golang.org/grpc/xds/internal"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s) TestEDSParseRespProto(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
m *v3endpointpb.ClusterLoadAssignment
|
||||||
|
want EndpointsUpdate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing-priority",
|
||||||
|
m: func() *v3endpointpb.ClusterLoadAssignment {
|
||||||
|
clab0 := newClaBuilder("test", nil)
|
||||||
|
clab0.addLocality("locality-1", 1, 0, []string{"addr1:314"}, nil)
|
||||||
|
clab0.addLocality("locality-2", 1, 2, []string{"addr2:159"}, nil)
|
||||||
|
return clab0.Build()
|
||||||
|
}(),
|
||||||
|
want: EndpointsUpdate{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing-locality-ID",
|
||||||
|
m: func() *v3endpointpb.ClusterLoadAssignment {
|
||||||
|
clab0 := newClaBuilder("test", nil)
|
||||||
|
clab0.addLocality("", 1, 0, []string{"addr1:314"}, nil)
|
||||||
|
return clab0.Build()
|
||||||
|
}(),
|
||||||
|
want: EndpointsUpdate{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good",
|
||||||
|
m: func() *v3endpointpb.ClusterLoadAssignment {
|
||||||
|
clab0 := newClaBuilder("test", nil)
|
||||||
|
clab0.addLocality("locality-1", 1, 1, []string{"addr1:314"}, &addLocalityOptions{
|
||||||
|
Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_UNHEALTHY},
|
||||||
|
Weight: []uint32{271},
|
||||||
|
})
|
||||||
|
clab0.addLocality("locality-2", 1, 0, []string{"addr2:159"}, &addLocalityOptions{
|
||||||
|
Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_DRAINING},
|
||||||
|
Weight: []uint32{828},
|
||||||
|
})
|
||||||
|
return clab0.Build()
|
||||||
|
}(),
|
||||||
|
want: EndpointsUpdate{
|
||||||
|
Drops: nil,
|
||||||
|
Localities: []Locality{
|
||||||
|
{
|
||||||
|
Endpoints: []Endpoint{{
|
||||||
|
Address: "addr1:314",
|
||||||
|
HealthStatus: EndpointHealthStatusUnhealthy,
|
||||||
|
Weight: 271,
|
||||||
|
}},
|
||||||
|
ID: internal.LocalityID{SubZone: "locality-1"},
|
||||||
|
Priority: 1,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Endpoints: []Endpoint{{
|
||||||
|
Address: "addr2:159",
|
||||||
|
HealthStatus: EndpointHealthStatusDraining,
|
||||||
|
Weight: 828,
|
||||||
|
}},
|
||||||
|
ID: internal.LocalityID{SubZone: "locality-2"},
|
||||||
|
Priority: 0,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseEDSRespProto(tt.m)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if d := cmp.Diff(got, tt.want); d != "" {
|
||||||
|
t.Errorf("parseEDSRespProto() got = %v, want %v, diff: %v", got, tt.want, d)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestUnmarshalEndpoints(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
resources []*anypb.Any
|
||||||
|
wantUpdate map[string]EndpointsUpdate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-clusterLoadAssignment resource type",
|
||||||
|
resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "badly marshaled clusterLoadAssignment resource",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3EndpointsURL,
|
||||||
|
Value: []byte{1, 2, 3, 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad endpoints resource",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3EndpointsURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
clab0 := newClaBuilder("test", nil)
|
||||||
|
clab0.addLocality("locality-1", 1, 0, []string{"addr1:314"}, nil)
|
||||||
|
clab0.addLocality("locality-2", 1, 2, []string{"addr2:159"}, nil)
|
||||||
|
e := clab0.Build()
|
||||||
|
me, _ := proto.Marshal(e)
|
||||||
|
return me
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v3 endpoints",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3EndpointsURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
clab0 := newClaBuilder("test", nil)
|
||||||
|
clab0.addLocality("locality-1", 1, 1, []string{"addr1:314"}, &addLocalityOptions{
|
||||||
|
Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_UNHEALTHY},
|
||||||
|
Weight: []uint32{271},
|
||||||
|
})
|
||||||
|
clab0.addLocality("locality-2", 1, 0, []string{"addr2:159"}, &addLocalityOptions{
|
||||||
|
Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_DRAINING},
|
||||||
|
Weight: []uint32{828},
|
||||||
|
})
|
||||||
|
e := clab0.Build()
|
||||||
|
me, _ := proto.Marshal(e)
|
||||||
|
return me
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: map[string]EndpointsUpdate{
|
||||||
|
"test": {
|
||||||
|
Drops: nil,
|
||||||
|
Localities: []Locality{
|
||||||
|
{
|
||||||
|
Endpoints: []Endpoint{{
|
||||||
|
Address: "addr1:314",
|
||||||
|
HealthStatus: EndpointHealthStatusUnhealthy,
|
||||||
|
Weight: 271,
|
||||||
|
}},
|
||||||
|
ID: internal.LocalityID{SubZone: "locality-1"},
|
||||||
|
Priority: 1,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Endpoints: []Endpoint{{
|
||||||
|
Address: "addr2:159",
|
||||||
|
HealthStatus: EndpointHealthStatusDraining,
|
||||||
|
Weight: 828,
|
||||||
|
}},
|
||||||
|
ID: internal.LocalityID{SubZone: "locality-2"},
|
||||||
|
Priority: 0,
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
update, err := UnmarshalEndpoints(test.resources, nil)
|
||||||
|
if ((err != nil) != test.wantErr) || !cmp.Equal(update, test.wantUpdate, cmpopts.EquateEmpty()) {
|
||||||
|
t.Errorf("UnmarshalEndpoints(%v) = (%+v, %v) want (%+v, %v)", test.resources, update, err, test.wantUpdate, test.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// claBuilder builds a ClusterLoadAssignment, aka EDS
|
||||||
|
// response.
|
||||||
|
type claBuilder struct {
|
||||||
|
v *v3endpointpb.ClusterLoadAssignment
|
||||||
|
}
|
||||||
|
|
||||||
|
// newClaBuilder creates a claBuilder.
|
||||||
|
func newClaBuilder(clusterName string, dropPercents []uint32) *claBuilder {
|
||||||
|
var drops []*v3endpointpb.ClusterLoadAssignment_Policy_DropOverload
|
||||||
|
for i, d := range dropPercents {
|
||||||
|
drops = append(drops, &v3endpointpb.ClusterLoadAssignment_Policy_DropOverload{
|
||||||
|
Category: fmt.Sprintf("test-drop-%d", i),
|
||||||
|
DropPercentage: &v3typepb.FractionalPercent{
|
||||||
|
Numerator: d,
|
||||||
|
Denominator: v3typepb.FractionalPercent_HUNDRED,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &claBuilder{
|
||||||
|
v: &v3endpointpb.ClusterLoadAssignment{
|
||||||
|
ClusterName: clusterName,
|
||||||
|
Policy: &v3endpointpb.ClusterLoadAssignment_Policy{
|
||||||
|
DropOverloads: drops,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addLocalityOptions contains options when adding locality to the builder.
|
||||||
|
type addLocalityOptions struct {
|
||||||
|
Health []v3corepb.HealthStatus
|
||||||
|
Weight []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// addLocality adds a locality to the builder.
|
||||||
|
func (clab *claBuilder) addLocality(subzone string, weight uint32, priority uint32, addrsWithPort []string, opts *addLocalityOptions) {
|
||||||
|
var lbEndPoints []*v3endpointpb.LbEndpoint
|
||||||
|
for i, a := range addrsWithPort {
|
||||||
|
host, portStr, err := net.SplitHostPort(a)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to split " + a)
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to atoi " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
lbe := &v3endpointpb.LbEndpoint{
|
||||||
|
HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{
|
||||||
|
Endpoint: &v3endpointpb.Endpoint{
|
||||||
|
Address: &v3corepb.Address{
|
||||||
|
Address: &v3corepb.Address_SocketAddress{
|
||||||
|
SocketAddress: &v3corepb.SocketAddress{
|
||||||
|
Protocol: v3corepb.SocketAddress_TCP,
|
||||||
|
Address: host,
|
||||||
|
PortSpecifier: &v3corepb.SocketAddress_PortValue{
|
||||||
|
PortValue: uint32(port)}}}}}},
|
||||||
|
}
|
||||||
|
if opts != nil {
|
||||||
|
if i < len(opts.Health) {
|
||||||
|
lbe.HealthStatus = opts.Health[i]
|
||||||
|
}
|
||||||
|
if i < len(opts.Weight) {
|
||||||
|
lbe.LoadBalancingWeight = &wrapperspb.UInt32Value{Value: opts.Weight[i]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lbEndPoints = append(lbEndPoints, lbe)
|
||||||
|
}
|
||||||
|
|
||||||
|
var localityID *v3corepb.Locality
|
||||||
|
if subzone != "" {
|
||||||
|
localityID = &v3corepb.Locality{
|
||||||
|
Region: "",
|
||||||
|
Zone: "",
|
||||||
|
SubZone: subzone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clab.v.Endpoints = append(clab.v.Endpoints, &v3endpointpb.LocalityLbEndpoints{
|
||||||
|
Locality: localityID,
|
||||||
|
LbEndpoints: lbEndPoints,
|
||||||
|
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: weight},
|
||||||
|
Priority: priority,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build builds ClusterLoadAssignment.
|
||||||
|
func (clab *claBuilder) Build() *v3endpointpb.ClusterLoadAssignment {
|
||||||
|
return clab.v
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
|
v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
v2httppb "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
|
||||||
|
v2listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v2"
|
||||||
|
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
|
v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s) TestUnmarshalListener(t *testing.T) {
|
||||||
|
const (
|
||||||
|
v2LDSTarget = "lds.target.good:2222"
|
||||||
|
v3LDSTarget = "lds.target.good:3333"
|
||||||
|
v2RouteConfigName = "v2RouteConfig"
|
||||||
|
v3RouteConfigName = "v3RouteConfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
v2Lis = &anypb.Any{
|
||||||
|
TypeUrl: version.V2ListenerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cm := &v2httppb.HttpConnectionManager{
|
||||||
|
RouteSpecifier: &v2httppb.HttpConnectionManager_Rds{
|
||||||
|
Rds: &v2httppb.Rds{
|
||||||
|
ConfigSource: &v2corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v2corepb.ConfigSource_Ads{Ads: &v2corepb.AggregatedConfigSource{}},
|
||||||
|
},
|
||||||
|
RouteConfigName: v2RouteConfigName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mcm, _ := proto.Marshal(cm)
|
||||||
|
lis := &v2xdspb.Listener{
|
||||||
|
Name: v2LDSTarget,
|
||||||
|
ApiListener: &v2listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V2HTTPConnManagerURL,
|
||||||
|
Value: mcm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mLis, _ := proto.Marshal(lis)
|
||||||
|
return mLis
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
v3Lis = &anypb.Any{
|
||||||
|
TypeUrl: version.V3ListenerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cm := &v3httppb.HttpConnectionManager{
|
||||||
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
|
||||||
|
Rds: &v3httppb.Rds{
|
||||||
|
ConfigSource: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
|
||||||
|
},
|
||||||
|
RouteConfigName: v3RouteConfigName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mcm, _ := proto.Marshal(cm)
|
||||||
|
lis := &v3listenerpb.Listener{
|
||||||
|
Name: v3LDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V3HTTPConnManagerURL,
|
||||||
|
Value: mcm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mLis, _ := proto.Marshal(lis)
|
||||||
|
return mLis
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
resources []*anypb.Any
|
||||||
|
wantUpdate map[string]ListenerUpdate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-listener resource",
|
||||||
|
resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "badly marshaled listener resource",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3ListenerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
lis := &v3listenerpb.Listener{
|
||||||
|
Name: v3LDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V3HTTPConnManagerURL,
|
||||||
|
Value: []byte{1, 2, 3, 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mLis, _ := proto.Marshal(lis)
|
||||||
|
return mLis
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty resource list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v2 listener resource",
|
||||||
|
resources: []*anypb.Any{v2Lis},
|
||||||
|
wantUpdate: map[string]ListenerUpdate{
|
||||||
|
v2LDSTarget: {RouteConfigName: v2RouteConfigName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v3 listener resource",
|
||||||
|
resources: []*anypb.Any{v3Lis},
|
||||||
|
wantUpdate: map[string]ListenerUpdate{
|
||||||
|
v3LDSTarget: {RouteConfigName: v3RouteConfigName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple listener resources",
|
||||||
|
resources: []*anypb.Any{v2Lis, v3Lis},
|
||||||
|
wantUpdate: map[string]ListenerUpdate{
|
||||||
|
v2LDSTarget: {RouteConfigName: v2RouteConfigName},
|
||||||
|
v3LDSTarget: {RouteConfigName: v3RouteConfigName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
update, err := UnmarshalListener(test.resources, nil)
|
||||||
|
if ((err != nil) != test.wantErr) || !cmp.Equal(update, test.wantUpdate, cmpopts.EquateEmpty()) {
|
||||||
|
t.Errorf("UnmarshalListener(%v) = (%v, %v) want (%v, %v)", test.resources, update, err, test.wantUpdate, test.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,854 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
|
v2routepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
|
||||||
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
|
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||||
|
v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
|
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
|
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s) TestGetRouteConfigFromListener(t *testing.T) {
|
||||||
|
const (
|
||||||
|
goodLDSTarget = "lds.target.good:1111"
|
||||||
|
goodRouteConfigName = "GoodRouteConfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
lis *v3listenerpb.Listener
|
||||||
|
wantRoute string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no-apiListener-field",
|
||||||
|
lis: &v3listenerpb.Listener{},
|
||||||
|
wantRoute: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "badly-marshaled-apiListener",
|
||||||
|
lis: &v3listenerpb.Listener{
|
||||||
|
Name: goodLDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V3HTTPConnManagerURL,
|
||||||
|
Value: []byte{1, 2, 3, 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRoute: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong-type-in-apiListener",
|
||||||
|
lis: &v3listenerpb.Listener{
|
||||||
|
Name: goodLDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V2ListenerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cm := &v3httppb.HttpConnectionManager{
|
||||||
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
|
||||||
|
Rds: &v3httppb.Rds{
|
||||||
|
ConfigSource: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
|
||||||
|
},
|
||||||
|
RouteConfigName: goodRouteConfigName}}}
|
||||||
|
mcm, _ := proto.Marshal(cm)
|
||||||
|
return mcm
|
||||||
|
}()}}},
|
||||||
|
wantRoute: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty-httpConnMgr-in-apiListener",
|
||||||
|
lis: &v3listenerpb.Listener{
|
||||||
|
Name: goodLDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V3HTTPConnManagerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cm := &v3httppb.HttpConnectionManager{
|
||||||
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
|
||||||
|
Rds: &v3httppb.Rds{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mcm, _ := proto.Marshal(cm)
|
||||||
|
return mcm
|
||||||
|
}()}}},
|
||||||
|
wantRoute: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scopedRoutes-routeConfig-in-apiListener",
|
||||||
|
lis: &v3listenerpb.Listener{
|
||||||
|
Name: goodLDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V3HTTPConnManagerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cm := &v3httppb.HttpConnectionManager{
|
||||||
|
RouteSpecifier: &v3httppb.HttpConnectionManager_ScopedRoutes{},
|
||||||
|
}
|
||||||
|
mcm, _ := proto.Marshal(cm)
|
||||||
|
return mcm
|
||||||
|
}()}}},
|
||||||
|
wantRoute: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rds.ConfigSource-in-apiListener-is-not-ADS",
|
||||||
|
lis: &v3listenerpb.Listener{
|
||||||
|
Name: goodLDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V3HTTPConnManagerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cm := &v3httppb.HttpConnectionManager{
|
||||||
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
|
||||||
|
Rds: &v3httppb.Rds{
|
||||||
|
ConfigSource: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Path{
|
||||||
|
Path: "/some/path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RouteConfigName: goodRouteConfigName}}}
|
||||||
|
mcm, _ := proto.Marshal(cm)
|
||||||
|
return mcm
|
||||||
|
}()}}},
|
||||||
|
wantRoute: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodListener",
|
||||||
|
lis: &v3listenerpb.Listener{
|
||||||
|
Name: goodLDSTarget,
|
||||||
|
ApiListener: &v3listenerpb.ApiListener{
|
||||||
|
ApiListener: &anypb.Any{
|
||||||
|
TypeUrl: version.V3HTTPConnManagerURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
cm := &v3httppb.HttpConnectionManager{
|
||||||
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
|
||||||
|
Rds: &v3httppb.Rds{
|
||||||
|
ConfigSource: &v3corepb.ConfigSource{
|
||||||
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
|
||||||
|
},
|
||||||
|
RouteConfigName: goodRouteConfigName}}}
|
||||||
|
mcm, _ := proto.Marshal(cm)
|
||||||
|
return mcm
|
||||||
|
}()}}},
|
||||||
|
wantRoute: goodRouteConfigName,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
gotRoute, err := getRouteConfigNameFromListener(test.lis, nil)
|
||||||
|
if (err != nil) != test.wantErr || gotRoute != test.wantRoute {
|
||||||
|
t.Errorf("getRouteConfigNameFromListener(%+v) = (%s, %v), want (%s, %v)", test.lis, gotRoute, err, test.wantRoute, test.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) {
|
||||||
|
const (
|
||||||
|
uninterestingDomain = "uninteresting.domain"
|
||||||
|
uninterestingClusterName = "uninterestingClusterName"
|
||||||
|
ldsTarget = "lds.target.good:1111"
|
||||||
|
routeName = "routeName"
|
||||||
|
clusterName = "clusterName"
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rc *v3routepb.RouteConfiguration
|
||||||
|
wantUpdate RouteConfigUpdate
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no-virtual-hosts-in-rc",
|
||||||
|
rc: &v3routepb.RouteConfiguration{},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-domains-in-rc",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{{}},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-matching-domain-in-rc",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{Domains: []string{uninterestingDomain}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-routes-in-rc",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{Domains: []string{ldsTarget}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default-route-match-field-is-nil",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default-route-match-field-is-non-nil",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{},
|
||||||
|
Action: &v3routepb.Route_Route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default-route-routeaction-field-is-nil",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default-route-cluster-field-is-empty",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_ClusterHeader{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// default route's match sets case-sensitive to false.
|
||||||
|
name: "good-route-config-but-with-casesensitive-false",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
Name: routeName,
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{{
|
||||||
|
Match: &v3routepb.RouteMatch{
|
||||||
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
|
||||||
|
CaseSensitive: &wrapperspb.BoolValue{Value: false},
|
||||||
|
},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
|
||||||
|
}}}}}}},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good-route-config-with-empty-string-route",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
Name: routeName,
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{uninterestingDomain},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: ""}},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: uninterestingClusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: ""}},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{clusterName: 1}}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// default route's match is not empty string, but "/".
|
||||||
|
name: "good-route-config-with-slash-string-route",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
Name: routeName,
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP("/"), Action: map[string]uint32{clusterName: 1}}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// weights not add up to total-weight.
|
||||||
|
name: "route-config-with-weighted_clusters_weights_not_add_up",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
Name: routeName,
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{
|
||||||
|
WeightedClusters: &v3routepb.WeightedCluster{
|
||||||
|
Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
|
||||||
|
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 2}},
|
||||||
|
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 3}},
|
||||||
|
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 5}},
|
||||||
|
},
|
||||||
|
TotalWeight: &wrapperspb.UInt32Value{Value: 30},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good-route-config-with-weighted_clusters",
|
||||||
|
rc: &v3routepb.RouteConfiguration{
|
||||||
|
Name: routeName,
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{
|
||||||
|
WeightedClusters: &v3routepb.WeightedCluster{
|
||||||
|
Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
|
||||||
|
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 2}},
|
||||||
|
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 3}},
|
||||||
|
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 5}},
|
||||||
|
},
|
||||||
|
TotalWeight: &wrapperspb.UInt32Value{Value: 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantUpdate: RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP("/"), Action: map[string]uint32{"a": 2, "b": 3, "c": 5}}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
gotUpdate, gotError := generateRDSUpdateFromRouteConfiguration(test.rc, ldsTarget, nil)
|
||||||
|
if (gotError != nil) != test.wantError || !cmp.Equal(gotUpdate, test.wantUpdate, cmpopts.EquateEmpty()) {
|
||||||
|
t.Errorf("generateRDSUpdateFromRouteConfiguration(%+v, %v) = %v, want %v", test.rc, ldsTarget, gotUpdate, test.wantUpdate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestUnmarshalRouteConfig(t *testing.T) {
|
||||||
|
const (
|
||||||
|
ldsTarget = "lds.target.good:1111"
|
||||||
|
uninterestingDomain = "uninteresting.domain"
|
||||||
|
uninterestingClusterName = "uninterestingClusterName"
|
||||||
|
v2RouteConfigName = "v2RouteConfig"
|
||||||
|
v3RouteConfigName = "v3RouteConfig"
|
||||||
|
v2ClusterName = "v2Cluster"
|
||||||
|
v3ClusterName = "v3Cluster"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
v2VirtualHost = []*v2routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{uninterestingDomain},
|
||||||
|
Routes: []*v2routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v2routepb.RouteMatch{PathSpecifier: &v2routepb.RouteMatch_Prefix{Prefix: ""}},
|
||||||
|
Action: &v2routepb.Route_Route{
|
||||||
|
Route: &v2routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v2routepb.RouteAction_Cluster{Cluster: uninterestingClusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v2routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v2routepb.RouteMatch{PathSpecifier: &v2routepb.RouteMatch_Prefix{Prefix: ""}},
|
||||||
|
Action: &v2routepb.Route_Route{
|
||||||
|
Route: &v2routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v2routepb.RouteAction_Cluster{Cluster: v2ClusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v2RouteConfig = &anypb.Any{
|
||||||
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
rc := &v2xdspb.RouteConfiguration{
|
||||||
|
Name: v2RouteConfigName,
|
||||||
|
VirtualHosts: v2VirtualHost,
|
||||||
|
}
|
||||||
|
m, _ := proto.Marshal(rc)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
v3VirtualHost = []*v3routepb.VirtualHost{
|
||||||
|
{
|
||||||
|
Domains: []string{uninterestingDomain},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: ""}},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: uninterestingClusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: []string{ldsTarget},
|
||||||
|
Routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: ""}},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: v3ClusterName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v3RouteConfig = &anypb.Any{
|
||||||
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
rc := &v3routepb.RouteConfiguration{
|
||||||
|
Name: v3RouteConfigName,
|
||||||
|
VirtualHosts: v3VirtualHost,
|
||||||
|
}
|
||||||
|
m, _ := proto.Marshal(rc)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
resources []*anypb.Any
|
||||||
|
wantUpdate map[string]RouteConfigUpdate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-routeConfig resource type",
|
||||||
|
resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "badly marshaled routeconfig resource",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3RouteConfigURL,
|
||||||
|
Value: []byte{1, 2, 3, 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad routeConfig resource",
|
||||||
|
resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V3RouteConfigURL,
|
||||||
|
Value: func() []byte {
|
||||||
|
rc := &v3routepb.RouteConfiguration{
|
||||||
|
VirtualHosts: []*v3routepb.VirtualHost{
|
||||||
|
{Domains: []string{uninterestingDomain}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
m, _ := proto.Marshal(rc)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty resource list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v2 routeConfig resource",
|
||||||
|
resources: []*anypb.Any{v2RouteConfig},
|
||||||
|
wantUpdate: map[string]RouteConfigUpdate{
|
||||||
|
v2RouteConfigName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{v2ClusterName: 1}}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v3 routeConfig resource",
|
||||||
|
resources: []*anypb.Any{v3RouteConfig},
|
||||||
|
wantUpdate: map[string]RouteConfigUpdate{
|
||||||
|
v3RouteConfigName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{v3ClusterName: 1}}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple routeConfig resources",
|
||||||
|
resources: []*anypb.Any{v2RouteConfig, v3RouteConfig},
|
||||||
|
wantUpdate: map[string]RouteConfigUpdate{
|
||||||
|
v3RouteConfigName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{v3ClusterName: 1}}}},
|
||||||
|
v2RouteConfigName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{v2ClusterName: 1}}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
update, err := UnmarshalRouteConfig(test.resources, ldsTarget, nil)
|
||||||
|
if ((err != nil) != test.wantErr) || !cmp.Equal(update, test.wantUpdate, cmpopts.EquateEmpty()) {
|
||||||
|
t.Errorf("UnmarshalRouteConfig(%v, %v) = (%v, %v) want (%v, %v)", test.resources, ldsTarget, update, err, test.wantUpdate, test.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestMatchTypeForDomain(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
d string
|
||||||
|
want domainMatchType
|
||||||
|
}{
|
||||||
|
{d: "", want: domainMatchTypeInvalid},
|
||||||
|
{d: "*", want: domainMatchTypeUniversal},
|
||||||
|
{d: "bar.*", want: domainMatchTypePrefix},
|
||||||
|
{d: "*.abc.com", want: domainMatchTypeSuffix},
|
||||||
|
{d: "foo.bar.com", want: domainMatchTypeExact},
|
||||||
|
{d: "foo.*.com", want: domainMatchTypeInvalid},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := matchTypeForDomain(tt.d); got != tt.want {
|
||||||
|
t.Errorf("matchTypeForDomain(%q) = %v, want %v", tt.d, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestMatch(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
domain string
|
||||||
|
host string
|
||||||
|
wantTyp domainMatchType
|
||||||
|
wantMatched bool
|
||||||
|
}{
|
||||||
|
{name: "invalid-empty", domain: "", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false},
|
||||||
|
{name: "invalid", domain: "a.*.b", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false},
|
||||||
|
{name: "universal", domain: "*", host: "abc.com", wantTyp: domainMatchTypeUniversal, wantMatched: true},
|
||||||
|
{name: "prefix-match", domain: "abc.*", host: "abc.123", wantTyp: domainMatchTypePrefix, wantMatched: true},
|
||||||
|
{name: "prefix-no-match", domain: "abc.*", host: "abcd.123", wantTyp: domainMatchTypePrefix, wantMatched: false},
|
||||||
|
{name: "suffix-match", domain: "*.123", host: "abc.123", wantTyp: domainMatchTypeSuffix, wantMatched: true},
|
||||||
|
{name: "suffix-no-match", domain: "*.123", host: "abc.1234", wantTyp: domainMatchTypeSuffix, wantMatched: false},
|
||||||
|
{name: "exact-match", domain: "foo.bar", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: true},
|
||||||
|
{name: "exact-no-match", domain: "foo.bar.com", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if gotTyp, gotMatched := match(tt.domain, tt.host); gotTyp != tt.wantTyp || gotMatched != tt.wantMatched {
|
||||||
|
t.Errorf("match() = %v, %v, want %v, %v", gotTyp, gotMatched, tt.wantTyp, tt.wantMatched)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestFindBestMatchingVirtualHost(t *testing.T) {
|
||||||
|
var (
|
||||||
|
oneExactMatch = &v3routepb.VirtualHost{
|
||||||
|
Name: "one-exact-match",
|
||||||
|
Domains: []string{"foo.bar.com"},
|
||||||
|
}
|
||||||
|
oneSuffixMatch = &v3routepb.VirtualHost{
|
||||||
|
Name: "one-suffix-match",
|
||||||
|
Domains: []string{"*.bar.com"},
|
||||||
|
}
|
||||||
|
onePrefixMatch = &v3routepb.VirtualHost{
|
||||||
|
Name: "one-prefix-match",
|
||||||
|
Domains: []string{"foo.bar.*"},
|
||||||
|
}
|
||||||
|
oneUniversalMatch = &v3routepb.VirtualHost{
|
||||||
|
Name: "one-universal-match",
|
||||||
|
Domains: []string{"*"},
|
||||||
|
}
|
||||||
|
longExactMatch = &v3routepb.VirtualHost{
|
||||||
|
Name: "one-exact-match",
|
||||||
|
Domains: []string{"v2.foo.bar.com"},
|
||||||
|
}
|
||||||
|
multipleMatch = &v3routepb.VirtualHost{
|
||||||
|
Name: "multiple-match",
|
||||||
|
Domains: []string{"pi.foo.bar.com", "314.*", "*.159"},
|
||||||
|
}
|
||||||
|
vhs = []*v3routepb.VirtualHost{oneExactMatch, oneSuffixMatch, onePrefixMatch, oneUniversalMatch, longExactMatch, multipleMatch}
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
host string
|
||||||
|
vHosts []*v3routepb.VirtualHost
|
||||||
|
want *v3routepb.VirtualHost
|
||||||
|
}{
|
||||||
|
{name: "exact-match", host: "foo.bar.com", vHosts: vhs, want: oneExactMatch},
|
||||||
|
{name: "suffix-match", host: "123.bar.com", vHosts: vhs, want: oneSuffixMatch},
|
||||||
|
{name: "prefix-match", host: "foo.bar.org", vHosts: vhs, want: onePrefixMatch},
|
||||||
|
{name: "universal-match", host: "abc.123", vHosts: vhs, want: oneUniversalMatch},
|
||||||
|
{name: "long-exact-match", host: "v2.foo.bar.com", vHosts: vhs, want: longExactMatch},
|
||||||
|
// Matches suffix "*.bar.com" and exact "pi.foo.bar.com". Takes exact.
|
||||||
|
{name: "multiple-match-exact", host: "pi.foo.bar.com", vHosts: vhs, want: multipleMatch},
|
||||||
|
// Matches suffix "*.159" and prefix "foo.bar.*". Takes suffix.
|
||||||
|
{name: "multiple-match-suffix", host: "foo.bar.159", vHosts: vhs, want: multipleMatch},
|
||||||
|
// Matches suffix "*.bar.com" and prefix "314.*". Takes suffix.
|
||||||
|
{name: "multiple-match-prefix", host: "314.bar.com", vHosts: vhs, want: oneSuffixMatch},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := findBestMatchingVirtualHost(tt.host, tt.vHosts); !cmp.Equal(got, tt.want, cmp.Comparer(proto.Equal)) {
|
||||||
|
t.Errorf("findBestMatchingVirtualHost() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestRoutesProtoToSlice(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
routes []*v3routepb.Route
|
||||||
|
wantRoutes []*Route
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no path",
|
||||||
|
routes: []*v3routepb.Route{{
|
||||||
|
Match: &v3routepb.RouteMatch{},
|
||||||
|
}},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case_sensitive is false",
|
||||||
|
routes: []*v3routepb.Route{{
|
||||||
|
Match: &v3routepb.RouteMatch{
|
||||||
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
|
||||||
|
CaseSensitive: &wrapperspb.BoolValue{Value: false},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good",
|
||||||
|
routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{
|
||||||
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/a/"},
|
||||||
|
Headers: []*v3routepb.HeaderMatcher{
|
||||||
|
{
|
||||||
|
Name: "th",
|
||||||
|
HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{
|
||||||
|
PrefixMatch: "tv",
|
||||||
|
},
|
||||||
|
InvertMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RuntimeFraction: &v3corepb.RuntimeFractionalPercent{
|
||||||
|
DefaultValue: &v3typepb.FractionalPercent{
|
||||||
|
Numerator: 1,
|
||||||
|
Denominator: v3typepb.FractionalPercent_HUNDRED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{
|
||||||
|
WeightedClusters: &v3routepb.WeightedCluster{
|
||||||
|
Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
|
||||||
|
{Name: "B", Weight: &wrapperspb.UInt32Value{Value: 60}},
|
||||||
|
{Name: "A", Weight: &wrapperspb.UInt32Value{Value: 40}},
|
||||||
|
},
|
||||||
|
TotalWeight: &wrapperspb.UInt32Value{Value: 100},
|
||||||
|
}}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRoutes: []*Route{{
|
||||||
|
Prefix: newStringP("/a/"),
|
||||||
|
Headers: []*HeaderMatcher{
|
||||||
|
{
|
||||||
|
Name: "th",
|
||||||
|
InvertMatch: newBoolP(true),
|
||||||
|
PrefixMatch: newStringP("tv"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Fraction: newUInt32P(10000),
|
||||||
|
Action: map[string]uint32{"A": 40, "B": 60},
|
||||||
|
}},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "query is ignored",
|
||||||
|
routes: []*v3routepb.Route{
|
||||||
|
{
|
||||||
|
Match: &v3routepb.RouteMatch{
|
||||||
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/a/"},
|
||||||
|
},
|
||||||
|
Action: &v3routepb.Route_Route{
|
||||||
|
Route: &v3routepb.RouteAction{
|
||||||
|
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{
|
||||||
|
WeightedClusters: &v3routepb.WeightedCluster{
|
||||||
|
Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
|
||||||
|
{Name: "B", Weight: &wrapperspb.UInt32Value{Value: 60}},
|
||||||
|
{Name: "A", Weight: &wrapperspb.UInt32Value{Value: 40}},
|
||||||
|
},
|
||||||
|
TotalWeight: &wrapperspb.UInt32Value{Value: 100},
|
||||||
|
}}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with_query",
|
||||||
|
Match: &v3routepb.RouteMatch{
|
||||||
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/b/"},
|
||||||
|
QueryParameters: []*v3routepb.QueryParameterMatcher{{Name: "route_will_be_ignored"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Only one route in the result, because the second one with query
|
||||||
|
// parameters is ignored.
|
||||||
|
wantRoutes: []*Route{{
|
||||||
|
Prefix: newStringP("/a/"),
|
||||||
|
Action: map[string]uint32{"A": 40, "B": 60},
|
||||||
|
}},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmpOpts := []cmp.Option{
|
||||||
|
cmp.AllowUnexported(Route{}, HeaderMatcher{}, Int64Range{}),
|
||||||
|
cmpopts.EquateEmpty(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := routesProtoToSlice(tt.routes, nil)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("routesProtoToSlice() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cmp.Equal(got, tt.wantRoutes, cmpOpts...) {
|
||||||
|
t.Errorf("routesProtoToSlice() got = %v, want %v, diff: %v", got, tt.wantRoutes, cmp.Diff(got, tt.wantRoutes, cmpOpts...))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStringP(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUInt32P(i uint32) *uint32 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBoolP(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
@ -23,13 +23,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/internal/grpclog"
|
|
||||||
"google.golang.org/grpc/internal/grpctest"
|
"google.golang.org/grpc/internal/grpctest"
|
||||||
"google.golang.org/grpc/xds/internal/client/bootstrap"
|
"google.golang.org/grpc/xds/internal/client/bootstrap"
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type s struct {
|
type s struct {
|
||||||
|
|
@ -60,119 +57,56 @@ func clientOpts(balancerName string) Options {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s) TestNew(t *testing.T) {
|
type testAPIClient struct {
|
||||||
fakeServer, cleanup, err := fakeserver.StartServer()
|
r UpdateHandler
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to start fake xDS server: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
opts Options
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{name: "empty-opts", opts: Options{}, wantErr: true},
|
|
||||||
{
|
|
||||||
name: "empty-balancer-name",
|
|
||||||
opts: Options{
|
|
||||||
Config: bootstrap.Config{
|
|
||||||
Creds: grpc.WithInsecure(),
|
|
||||||
NodeProto: testutils.EmptyNodeProtoV2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty-dial-creds",
|
|
||||||
opts: Options{
|
|
||||||
Config: bootstrap.Config{
|
|
||||||
BalancerName: "dummy",
|
|
||||||
NodeProto: testutils.EmptyNodeProtoV2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty-node-proto",
|
|
||||||
opts: Options{
|
|
||||||
Config: bootstrap.Config{
|
|
||||||
BalancerName: "dummy",
|
|
||||||
Creds: grpc.WithInsecure(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy-case",
|
|
||||||
opts: clientOpts(fakeServer.Address),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
c, err := New(test.opts)
|
|
||||||
if err == nil {
|
|
||||||
defer c.Close()
|
|
||||||
}
|
|
||||||
if (err != nil) != test.wantErr {
|
|
||||||
t.Fatalf("New(%+v) = %v, wantErr: %v", test.opts, err, test.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testXDSV2Client struct {
|
|
||||||
r updateHandler
|
|
||||||
|
|
||||||
addWatches map[string]*testutils.Channel
|
addWatches map[string]*testutils.Channel
|
||||||
removeWatches map[string]*testutils.Channel
|
removeWatches map[string]*testutils.Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
func overrideNewXDSV2Client() (<-chan *testXDSV2Client, func()) {
|
func overrideNewAPIClient() (<-chan *testAPIClient, func()) {
|
||||||
oldNewXDSV2Client := newXDSV2Client
|
origNewAPIClient := newAPIClient
|
||||||
ch := make(chan *testXDSV2Client, 1)
|
ch := make(chan *testAPIClient, 1)
|
||||||
newXDSV2Client = func(parent *Client, cc *grpc.ClientConn, nodeProto *corepb.Node, backoff func(int) time.Duration, logger *grpclog.PrefixLogger) xdsv2Client {
|
newAPIClient = func(apiVersion version.TransportAPI, cc *grpc.ClientConn, opts BuildOptions) (APIClient, error) {
|
||||||
ret := newTestXDSV2Client(parent)
|
ret := newTestAPIClient(opts.Parent)
|
||||||
ch <- ret
|
ch <- ret
|
||||||
return ret
|
return ret, nil
|
||||||
}
|
}
|
||||||
return ch, func() { newXDSV2Client = oldNewXDSV2Client }
|
return ch, func() { newAPIClient = origNewAPIClient }
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestXDSV2Client(r updateHandler) *testXDSV2Client {
|
func newTestAPIClient(r UpdateHandler) *testAPIClient {
|
||||||
addWatches := make(map[string]*testutils.Channel)
|
addWatches := make(map[string]*testutils.Channel)
|
||||||
addWatches[ldsURL] = testutils.NewChannel()
|
addWatches[version.V2ListenerURL] = testutils.NewChannel()
|
||||||
addWatches[rdsURL] = testutils.NewChannel()
|
addWatches[version.V2RouteConfigURL] = testutils.NewChannel()
|
||||||
addWatches[cdsURL] = testutils.NewChannel()
|
addWatches[version.V2ClusterURL] = testutils.NewChannel()
|
||||||
addWatches[edsURL] = testutils.NewChannel()
|
addWatches[version.V2EndpointsURL] = testutils.NewChannel()
|
||||||
removeWatches := make(map[string]*testutils.Channel)
|
removeWatches := make(map[string]*testutils.Channel)
|
||||||
removeWatches[ldsURL] = testutils.NewChannel()
|
removeWatches[version.V2ListenerURL] = testutils.NewChannel()
|
||||||
removeWatches[rdsURL] = testutils.NewChannel()
|
removeWatches[version.V2RouteConfigURL] = testutils.NewChannel()
|
||||||
removeWatches[cdsURL] = testutils.NewChannel()
|
removeWatches[version.V2ClusterURL] = testutils.NewChannel()
|
||||||
removeWatches[edsURL] = testutils.NewChannel()
|
removeWatches[version.V2EndpointsURL] = testutils.NewChannel()
|
||||||
return &testXDSV2Client{
|
return &testAPIClient{
|
||||||
r: r,
|
r: r,
|
||||||
addWatches: addWatches,
|
addWatches: addWatches,
|
||||||
removeWatches: removeWatches,
|
removeWatches: removeWatches,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testXDSV2Client) addWatch(resourceType, resourceName string) {
|
func (c *testAPIClient) AddWatch(resourceType, resourceName string) {
|
||||||
c.addWatches[resourceType].Send(resourceName)
|
c.addWatches[resourceType].Send(resourceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testXDSV2Client) removeWatch(resourceType, resourceName string) {
|
func (c *testAPIClient) RemoveWatch(resourceType, resourceName string) {
|
||||||
c.removeWatches[resourceType].Send(resourceName)
|
c.removeWatches[resourceType].Send(resourceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testXDSV2Client) close() {}
|
func (c *testAPIClient) Close() {}
|
||||||
|
|
||||||
// TestWatchCallAnotherWatch covers the case where watch() is called inline by a
|
// TestWatchCallAnotherWatch covers the case where watch() is called inline by a
|
||||||
// callback. It makes sure it doesn't cause a deadlock.
|
// callback. It makes sure it doesn't cause a deadlock.
|
||||||
func (s) TestWatchCallAnotherWatch(t *testing.T) {
|
func (s) TestWatchCallAnotherWatch(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -189,17 +123,17 @@ func (s) TestWatchCallAnotherWatch(t *testing.T) {
|
||||||
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
||||||
// Calls another watch inline, to ensure there's deadlock.
|
// Calls another watch inline, to ensure there's deadlock.
|
||||||
c.WatchCluster("another-random-name", func(ClusterUpdate, error) {})
|
c.WatchCluster("another-random-name", func(ClusterUpdate, error) {})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); firstTime && err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); firstTime && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
firstTime = false
|
firstTime = false
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -208,7 +142,7 @@ func (s) TestWatchCallAnotherWatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate2 := ClusterUpdate{ServiceName: testEDSName + "2"}
|
wantUpdate2 := ClusterUpdate{ServiceName: testEDSName + "2"}
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate2,
|
testCDSName: wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The value chosen here is based on the default value of the
|
// The value chosen here is based on the default value of the
|
||||||
// initial_fetch_timeout field in corepb.ConfigSource proto.
|
// initial_fetch_timeout field in corepb.ConfigSource proto.
|
||||||
var defaultWatchExpiryTimeout = 15 * time.Second
|
var defaultWatchExpiryTimeout = 15 * time.Second
|
||||||
|
|
||||||
const (
|
|
||||||
ldsURL = "type.googleapis.com/envoy.api.v2.Listener"
|
|
||||||
rdsURL = "type.googleapis.com/envoy.api.v2.RouteConfiguration"
|
|
||||||
cdsURL = "type.googleapis.com/envoy.api.v2.Cluster"
|
|
||||||
edsURL = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
|
|
||||||
)
|
|
||||||
|
|
||||||
type watchInfoState int
|
type watchInfoState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -50,8 +45,8 @@ type watchInfo struct {
|
||||||
typeURL string
|
typeURL string
|
||||||
target string
|
target string
|
||||||
|
|
||||||
ldsCallback ldsCallbackFunc
|
ldsCallback func(ListenerUpdate, error)
|
||||||
rdsCallback rdsCallbackFunc
|
rdsCallback func(RouteConfigUpdate, error)
|
||||||
cdsCallback func(ClusterUpdate, error)
|
cdsCallback func(ClusterUpdate, error)
|
||||||
edsCallback func(EndpointsUpdate, error)
|
edsCallback func(EndpointsUpdate, error)
|
||||||
|
|
||||||
|
|
@ -102,13 +97,13 @@ func (wi *watchInfo) sendErrorLocked(err error) {
|
||||||
u interface{}
|
u interface{}
|
||||||
)
|
)
|
||||||
switch wi.typeURL {
|
switch wi.typeURL {
|
||||||
case ldsURL:
|
case version.V2ListenerURL:
|
||||||
u = ldsUpdate{}
|
u = ListenerUpdate{}
|
||||||
case rdsURL:
|
case version.V2RouteConfigURL:
|
||||||
u = rdsUpdate{}
|
u = RouteConfigUpdate{}
|
||||||
case cdsURL:
|
case version.V2ClusterURL:
|
||||||
u = ClusterUpdate{}
|
u = ClusterUpdate{}
|
||||||
case edsURL:
|
case version.V2EndpointsURL:
|
||||||
u = EndpointsUpdate{}
|
u = EndpointsUpdate{}
|
||||||
}
|
}
|
||||||
wi.c.scheduleCallback(wi, u, err)
|
wi.c.scheduleCallback(wi, u, err)
|
||||||
|
|
@ -130,13 +125,13 @@ func (c *Client) watch(wi *watchInfo) (cancel func()) {
|
||||||
c.logger.Debugf("new watch for type %v, resource name %v", wi.typeURL, wi.target)
|
c.logger.Debugf("new watch for type %v, resource name %v", wi.typeURL, wi.target)
|
||||||
var watchers map[string]map[*watchInfo]bool
|
var watchers map[string]map[*watchInfo]bool
|
||||||
switch wi.typeURL {
|
switch wi.typeURL {
|
||||||
case ldsURL:
|
case version.V2ListenerURL:
|
||||||
watchers = c.ldsWatchers
|
watchers = c.ldsWatchers
|
||||||
case rdsURL:
|
case version.V2RouteConfigURL:
|
||||||
watchers = c.rdsWatchers
|
watchers = c.rdsWatchers
|
||||||
case cdsURL:
|
case version.V2ClusterURL:
|
||||||
watchers = c.cdsWatchers
|
watchers = c.cdsWatchers
|
||||||
case edsURL:
|
case version.V2EndpointsURL:
|
||||||
watchers = c.edsWatchers
|
watchers = c.edsWatchers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,7 +146,7 @@ func (c *Client) watch(wi *watchInfo) (cancel func()) {
|
||||||
c.logger.Debugf("first watch for type %v, resource name %v, will send a new xDS request", wi.typeURL, wi.target)
|
c.logger.Debugf("first watch for type %v, resource name %v, will send a new xDS request", wi.typeURL, wi.target)
|
||||||
s = make(map[*watchInfo]bool)
|
s = make(map[*watchInfo]bool)
|
||||||
watchers[resourceName] = s
|
watchers[resourceName] = s
|
||||||
c.v2c.addWatch(wi.typeURL, resourceName)
|
c.apiClient.AddWatch(wi.typeURL, resourceName)
|
||||||
}
|
}
|
||||||
// No matter what, add the new watcher to the set, so it's callback will be
|
// No matter what, add the new watcher to the set, so it's callback will be
|
||||||
// call for new responses.
|
// call for new responses.
|
||||||
|
|
@ -159,22 +154,22 @@ func (c *Client) watch(wi *watchInfo) (cancel func()) {
|
||||||
|
|
||||||
// If the resource is in cache, call the callback with the value.
|
// If the resource is in cache, call the callback with the value.
|
||||||
switch wi.typeURL {
|
switch wi.typeURL {
|
||||||
case ldsURL:
|
case version.V2ListenerURL:
|
||||||
if v, ok := c.ldsCache[resourceName]; ok {
|
if v, ok := c.ldsCache[resourceName]; ok {
|
||||||
c.logger.Debugf("LDS resource with name %v found in cache: %+v", wi.target, v)
|
c.logger.Debugf("LDS resource with name %v found in cache: %+v", wi.target, v)
|
||||||
wi.newUpdate(v)
|
wi.newUpdate(v)
|
||||||
}
|
}
|
||||||
case rdsURL:
|
case version.V2RouteConfigURL:
|
||||||
if v, ok := c.rdsCache[resourceName]; ok {
|
if v, ok := c.rdsCache[resourceName]; ok {
|
||||||
c.logger.Debugf("RDS resource with name %v found in cache: %+v", wi.target, v)
|
c.logger.Debugf("RDS resource with name %v found in cache: %+v", wi.target, v)
|
||||||
wi.newUpdate(v)
|
wi.newUpdate(v)
|
||||||
}
|
}
|
||||||
case cdsURL:
|
case version.V2ClusterURL:
|
||||||
if v, ok := c.cdsCache[resourceName]; ok {
|
if v, ok := c.cdsCache[resourceName]; ok {
|
||||||
c.logger.Debugf("CDS resource with name %v found in cache: %+v", wi.target, v)
|
c.logger.Debugf("CDS resource with name %v found in cache: %+v", wi.target, v)
|
||||||
wi.newUpdate(v)
|
wi.newUpdate(v)
|
||||||
}
|
}
|
||||||
case edsURL:
|
case version.V2EndpointsURL:
|
||||||
if v, ok := c.edsCache[resourceName]; ok {
|
if v, ok := c.edsCache[resourceName]; ok {
|
||||||
c.logger.Debugf("EDS resource with name %v found in cache: %+v", wi.target, v)
|
c.logger.Debugf("EDS resource with name %v found in cache: %+v", wi.target, v)
|
||||||
wi.newUpdate(v)
|
wi.newUpdate(v)
|
||||||
|
|
@ -195,21 +190,207 @@ func (c *Client) watch(wi *watchInfo) (cancel func()) {
|
||||||
// If this was the last watcher, also tell xdsv2Client to stop
|
// If this was the last watcher, also tell xdsv2Client to stop
|
||||||
// watching this resource.
|
// watching this resource.
|
||||||
delete(watchers, resourceName)
|
delete(watchers, resourceName)
|
||||||
c.v2c.removeWatch(wi.typeURL, resourceName)
|
c.apiClient.RemoveWatch(wi.typeURL, resourceName)
|
||||||
// Remove the resource from cache. When a watch for this
|
// Remove the resource from cache. When a watch for this
|
||||||
// resource is added later, it will trigger a xDS request with
|
// resource is added later, it will trigger a xDS request with
|
||||||
// resource names, and client will receive new xDS responses.
|
// resource names, and client will receive new xDS responses.
|
||||||
switch wi.typeURL {
|
switch wi.typeURL {
|
||||||
case ldsURL:
|
case version.V2ListenerURL:
|
||||||
delete(c.ldsCache, resourceName)
|
delete(c.ldsCache, resourceName)
|
||||||
case rdsURL:
|
case version.V2RouteConfigURL:
|
||||||
delete(c.rdsCache, resourceName)
|
delete(c.rdsCache, resourceName)
|
||||||
case cdsURL:
|
case version.V2ClusterURL:
|
||||||
delete(c.cdsCache, resourceName)
|
delete(c.cdsCache, resourceName)
|
||||||
case edsURL:
|
case version.V2EndpointsURL:
|
||||||
delete(c.edsCache, resourceName)
|
delete(c.edsCache, resourceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// watchLDS starts a listener watcher for the service..
|
||||||
|
//
|
||||||
|
// Note that during race (e.g. an xDS response is received while the user is
|
||||||
|
// calling cancel()), there's a small window where the callback can be called
|
||||||
|
// after the watcher is canceled. The caller needs to handle this case.
|
||||||
|
func (c *Client) watchLDS(serviceName string, cb func(ListenerUpdate, error)) (cancel func()) {
|
||||||
|
wi := &watchInfo{
|
||||||
|
c: c,
|
||||||
|
typeURL: version.V2ListenerURL,
|
||||||
|
target: serviceName,
|
||||||
|
ldsCallback: cb,
|
||||||
|
}
|
||||||
|
|
||||||
|
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
||||||
|
wi.timeout()
|
||||||
|
})
|
||||||
|
return c.watch(wi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchRDS starts a listener watcher for the service..
|
||||||
|
//
|
||||||
|
// Note that during race (e.g. an xDS response is received while the user is
|
||||||
|
// calling cancel()), there's a small window where the callback can be called
|
||||||
|
// after the watcher is canceled. The caller needs to handle this case.
|
||||||
|
func (c *Client) watchRDS(routeName string, cb func(RouteConfigUpdate, error)) (cancel func()) {
|
||||||
|
wi := &watchInfo{
|
||||||
|
c: c,
|
||||||
|
typeURL: version.V2RouteConfigURL,
|
||||||
|
target: routeName,
|
||||||
|
rdsCallback: cb,
|
||||||
|
}
|
||||||
|
|
||||||
|
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
||||||
|
wi.timeout()
|
||||||
|
})
|
||||||
|
return c.watch(wi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchService uses LDS and RDS to discover information about the provided
|
||||||
|
// serviceName.
|
||||||
|
//
|
||||||
|
// WatchService can only be called once. The second call will not start a
|
||||||
|
// watcher and the callback will get an error. It's this case because an xDS
|
||||||
|
// client is expected to be used only by one ClientConn.
|
||||||
|
//
|
||||||
|
// Note that during race (e.g. an xDS response is received while the user is
|
||||||
|
// calling cancel()), there's a small window where the callback can be called
|
||||||
|
// after the watcher is canceled. The caller needs to handle this case.
|
||||||
|
func (c *Client) WatchService(serviceName string, cb func(ServiceUpdate, error)) (cancel func()) {
|
||||||
|
c.mu.Lock()
|
||||||
|
if len(c.ldsWatchers) != 0 {
|
||||||
|
go cb(ServiceUpdate{}, fmt.Errorf("unexpected WatchService when there's another service being watched"))
|
||||||
|
c.mu.Unlock()
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
w := &serviceUpdateWatcher{c: c, serviceCb: cb}
|
||||||
|
w.ldsCancel = c.watchLDS(serviceName, w.handleLDSResp)
|
||||||
|
|
||||||
|
return w.close
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceUpdateWatcher handles LDS and RDS response, and calls the service
|
||||||
|
// callback at the right time.
|
||||||
|
type serviceUpdateWatcher struct {
|
||||||
|
c *Client
|
||||||
|
ldsCancel func()
|
||||||
|
serviceCb func(ServiceUpdate, error)
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
rdsName string
|
||||||
|
rdsCancel func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *serviceUpdateWatcher) handleLDSResp(update ListenerUpdate, err error) {
|
||||||
|
w.c.logger.Infof("xds: client received LDS update: %+v, err: %v", update, err)
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if w.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// We check the error type and do different things. For now, the only
|
||||||
|
// type we check is ResourceNotFound, which indicates the LDS resource
|
||||||
|
// was removed, and besides sending the error to callback, we also
|
||||||
|
// cancel the RDS watch.
|
||||||
|
if ErrType(err) == ErrorTypeResourceNotFound && w.rdsCancel != nil {
|
||||||
|
w.rdsCancel()
|
||||||
|
w.rdsName = ""
|
||||||
|
w.rdsCancel = nil
|
||||||
|
}
|
||||||
|
// The other error cases still return early without canceling the
|
||||||
|
// existing RDS watch.
|
||||||
|
w.serviceCb(ServiceUpdate{}, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.rdsName == update.RouteConfigName {
|
||||||
|
// If the new RouteConfigName is same as the previous, don't cancel and
|
||||||
|
// restart the RDS watch.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.rdsName = update.RouteConfigName
|
||||||
|
if w.rdsCancel != nil {
|
||||||
|
w.rdsCancel()
|
||||||
|
}
|
||||||
|
w.rdsCancel = w.c.watchRDS(update.RouteConfigName, w.handleRDSResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *serviceUpdateWatcher) handleRDSResp(update RouteConfigUpdate, err error) {
|
||||||
|
w.c.logger.Infof("xds: client received RDS update: %+v, err: %v", update, err)
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if w.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.rdsCancel == nil {
|
||||||
|
// This mean only the RDS watch is canceled, can happen if the LDS
|
||||||
|
// resource is removed.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
w.serviceCb(ServiceUpdate{}, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.serviceCb(ServiceUpdate(update), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *serviceUpdateWatcher) close() {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
w.closed = true
|
||||||
|
w.ldsCancel()
|
||||||
|
if w.rdsCancel != nil {
|
||||||
|
w.rdsCancel()
|
||||||
|
w.rdsCancel = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchCluster uses CDS to discover information about the provided
|
||||||
|
// clusterName.
|
||||||
|
//
|
||||||
|
// WatchCluster can be called multiple times, with same or different
|
||||||
|
// clusterNames. Each call will start an independent watcher for the resource.
|
||||||
|
//
|
||||||
|
// Note that during race (e.g. an xDS response is received while the user is
|
||||||
|
// calling cancel()), there's a small window where the callback can be called
|
||||||
|
// after the watcher is canceled. The caller needs to handle this case.
|
||||||
|
func (c *Client) WatchCluster(clusterName string, cb func(ClusterUpdate, error)) (cancel func()) {
|
||||||
|
wi := &watchInfo{
|
||||||
|
c: c,
|
||||||
|
typeURL: version.V2ClusterURL,
|
||||||
|
target: clusterName,
|
||||||
|
cdsCallback: cb,
|
||||||
|
}
|
||||||
|
|
||||||
|
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
||||||
|
wi.timeout()
|
||||||
|
})
|
||||||
|
return c.watch(wi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchEndpoints uses EDS to discover endpoints in the provided clusterName.
|
||||||
|
//
|
||||||
|
// WatchEndpoints can be called multiple times, with same or different
|
||||||
|
// clusterNames. Each call will start an independent watcher for the resource.
|
||||||
|
//
|
||||||
|
// Note that during race (e.g. an xDS response is received while the user is
|
||||||
|
// calling cancel()), there's a small window where the callback can be called
|
||||||
|
// after the watcher is canceled. The caller needs to handle this case.
|
||||||
|
func (c *Client) WatchEndpoints(clusterName string, cb func(EndpointsUpdate, error)) (cancel func()) {
|
||||||
|
wi := &watchInfo{
|
||||||
|
c: c,
|
||||||
|
typeURL: version.V2EndpointsURL,
|
||||||
|
target: clusterName,
|
||||||
|
edsCallback: cb,
|
||||||
|
}
|
||||||
|
|
||||||
|
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
||||||
|
wi.timeout()
|
||||||
|
})
|
||||||
|
return c.watch(wi)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright 2020 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 (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClusterUpdate contains information from a received CDS response, which is of
|
|
||||||
// interest to the registered CDS watcher.
|
|
||||||
type ClusterUpdate struct {
|
|
||||||
// ServiceName is the service name corresponding to the clusterName which
|
|
||||||
// is being watched for through CDS.
|
|
||||||
ServiceName string
|
|
||||||
// EnableLRS indicates whether or not load should be reported through LRS.
|
|
||||||
EnableLRS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchCluster uses CDS to discover information about the provided
|
|
||||||
// clusterName.
|
|
||||||
//
|
|
||||||
// WatchCluster can be called multiple times, with same or different
|
|
||||||
// clusterNames. Each call will start an independent watcher for the resource.
|
|
||||||
//
|
|
||||||
// Note that during race (e.g. an xDS response is received while the user is
|
|
||||||
// calling cancel()), there's a small window where the callback can be called
|
|
||||||
// after the watcher is canceled. The caller needs to handle this case.
|
|
||||||
func (c *Client) WatchCluster(clusterName string, cb func(ClusterUpdate, error)) (cancel func()) {
|
|
||||||
wi := &watchInfo{
|
|
||||||
c: c,
|
|
||||||
typeURL: cdsURL,
|
|
||||||
target: clusterName,
|
|
||||||
cdsCallback: cb,
|
|
||||||
}
|
|
||||||
|
|
||||||
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
|
||||||
wi.timeout()
|
|
||||||
})
|
|
||||||
return c.watch(wi)
|
|
||||||
}
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type clusterUpdateErr struct {
|
type clusterUpdateErr struct {
|
||||||
|
|
@ -35,7 +36,7 @@ type clusterUpdateErr struct {
|
||||||
// - an update for another resource name
|
// - an update for another resource name
|
||||||
// - an update is received after cancel()
|
// - an update is received after cancel()
|
||||||
func (s) TestClusterWatch(t *testing.T) {
|
func (s) TestClusterWatch(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -53,7 +54,7 @@ func (s) TestClusterWatch(t *testing.T) {
|
||||||
cancelWatch := c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
cancelWatch := c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +65,7 @@ func (s) TestClusterWatch(t *testing.T) {
|
||||||
//
|
//
|
||||||
// TODO: in a future cleanup, this (and the same thing in other tests) can
|
// TODO: in a future cleanup, this (and the same thing in other tests) can
|
||||||
// be changed call Client directly.
|
// be changed call Client directly.
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -73,7 +74,7 @@ func (s) TestClusterWatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another update, with an extra resource for a different resource name.
|
// Another update, with an extra resource for a different resource name.
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
"randomName": {},
|
"randomName": {},
|
||||||
})
|
})
|
||||||
|
|
@ -84,7 +85,7 @@ func (s) TestClusterWatch(t *testing.T) {
|
||||||
|
|
||||||
// Cancel watch, and send update again.
|
// Cancel watch, and send update again.
|
||||||
cancelWatch()
|
cancelWatch()
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -96,7 +97,7 @@ func (s) TestClusterWatch(t *testing.T) {
|
||||||
// TestClusterTwoWatchSameResourceName covers the case where an update is received
|
// TestClusterTwoWatchSameResourceName covers the case where an update is received
|
||||||
// after two watch() for the same resource name.
|
// after two watch() for the same resource name.
|
||||||
func (s) TestClusterTwoWatchSameResourceName(t *testing.T) {
|
func (s) TestClusterTwoWatchSameResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -118,13 +119,13 @@ func (s) TestClusterTwoWatchSameResourceName(t *testing.T) {
|
||||||
cancelLastWatch = c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
cancelLastWatch = c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -136,7 +137,7 @@ func (s) TestClusterTwoWatchSameResourceName(t *testing.T) {
|
||||||
|
|
||||||
// Cancel the last watch, and send update again.
|
// Cancel the last watch, and send update again.
|
||||||
cancelLastWatch()
|
cancelLastWatch()
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -154,7 +155,7 @@ func (s) TestClusterTwoWatchSameResourceName(t *testing.T) {
|
||||||
// TestClusterThreeWatchDifferentResourceName covers the case where an update is
|
// TestClusterThreeWatchDifferentResourceName covers the case where an update is
|
||||||
// received after three watch() for different resource names.
|
// received after three watch() for different resource names.
|
||||||
func (s) TestClusterThreeWatchDifferentResourceName(t *testing.T) {
|
func (s) TestClusterThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -175,7 +176,7 @@ func (s) TestClusterThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName+"1", func(update ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName+"1", func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -185,13 +186,13 @@ func (s) TestClusterThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName+"2", func(update ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName+"2", func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh2.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh2.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate1 := ClusterUpdate{ServiceName: testEDSName + "1"}
|
wantUpdate1 := ClusterUpdate{ServiceName: testEDSName + "1"}
|
||||||
wantUpdate2 := ClusterUpdate{ServiceName: testEDSName + "2"}
|
wantUpdate2 := ClusterUpdate{ServiceName: testEDSName + "2"}
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName + "1": wantUpdate1,
|
testCDSName + "1": wantUpdate1,
|
||||||
testCDSName + "2": wantUpdate2,
|
testCDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
@ -210,7 +211,7 @@ func (s) TestClusterThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
// TestClusterWatchAfterCache covers the case where watch is called after the update
|
// TestClusterWatchAfterCache covers the case where watch is called after the update
|
||||||
// is in cache.
|
// is in cache.
|
||||||
func (s) TestClusterWatchAfterCache(t *testing.T) {
|
func (s) TestClusterWatchAfterCache(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -225,12 +226,12 @@ func (s) TestClusterWatchAfterCache(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -243,7 +244,7 @@ func (s) TestClusterWatchAfterCache(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName, func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh2.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh2.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if n, err := v2Client.addWatches[cdsURL].Receive(); err == nil {
|
if n, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err == nil {
|
||||||
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,7 +269,7 @@ func (s) TestClusterWatchExpiryTimer(t *testing.T) {
|
||||||
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
||||||
}()
|
}()
|
||||||
|
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -283,7 +284,7 @@ func (s) TestClusterWatchExpiryTimer(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName, func(u ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName, func(u ClusterUpdate, err error) {
|
||||||
clusterUpdateCh.Send(clusterUpdateErr{u: u, err: err})
|
clusterUpdateCh.Send(clusterUpdateErr{u: u, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,7 +311,7 @@ func (s) TestClusterWatchExpiryTimerStop(t *testing.T) {
|
||||||
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
||||||
}()
|
}()
|
||||||
|
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -325,12 +326,12 @@ func (s) TestClusterWatchExpiryTimerStop(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName, func(u ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName, func(u ClusterUpdate, err error) {
|
||||||
clusterUpdateCh.Send(clusterUpdateErr{u: u, err: err})
|
clusterUpdateCh.Send(clusterUpdateErr{u: u, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
wantUpdate := ClusterUpdate{ServiceName: testEDSName}
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -352,7 +353,7 @@ func (s) TestClusterWatchExpiryTimerStop(t *testing.T) {
|
||||||
// - one more update without the removed resource
|
// - one more update without the removed resource
|
||||||
// - the callback (above) shouldn't receive any update
|
// - the callback (above) shouldn't receive any update
|
||||||
func (s) TestClusterResourceRemoved(t *testing.T) {
|
func (s) TestClusterResourceRemoved(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -367,7 +368,7 @@ func (s) TestClusterResourceRemoved(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName+"1", func(update ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName+"1", func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh1.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh1.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
// Another watch for a different name.
|
// Another watch for a different name.
|
||||||
|
|
@ -375,13 +376,13 @@ func (s) TestClusterResourceRemoved(t *testing.T) {
|
||||||
c.WatchCluster(testCDSName+"2", func(update ClusterUpdate, err error) {
|
c.WatchCluster(testCDSName+"2", func(update ClusterUpdate, err error) {
|
||||||
clusterUpdateCh2.Send(clusterUpdateErr{u: update, err: err})
|
clusterUpdateCh2.Send(clusterUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[cdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ClusterURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate1 := ClusterUpdate{ServiceName: testEDSName + "1"}
|
wantUpdate1 := ClusterUpdate{ServiceName: testEDSName + "1"}
|
||||||
wantUpdate2 := ClusterUpdate{ServiceName: testEDSName + "2"}
|
wantUpdate2 := ClusterUpdate{ServiceName: testEDSName + "2"}
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName + "1": wantUpdate1,
|
testCDSName + "1": wantUpdate1,
|
||||||
testCDSName + "2": wantUpdate2,
|
testCDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
@ -395,7 +396,7 @@ func (s) TestClusterResourceRemoved(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send another update to remove resource 1.
|
// Send another update to remove resource 1.
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName + "2": wantUpdate2,
|
testCDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -410,7 +411,7 @@ func (s) TestClusterResourceRemoved(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send one more update without resource 1.
|
// Send one more update without resource 1.
|
||||||
v2Client.r.newCDSUpdate(map[string]ClusterUpdate{
|
v2Client.r.NewClusters(map[string]ClusterUpdate{
|
||||||
testCDSName + "2": wantUpdate2,
|
testCDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright 2020 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 (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/xds/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OverloadDropConfig contains the config to drop overloads.
|
|
||||||
type OverloadDropConfig struct {
|
|
||||||
Category string
|
|
||||||
Numerator uint32
|
|
||||||
Denominator uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndpointHealthStatus represents the health status of an endpoint.
|
|
||||||
type EndpointHealthStatus int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
// EndpointHealthStatusUnknown represents HealthStatus UNKNOWN.
|
|
||||||
EndpointHealthStatusUnknown EndpointHealthStatus = iota
|
|
||||||
// EndpointHealthStatusHealthy represents HealthStatus HEALTHY.
|
|
||||||
EndpointHealthStatusHealthy
|
|
||||||
// EndpointHealthStatusUnhealthy represents HealthStatus UNHEALTHY.
|
|
||||||
EndpointHealthStatusUnhealthy
|
|
||||||
// EndpointHealthStatusDraining represents HealthStatus DRAINING.
|
|
||||||
EndpointHealthStatusDraining
|
|
||||||
// EndpointHealthStatusTimeout represents HealthStatus TIMEOUT.
|
|
||||||
EndpointHealthStatusTimeout
|
|
||||||
// EndpointHealthStatusDegraded represents HealthStatus DEGRADED.
|
|
||||||
EndpointHealthStatusDegraded
|
|
||||||
)
|
|
||||||
|
|
||||||
// Endpoint contains information of an endpoint.
|
|
||||||
type Endpoint struct {
|
|
||||||
Address string
|
|
||||||
HealthStatus EndpointHealthStatus
|
|
||||||
Weight uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locality contains information of a locality.
|
|
||||||
type Locality struct {
|
|
||||||
Endpoints []Endpoint
|
|
||||||
ID internal.LocalityID
|
|
||||||
Priority uint32
|
|
||||||
Weight uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndpointsUpdate contains an EDS update.
|
|
||||||
type EndpointsUpdate struct {
|
|
||||||
Drops []OverloadDropConfig
|
|
||||||
Localities []Locality
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchEndpoints uses EDS to discover endpoints in the provided clusterName.
|
|
||||||
//
|
|
||||||
// WatchEndpoints can be called multiple times, with same or different
|
|
||||||
// clusterNames. Each call will start an independent watcher for the resource.
|
|
||||||
//
|
|
||||||
// Note that during race (e.g. an xDS response is received while the user is
|
|
||||||
// calling cancel()), there's a small window where the callback can be called
|
|
||||||
// after the watcher is canceled. The caller needs to handle this case.
|
|
||||||
func (c *Client) WatchEndpoints(clusterName string, cb func(EndpointsUpdate, error)) (cancel func()) {
|
|
||||||
wi := &watchInfo{
|
|
||||||
c: c,
|
|
||||||
typeURL: edsURL,
|
|
||||||
target: clusterName,
|
|
||||||
edsCallback: cb,
|
|
||||||
}
|
|
||||||
|
|
||||||
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
|
||||||
wi.timeout()
|
|
||||||
})
|
|
||||||
return c.watch(wi)
|
|
||||||
}
|
|
||||||
|
|
@ -24,9 +24,9 @@ import (
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
|
||||||
"google.golang.org/grpc/xds/internal"
|
"google.golang.org/grpc/xds/internal"
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -57,7 +57,7 @@ type endpointsUpdateErr struct {
|
||||||
// - an update for another resource name (which doesn't trigger callback)
|
// - an update for another resource name (which doesn't trigger callback)
|
||||||
// - an update is received after cancel()
|
// - an update is received after cancel()
|
||||||
func (s) TestEndpointsWatch(t *testing.T) {
|
func (s) TestEndpointsWatch(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -72,12 +72,12 @@ func (s) TestEndpointsWatch(t *testing.T) {
|
||||||
cancelWatch := c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
cancelWatch := c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
||||||
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[edsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2EndpointsURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
wantUpdate := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
||||||
v2Client.r.newEDSUpdate(map[string]EndpointsUpdate{
|
v2Client.r.NewEndpoints(map[string]EndpointsUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ func (s) TestEndpointsWatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another update for a different resource name.
|
// Another update for a different resource name.
|
||||||
v2Client.r.newEDSUpdate(map[string]EndpointsUpdate{
|
v2Client.r.NewEndpoints(map[string]EndpointsUpdate{
|
||||||
"randomName": {},
|
"randomName": {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ func (s) TestEndpointsWatch(t *testing.T) {
|
||||||
|
|
||||||
// Cancel watch, and send update again.
|
// Cancel watch, and send update again.
|
||||||
cancelWatch()
|
cancelWatch()
|
||||||
v2Client.r.newEDSUpdate(map[string]EndpointsUpdate{
|
v2Client.r.NewEndpoints(map[string]EndpointsUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ func (s) TestEndpointsWatch(t *testing.T) {
|
||||||
// TestEndpointsTwoWatchSameResourceName covers the case where an update is received
|
// TestEndpointsTwoWatchSameResourceName covers the case where an update is received
|
||||||
// after two watch() for the same resource name.
|
// after two watch() for the same resource name.
|
||||||
func (s) TestEndpointsTwoWatchSameResourceName(t *testing.T) {
|
func (s) TestEndpointsTwoWatchSameResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -130,13 +130,13 @@ func (s) TestEndpointsTwoWatchSameResourceName(t *testing.T) {
|
||||||
cancelLastWatch = c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
cancelLastWatch = c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
||||||
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[edsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2EndpointsURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
wantUpdate := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
||||||
v2Client.r.newEDSUpdate(map[string]EndpointsUpdate{
|
v2Client.r.NewEndpoints(map[string]EndpointsUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ func (s) TestEndpointsTwoWatchSameResourceName(t *testing.T) {
|
||||||
|
|
||||||
// Cancel the last watch, and send update again.
|
// Cancel the last watch, and send update again.
|
||||||
cancelLastWatch()
|
cancelLastWatch()
|
||||||
v2Client.r.newEDSUpdate(map[string]EndpointsUpdate{
|
v2Client.r.NewEndpoints(map[string]EndpointsUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -166,7 +166,7 @@ func (s) TestEndpointsTwoWatchSameResourceName(t *testing.T) {
|
||||||
// TestEndpointsThreeWatchDifferentResourceName covers the case where an update is
|
// TestEndpointsThreeWatchDifferentResourceName covers the case where an update is
|
||||||
// received after three watch() for different resource names.
|
// received after three watch() for different resource names.
|
||||||
func (s) TestEndpointsThreeWatchDifferentResourceName(t *testing.T) {
|
func (s) TestEndpointsThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -187,7 +187,7 @@ func (s) TestEndpointsThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
c.WatchEndpoints(testCDSName+"1", func(update EndpointsUpdate, err error) {
|
c.WatchEndpoints(testCDSName+"1", func(update EndpointsUpdate, err error) {
|
||||||
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[edsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2EndpointsURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,13 +197,13 @@ func (s) TestEndpointsThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
c.WatchEndpoints(testCDSName+"2", func(update EndpointsUpdate, err error) {
|
c.WatchEndpoints(testCDSName+"2", func(update EndpointsUpdate, err error) {
|
||||||
endpointsUpdateCh2.Send(endpointsUpdateErr{u: update, err: err})
|
endpointsUpdateCh2.Send(endpointsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[edsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2EndpointsURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate1 := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
wantUpdate1 := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
||||||
wantUpdate2 := EndpointsUpdate{Localities: []Locality{testLocalities[1]}}
|
wantUpdate2 := EndpointsUpdate{Localities: []Locality{testLocalities[1]}}
|
||||||
v2Client.r.newEDSUpdate(map[string]EndpointsUpdate{
|
v2Client.r.NewEndpoints(map[string]EndpointsUpdate{
|
||||||
testCDSName + "1": wantUpdate1,
|
testCDSName + "1": wantUpdate1,
|
||||||
testCDSName + "2": wantUpdate2,
|
testCDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
@ -222,7 +222,7 @@ func (s) TestEndpointsThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
// TestEndpointsWatchAfterCache covers the case where watch is called after the update
|
// TestEndpointsWatchAfterCache covers the case where watch is called after the update
|
||||||
// is in cache.
|
// is in cache.
|
||||||
func (s) TestEndpointsWatchAfterCache(t *testing.T) {
|
func (s) TestEndpointsWatchAfterCache(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -237,12 +237,12 @@ func (s) TestEndpointsWatchAfterCache(t *testing.T) {
|
||||||
c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
||||||
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[edsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2EndpointsURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
wantUpdate := EndpointsUpdate{Localities: []Locality{testLocalities[0]}}
|
||||||
v2Client.r.newEDSUpdate(map[string]EndpointsUpdate{
|
v2Client.r.NewEndpoints(map[string]EndpointsUpdate{
|
||||||
testCDSName: wantUpdate,
|
testCDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -255,7 +255,7 @@ func (s) TestEndpointsWatchAfterCache(t *testing.T) {
|
||||||
c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
||||||
endpointsUpdateCh2.Send(endpointsUpdateErr{u: update, err: err})
|
endpointsUpdateCh2.Send(endpointsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if n, err := v2Client.addWatches[edsURL].Receive(); err == nil {
|
if n, err := v2Client.addWatches[version.V2EndpointsURL].Receive(); err == nil {
|
||||||
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,7 +280,7 @@ func (s) TestEndpointsWatchExpiryTimer(t *testing.T) {
|
||||||
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
||||||
}()
|
}()
|
||||||
|
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -295,7 +295,7 @@ func (s) TestEndpointsWatchExpiryTimer(t *testing.T) {
|
||||||
c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
c.WatchEndpoints(testCDSName, func(update EndpointsUpdate, err error) {
|
||||||
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
endpointsUpdateCh.Send(endpointsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[edsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2EndpointsURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright 2020 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 (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ldsUpdate struct {
|
|
||||||
routeName string
|
|
||||||
}
|
|
||||||
type ldsCallbackFunc func(ldsUpdate, error)
|
|
||||||
|
|
||||||
// watchLDS starts a listener watcher for the service..
|
|
||||||
//
|
|
||||||
// Note that during race (e.g. an xDS response is received while the user is
|
|
||||||
// calling cancel()), there's a small window where the callback can be called
|
|
||||||
// after the watcher is canceled. The caller needs to handle this case.
|
|
||||||
func (c *Client) watchLDS(serviceName string, cb ldsCallbackFunc) (cancel func()) {
|
|
||||||
wi := &watchInfo{
|
|
||||||
c: c,
|
|
||||||
typeURL: ldsURL,
|
|
||||||
target: serviceName,
|
|
||||||
ldsCallback: cb,
|
|
||||||
}
|
|
||||||
|
|
||||||
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
|
||||||
wi.timeout()
|
|
||||||
})
|
|
||||||
return c.watch(wi)
|
|
||||||
}
|
|
||||||
|
|
@ -22,10 +22,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ldsUpdateErr struct {
|
type ldsUpdateErr struct {
|
||||||
u ldsUpdate
|
u ListenerUpdate
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ type ldsUpdateErr struct {
|
||||||
// - an update for another resource name
|
// - an update for another resource name
|
||||||
// - an update is received after cancel()
|
// - an update is received after cancel()
|
||||||
func (s) TestLDSWatch(t *testing.T) {
|
func (s) TestLDSWatch(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -46,47 +47,47 @@ func (s) TestLDSWatch(t *testing.T) {
|
||||||
v2Client := <-v2ClientCh
|
v2Client := <-v2ClientCh
|
||||||
|
|
||||||
ldsUpdateCh := testutils.NewChannel()
|
ldsUpdateCh := testutils.NewChannel()
|
||||||
cancelWatch := c.watchLDS(testLDSName, func(update ldsUpdate, err error) {
|
cancelWatch := c.watchLDS(testLDSName, func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := ldsUpdate{routeName: testRDSName}
|
wantUpdate := ListenerUpdate{RouteConfigName: testRDSName}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: wantUpdate,
|
testLDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := ldsUpdateCh.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
if u, err := ldsUpdateCh.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another update, with an extra resource for a different resource name.
|
// Another update, with an extra resource for a different resource name.
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: wantUpdate,
|
testLDSName: wantUpdate,
|
||||||
"randomName": {},
|
"randomName": {},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := ldsUpdateCh.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
if u, err := ldsUpdateCh.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel watch, and send update again.
|
// Cancel watch, and send update again.
|
||||||
cancelWatch()
|
cancelWatch()
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: wantUpdate,
|
testLDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := ldsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
if u, err := ldsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLDSTwoWatchSameResourceName covers the case where an update is received
|
// TestLDSTwoWatchSameResourceName covers the case where an update is received
|
||||||
// after two watch() for the same resource name.
|
// after two watch() for the same resource name.
|
||||||
func (s) TestLDSTwoWatchSameResourceName(t *testing.T) {
|
func (s) TestLDSTwoWatchSameResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -105,46 +106,46 @@ func (s) TestLDSTwoWatchSameResourceName(t *testing.T) {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
ldsUpdateCh := testutils.NewChannel()
|
ldsUpdateCh := testutils.NewChannel()
|
||||||
ldsUpdateChs = append(ldsUpdateChs, ldsUpdateCh)
|
ldsUpdateChs = append(ldsUpdateChs, ldsUpdateCh)
|
||||||
cancelLastWatch = c.watchLDS(testLDSName, func(update ldsUpdate, err error) {
|
cancelLastWatch = c.watchLDS(testLDSName, func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := ldsUpdate{routeName: testRDSName}
|
wantUpdate := ListenerUpdate{RouteConfigName: testRDSName}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: wantUpdate,
|
testLDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if u, err := ldsUpdateChs[i].Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
if u, err := ldsUpdateChs[i].Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
||||||
t.Errorf("i=%v, unexpected ldsUpdate: %v, error receiving from channel: %v", i, u, err)
|
t.Errorf("i=%v, unexpected ListenerUpdate: %v, error receiving from channel: %v", i, u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel the last watch, and send update again.
|
// Cancel the last watch, and send update again.
|
||||||
cancelLastWatch()
|
cancelLastWatch()
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: wantUpdate,
|
testLDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < count-1; i++ {
|
for i := 0; i < count-1; i++ {
|
||||||
if u, err := ldsUpdateChs[i].Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
if u, err := ldsUpdateChs[i].Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
||||||
t.Errorf("i=%v, unexpected ldsUpdate: %v, error receiving from channel: %v", i, u, err)
|
t.Errorf("i=%v, unexpected ListenerUpdate: %v, error receiving from channel: %v", i, u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := ldsUpdateChs[count-1].TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
if u, err := ldsUpdateChs[count-1].TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLDSThreeWatchDifferentResourceName covers the case where an update is
|
// TestLDSThreeWatchDifferentResourceName covers the case where an update is
|
||||||
// received after three watch() for different resource names.
|
// received after three watch() for different resource names.
|
||||||
func (s) TestLDSThreeWatchDifferentResourceName(t *testing.T) {
|
func (s) TestLDSThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -162,45 +163,45 @@ func (s) TestLDSThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
ldsUpdateCh := testutils.NewChannel()
|
ldsUpdateCh := testutils.NewChannel()
|
||||||
ldsUpdateChs = append(ldsUpdateChs, ldsUpdateCh)
|
ldsUpdateChs = append(ldsUpdateChs, ldsUpdateCh)
|
||||||
c.watchLDS(testLDSName+"1", func(update ldsUpdate, err error) {
|
c.watchLDS(testLDSName+"1", func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third watch for a different name.
|
// Third watch for a different name.
|
||||||
ldsUpdateCh2 := testutils.NewChannel()
|
ldsUpdateCh2 := testutils.NewChannel()
|
||||||
c.watchLDS(testLDSName+"2", func(update ldsUpdate, err error) {
|
c.watchLDS(testLDSName+"2", func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh2.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh2.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate1 := ldsUpdate{routeName: testRDSName + "1"}
|
wantUpdate1 := ListenerUpdate{RouteConfigName: testRDSName + "1"}
|
||||||
wantUpdate2 := ldsUpdate{routeName: testRDSName + "2"}
|
wantUpdate2 := ListenerUpdate{RouteConfigName: testRDSName + "2"}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName + "1": wantUpdate1,
|
testLDSName + "1": wantUpdate1,
|
||||||
testLDSName + "2": wantUpdate2,
|
testLDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if u, err := ldsUpdateChs[i].Receive(); err != nil || u != (ldsUpdateErr{wantUpdate1, nil}) {
|
if u, err := ldsUpdateChs[i].Receive(); err != nil || u != (ldsUpdateErr{wantUpdate1, nil}) {
|
||||||
t.Errorf("i=%v, unexpected ldsUpdate: %v, error receiving from channel: %v", i, u, err)
|
t.Errorf("i=%v, unexpected ListenerUpdate: %v, error receiving from channel: %v", i, u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLDSWatchAfterCache covers the case where watch is called after the update
|
// TestLDSWatchAfterCache covers the case where watch is called after the update
|
||||||
// is in cache.
|
// is in cache.
|
||||||
func (s) TestLDSWatchAfterCache(t *testing.T) {
|
func (s) TestLDSWatchAfterCache(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -212,39 +213,39 @@ func (s) TestLDSWatchAfterCache(t *testing.T) {
|
||||||
v2Client := <-v2ClientCh
|
v2Client := <-v2ClientCh
|
||||||
|
|
||||||
ldsUpdateCh := testutils.NewChannel()
|
ldsUpdateCh := testutils.NewChannel()
|
||||||
c.watchLDS(testLDSName, func(update ldsUpdate, err error) {
|
c.watchLDS(testLDSName, func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := ldsUpdate{routeName: testRDSName}
|
wantUpdate := ListenerUpdate{RouteConfigName: testRDSName}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: wantUpdate,
|
testLDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := ldsUpdateCh.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
if u, err := ldsUpdateCh.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another watch for the resource in cache.
|
// Another watch for the resource in cache.
|
||||||
ldsUpdateCh2 := testutils.NewChannel()
|
ldsUpdateCh2 := testutils.NewChannel()
|
||||||
c.watchLDS(testLDSName, func(update ldsUpdate, err error) {
|
c.watchLDS(testLDSName, func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh2.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh2.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if n, err := v2Client.addWatches[ldsURL].Receive(); err == nil {
|
if n, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err == nil {
|
||||||
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New watch should receives the update.
|
// New watch should receives the update.
|
||||||
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old watch should see nothing.
|
// Old watch should see nothing.
|
||||||
if u, err := ldsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
if u, err := ldsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +256,7 @@ func (s) TestLDSWatchAfterCache(t *testing.T) {
|
||||||
// - one more update without the removed resource
|
// - one more update without the removed resource
|
||||||
// - the callback (above) shouldn't receive any update
|
// - the callback (above) shouldn't receive any update
|
||||||
func (s) TestLDSResourceRemoved(t *testing.T) {
|
func (s) TestLDSResourceRemoved(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -267,63 +268,63 @@ func (s) TestLDSResourceRemoved(t *testing.T) {
|
||||||
v2Client := <-v2ClientCh
|
v2Client := <-v2ClientCh
|
||||||
|
|
||||||
ldsUpdateCh1 := testutils.NewChannel()
|
ldsUpdateCh1 := testutils.NewChannel()
|
||||||
c.watchLDS(testLDSName+"1", func(update ldsUpdate, err error) {
|
c.watchLDS(testLDSName+"1", func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh1.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh1.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
// Another watch for a different name.
|
// Another watch for a different name.
|
||||||
ldsUpdateCh2 := testutils.NewChannel()
|
ldsUpdateCh2 := testutils.NewChannel()
|
||||||
c.watchLDS(testLDSName+"2", func(update ldsUpdate, err error) {
|
c.watchLDS(testLDSName+"2", func(update ListenerUpdate, err error) {
|
||||||
ldsUpdateCh2.Send(ldsUpdateErr{u: update, err: err})
|
ldsUpdateCh2.Send(ldsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate1 := ldsUpdate{routeName: testEDSName + "1"}
|
wantUpdate1 := ListenerUpdate{RouteConfigName: testEDSName + "1"}
|
||||||
wantUpdate2 := ldsUpdate{routeName: testEDSName + "2"}
|
wantUpdate2 := ListenerUpdate{RouteConfigName: testEDSName + "2"}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName + "1": wantUpdate1,
|
testLDSName + "1": wantUpdate1,
|
||||||
testLDSName + "2": wantUpdate2,
|
testLDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := ldsUpdateCh1.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate1, nil}) {
|
if u, err := ldsUpdateCh1.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate1, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send another update to remove resource 1.
|
// Send another update to remove resource 1.
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName + "2": wantUpdate2,
|
testLDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
// watcher 1 should get an error.
|
// watcher 1 should get an error.
|
||||||
if u, err := ldsUpdateCh1.Receive(); err != nil || ErrType(u.(ldsUpdateErr).err) != ErrorTypeResourceNotFound {
|
if u, err := ldsUpdateCh1.Receive(); err != nil || ErrType(u.(ldsUpdateErr).err) != ErrorTypeResourceNotFound {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v, want update with error resource not found", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v, want update with error resource not found", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// watcher 2 should get the same update again.
|
// watcher 2 should get the same update again.
|
||||||
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send one more update without resource 1.
|
// Send one more update without resource 1.
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName + "2": wantUpdate2,
|
testLDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
// watcher 1 should get an error.
|
// watcher 1 should get an error.
|
||||||
if u, err := ldsUpdateCh1.Receive(); err != testutils.ErrRecvTimeout {
|
if u, err := ldsUpdateCh1.Receive(); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, want receiving from channel timeout", u)
|
t.Errorf("unexpected ListenerUpdate: %v, want receiving from channel timeout", u)
|
||||||
}
|
}
|
||||||
|
|
||||||
// watcher 2 should get the same update again.
|
// watcher 2 should get the same update again.
|
||||||
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
if u, err := ldsUpdateCh2.Receive(); err != nil || u != (ldsUpdateErr{wantUpdate2, nil}) {
|
||||||
t.Errorf("unexpected ldsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected ListenerUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright 2020 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 (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Int64Range is a range for header range match.
|
|
||||||
type Int64Range struct {
|
|
||||||
Start int64 `json:"start"`
|
|
||||||
End int64 `json:"end"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderMatcher represents header matchers.
|
|
||||||
type HeaderMatcher struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
InvertMatch *bool `json:"invertMatch,omitempty"`
|
|
||||||
ExactMatch *string `json:"exactMatch,omitempty"`
|
|
||||||
RegexMatch *string `json:"regexMatch,omitempty"`
|
|
||||||
PrefixMatch *string `json:"prefixMatch,omitempty"`
|
|
||||||
SuffixMatch *string `json:"suffixMatch,omitempty"`
|
|
||||||
RangeMatch *Int64Range `json:"rangeMatch,omitempty"`
|
|
||||||
PresentMatch *bool `json:"presentMatch,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route represents route with matchers and action.
|
|
||||||
type Route struct {
|
|
||||||
Path, Prefix, Regex *string
|
|
||||||
Headers []*HeaderMatcher
|
|
||||||
Fraction *uint32
|
|
||||||
Action map[string]uint32 // action is weighted clusters.
|
|
||||||
}
|
|
||||||
|
|
||||||
type rdsUpdate struct {
|
|
||||||
routes []*Route
|
|
||||||
}
|
|
||||||
type rdsCallbackFunc func(rdsUpdate, error)
|
|
||||||
|
|
||||||
// watchRDS starts a listener watcher for the service..
|
|
||||||
//
|
|
||||||
// Note that during race (e.g. an xDS response is received while the user is
|
|
||||||
// calling cancel()), there's a small window where the callback can be called
|
|
||||||
// after the watcher is canceled. The caller needs to handle this case.
|
|
||||||
func (c *Client) watchRDS(routeName string, cb rdsCallbackFunc) (cancel func()) {
|
|
||||||
wi := &watchInfo{
|
|
||||||
c: c,
|
|
||||||
typeURL: rdsURL,
|
|
||||||
target: routeName,
|
|
||||||
rdsCallback: cb,
|
|
||||||
}
|
|
||||||
|
|
||||||
wi.expiryTimer = time.AfterFunc(defaultWatchExpiryTimeout, func() {
|
|
||||||
wi.timeout()
|
|
||||||
})
|
|
||||||
return c.watch(wi)
|
|
||||||
}
|
|
||||||
|
|
@ -23,10 +23,11 @@ import (
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rdsUpdateErr struct {
|
type rdsUpdateErr struct {
|
||||||
u rdsUpdate
|
u RouteConfigUpdate
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +36,7 @@ type rdsUpdateErr struct {
|
||||||
// - an update for another resource name (which doesn't trigger callback)
|
// - an update for another resource name (which doesn't trigger callback)
|
||||||
// - an update is received after cancel()
|
// - an update is received after cancel()
|
||||||
func (s) TestRDSWatch(t *testing.T) {
|
func (s) TestRDSWatch(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -47,46 +48,46 @@ func (s) TestRDSWatch(t *testing.T) {
|
||||||
v2Client := <-v2ClientCh
|
v2Client := <-v2ClientCh
|
||||||
|
|
||||||
rdsUpdateCh := testutils.NewChannel()
|
rdsUpdateCh := testutils.NewChannel()
|
||||||
cancelWatch := c.watchRDS(testRDSName, func(update rdsUpdate, err error) {
|
cancelWatch := c.watchRDS(testRDSName, func(update RouteConfigUpdate, err error) {
|
||||||
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := rdsUpdate{routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: wantUpdate,
|
testRDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := rdsUpdateCh.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdate{}, rdsUpdateErr{})) {
|
if u, err := rdsUpdateCh.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdateErr{})) {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another update for a different resource name.
|
// Another update for a different resource name.
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
"randomName": {},
|
"randomName": {},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := rdsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
if u, err := rdsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel watch, and send update again.
|
// Cancel watch, and send update again.
|
||||||
cancelWatch()
|
cancelWatch()
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: wantUpdate,
|
testRDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := rdsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
if u, err := rdsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRDSTwoWatchSameResourceName covers the case where an update is received
|
// TestRDSTwoWatchSameResourceName covers the case where an update is received
|
||||||
// after two watch() for the same resource name.
|
// after two watch() for the same resource name.
|
||||||
func (s) TestRDSTwoWatchSameResourceName(t *testing.T) {
|
func (s) TestRDSTwoWatchSameResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -105,46 +106,46 @@ func (s) TestRDSTwoWatchSameResourceName(t *testing.T) {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
rdsUpdateCh := testutils.NewChannel()
|
rdsUpdateCh := testutils.NewChannel()
|
||||||
rdsUpdateChs = append(rdsUpdateChs, rdsUpdateCh)
|
rdsUpdateChs = append(rdsUpdateChs, rdsUpdateCh)
|
||||||
cancelLastWatch = c.watchRDS(testRDSName, func(update rdsUpdate, err error) {
|
cancelLastWatch = c.watchRDS(testRDSName, func(update RouteConfigUpdate, err error) {
|
||||||
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := rdsUpdate{routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: wantUpdate,
|
testRDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if u, err := rdsUpdateChs[i].Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdate{}, rdsUpdateErr{})) {
|
if u, err := rdsUpdateChs[i].Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdateErr{})) {
|
||||||
t.Errorf("i=%v, unexpected rdsUpdate: %v, error receiving from channel: %v", i, u, err)
|
t.Errorf("i=%v, unexpected RouteConfigUpdate: %v, error receiving from channel: %v", i, u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel the last watch, and send update again.
|
// Cancel the last watch, and send update again.
|
||||||
cancelLastWatch()
|
cancelLastWatch()
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: wantUpdate,
|
testRDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < count-1; i++ {
|
for i := 0; i < count-1; i++ {
|
||||||
if u, err := rdsUpdateChs[i].Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdate{}, rdsUpdateErr{})) {
|
if u, err := rdsUpdateChs[i].Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdateErr{})) {
|
||||||
t.Errorf("i=%v, unexpected rdsUpdate: %v, error receiving from channel: %v", i, u, err)
|
t.Errorf("i=%v, unexpected RouteConfigUpdate: %v, error receiving from channel: %v", i, u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := rdsUpdateChs[count-1].TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
if u, err := rdsUpdateChs[count-1].TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRDSThreeWatchDifferentResourceName covers the case where an update is
|
// TestRDSThreeWatchDifferentResourceName covers the case where an update is
|
||||||
// received after three watch() for different resource names.
|
// received after three watch() for different resource names.
|
||||||
func (s) TestRDSThreeWatchDifferentResourceName(t *testing.T) {
|
func (s) TestRDSThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -162,45 +163,45 @@ func (s) TestRDSThreeWatchDifferentResourceName(t *testing.T) {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
rdsUpdateCh := testutils.NewChannel()
|
rdsUpdateCh := testutils.NewChannel()
|
||||||
rdsUpdateChs = append(rdsUpdateChs, rdsUpdateCh)
|
rdsUpdateChs = append(rdsUpdateChs, rdsUpdateCh)
|
||||||
c.watchRDS(testRDSName+"1", func(update rdsUpdate, err error) {
|
c.watchRDS(testRDSName+"1", func(update RouteConfigUpdate, err error) {
|
||||||
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); i == 0 && err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); i == 0 && err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third watch for a different name.
|
// Third watch for a different name.
|
||||||
rdsUpdateCh2 := testutils.NewChannel()
|
rdsUpdateCh2 := testutils.NewChannel()
|
||||||
c.watchRDS(testRDSName+"2", func(update rdsUpdate, err error) {
|
c.watchRDS(testRDSName+"2", func(update RouteConfigUpdate, err error) {
|
||||||
rdsUpdateCh2.Send(rdsUpdateErr{u: update, err: err})
|
rdsUpdateCh2.Send(rdsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate1 := rdsUpdate{routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "1": 1}}}}
|
wantUpdate1 := RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "1": 1}}}}
|
||||||
wantUpdate2 := rdsUpdate{routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "2": 1}}}}
|
wantUpdate2 := RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "2": 1}}}}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName + "1": wantUpdate1,
|
testRDSName + "1": wantUpdate1,
|
||||||
testRDSName + "2": wantUpdate2,
|
testRDSName + "2": wantUpdate2,
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if u, err := rdsUpdateChs[i].Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate1, nil}, cmp.AllowUnexported(rdsUpdate{}, rdsUpdateErr{})) {
|
if u, err := rdsUpdateChs[i].Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate1, nil}, cmp.AllowUnexported(rdsUpdateErr{})) {
|
||||||
t.Errorf("i=%v, unexpected rdsUpdate: %v, error receiving from channel: %v", i, u, err)
|
t.Errorf("i=%v, unexpected RouteConfigUpdate: %v, error receiving from channel: %v", i, u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := rdsUpdateCh2.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate2, nil}, cmp.AllowUnexported(rdsUpdate{}, rdsUpdateErr{})) {
|
if u, err := rdsUpdateCh2.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate2, nil}, cmp.AllowUnexported(rdsUpdateErr{})) {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRDSWatchAfterCache covers the case where watch is called after the update
|
// TestRDSWatchAfterCache covers the case where watch is called after the update
|
||||||
// is in cache.
|
// is in cache.
|
||||||
func (s) TestRDSWatchAfterCache(t *testing.T) {
|
func (s) TestRDSWatchAfterCache(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -212,38 +213,38 @@ func (s) TestRDSWatchAfterCache(t *testing.T) {
|
||||||
v2Client := <-v2ClientCh
|
v2Client := <-v2ClientCh
|
||||||
|
|
||||||
rdsUpdateCh := testutils.NewChannel()
|
rdsUpdateCh := testutils.NewChannel()
|
||||||
c.watchRDS(testRDSName, func(update rdsUpdate, err error) {
|
c.watchRDS(testRDSName, func(update RouteConfigUpdate, err error) {
|
||||||
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
rdsUpdateCh.Send(rdsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUpdate := rdsUpdate{routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := RouteConfigUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: wantUpdate,
|
testRDSName: wantUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := rdsUpdateCh.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdate{}, rdsUpdateErr{})) {
|
if u, err := rdsUpdateCh.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdateErr{})) {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another watch for the resource in cache.
|
// Another watch for the resource in cache.
|
||||||
rdsUpdateCh2 := testutils.NewChannel()
|
rdsUpdateCh2 := testutils.NewChannel()
|
||||||
c.watchRDS(testRDSName, func(update rdsUpdate, err error) {
|
c.watchRDS(testRDSName, func(update RouteConfigUpdate, err error) {
|
||||||
rdsUpdateCh2.Send(rdsUpdateErr{u: update, err: err})
|
rdsUpdateCh2.Send(rdsUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
if n, err := v2Client.addWatches[rdsURL].Receive(); err == nil {
|
if n, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err == nil {
|
||||||
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
t.Fatalf("want no new watch to start (recv timeout), got resource name: %v error %v", n, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New watch should receives the update.
|
// New watch should receives the update.
|
||||||
if u, err := rdsUpdateCh2.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdate{}, rdsUpdateErr{})) {
|
if u, err := rdsUpdateCh2.Receive(); err != nil || !cmp.Equal(u, rdsUpdateErr{wantUpdate, nil}, cmp.AllowUnexported(rdsUpdateErr{})) {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old watch should see nothing.
|
// Old watch should see nothing.
|
||||||
if u, err := rdsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
if u, err := rdsUpdateCh.TimedReceive(chanRecvTimeout); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected rdsUpdate: %v, %v, want channel recv timeout", u, err)
|
t.Errorf("unexpected RouteConfigUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright 2020 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 (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServiceUpdate contains update about the service.
|
|
||||||
type ServiceUpdate struct {
|
|
||||||
// Routes contain matchers+actions to route RPCs.
|
|
||||||
Routes []*Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchService uses LDS and RDS to discover information about the provided
|
|
||||||
// serviceName.
|
|
||||||
//
|
|
||||||
// WatchService can only be called once. The second call will not start a
|
|
||||||
// watcher and the callback will get an error. It's this case because an xDS
|
|
||||||
// client is expected to be used only by one ClientConn.
|
|
||||||
//
|
|
||||||
// Note that during race (e.g. an xDS response is received while the user is
|
|
||||||
// calling cancel()), there's a small window where the callback can be called
|
|
||||||
// after the watcher is canceled. The caller needs to handle this case.
|
|
||||||
func (c *Client) WatchService(serviceName string, cb func(ServiceUpdate, error)) (cancel func()) {
|
|
||||||
c.mu.Lock()
|
|
||||||
if len(c.ldsWatchers) != 0 {
|
|
||||||
go cb(ServiceUpdate{}, fmt.Errorf("unexpected WatchService when there's another service being watched"))
|
|
||||||
c.mu.Unlock()
|
|
||||||
return func() {}
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
w := &serviceUpdateWatcher{c: c, serviceCb: cb}
|
|
||||||
w.ldsCancel = c.watchLDS(serviceName, w.handleLDSResp)
|
|
||||||
|
|
||||||
return w.close
|
|
||||||
}
|
|
||||||
|
|
||||||
// serviceUpdateWatcher handles LDS and RDS response, and calls the service
|
|
||||||
// callback at the right time.
|
|
||||||
type serviceUpdateWatcher struct {
|
|
||||||
c *Client
|
|
||||||
ldsCancel func()
|
|
||||||
serviceCb func(ServiceUpdate, error)
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
closed bool
|
|
||||||
rdsName string
|
|
||||||
rdsCancel func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *serviceUpdateWatcher) handleLDSResp(update ldsUpdate, err error) {
|
|
||||||
w.c.logger.Infof("xds: client received LDS update: %+v, err: %v", update, err)
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
if w.closed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// We check the error type and do different things. For now, the only
|
|
||||||
// type we check is ResourceNotFound, which indicates the LDS resource
|
|
||||||
// was removed, and besides sending the error to callback, we also
|
|
||||||
// cancel the RDS watch.
|
|
||||||
if ErrType(err) == ErrorTypeResourceNotFound && w.rdsCancel != nil {
|
|
||||||
w.rdsCancel()
|
|
||||||
w.rdsName = ""
|
|
||||||
w.rdsCancel = nil
|
|
||||||
}
|
|
||||||
// The other error cases still return early without canceling the
|
|
||||||
// existing RDS watch.
|
|
||||||
w.serviceCb(ServiceUpdate{}, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.rdsName == update.routeName {
|
|
||||||
// If the new routeName is same as the previous, don't cancel and
|
|
||||||
// restart the RDS watch.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.rdsName = update.routeName
|
|
||||||
if w.rdsCancel != nil {
|
|
||||||
w.rdsCancel()
|
|
||||||
}
|
|
||||||
w.rdsCancel = w.c.watchRDS(update.routeName, w.handleRDSResp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *serviceUpdateWatcher) handleRDSResp(update rdsUpdate, err error) {
|
|
||||||
w.c.logger.Infof("xds: client received RDS update: %+v, err: %v", update, err)
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
if w.closed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if w.rdsCancel == nil {
|
|
||||||
// This mean only the RDS watch is canceled, can happen if the LDS
|
|
||||||
// resource is removed.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
w.serviceCb(ServiceUpdate{}, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.serviceCb(ServiceUpdate{
|
|
||||||
Routes: update.routes,
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *serviceUpdateWatcher) close() {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
w.closed = true
|
|
||||||
w.ldsCancel()
|
|
||||||
if w.rdsCancel != nil {
|
|
||||||
w.rdsCancel()
|
|
||||||
w.rdsCancel = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -19,16 +19,13 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serviceUpdateErr struct {
|
type serviceUpdateErr struct {
|
||||||
|
|
@ -42,7 +39,7 @@ var serviceCmpOpts = []cmp.Option{cmp.AllowUnexported(serviceUpdateErr{}), cmpop
|
||||||
// - an update is received after a watch()
|
// - an update is received after a watch()
|
||||||
// - an update with routes received
|
// - an update with routes received
|
||||||
func (s) TestServiceWatch(t *testing.T) {
|
func (s) TestServiceWatch(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -60,17 +57,17 @@ func (s) TestServiceWatch(t *testing.T) {
|
||||||
|
|
||||||
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
|
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
||||||
|
|
@ -83,9 +80,9 @@ func (s) TestServiceWatch(t *testing.T) {
|
||||||
Action: map[string]uint32{testCDSName: 1},
|
Action: map[string]uint32{testCDSName: 1},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {
|
testRDSName: {
|
||||||
routes: []*Route{{
|
Routes: []*Route{{
|
||||||
Prefix: newStringP(""),
|
Prefix: newStringP(""),
|
||||||
Action: map[string]uint32{testCDSName: 1},
|
Action: map[string]uint32{testCDSName: 1},
|
||||||
}},
|
}},
|
||||||
|
|
@ -100,7 +97,7 @@ func (s) TestServiceWatch(t *testing.T) {
|
||||||
// response, the second LDS response trigger an new RDS watch, and an update of
|
// response, the second LDS response trigger an new RDS watch, and an update of
|
||||||
// the old RDS watch doesn't trigger update to service callback.
|
// the old RDS watch doesn't trigger update to service callback.
|
||||||
func (s) TestServiceWatchLDSUpdate(t *testing.T) {
|
func (s) TestServiceWatchLDSUpdate(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -118,17 +115,17 @@ func (s) TestServiceWatchLDSUpdate(t *testing.T) {
|
||||||
|
|
||||||
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
|
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
||||||
|
|
@ -136,16 +133,16 @@ func (s) TestServiceWatchLDSUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another LDS update with a different RDS_name.
|
// Another LDS update with a different RDS_name.
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName + "2"},
|
testLDSName: {RouteConfigName: testRDSName + "2"},
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another update for the old name.
|
// Another update for the old name.
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != testutils.ErrRecvTimeout {
|
if u, err := serviceUpdateCh.Receive(); err != testutils.ErrRecvTimeout {
|
||||||
|
|
@ -154,8 +151,8 @@ func (s) TestServiceWatchLDSUpdate(t *testing.T) {
|
||||||
|
|
||||||
wantUpdate2 := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "2": 1}}}}
|
wantUpdate2 := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "2": 1}}}}
|
||||||
// RDS update for the new name.
|
// RDS update for the new name.
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName + "2": {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "2": 1}}}},
|
testRDSName + "2": {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "2": 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate2, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate2, nil}, serviceCmpOpts...) {
|
||||||
|
|
@ -167,7 +164,7 @@ func (s) TestServiceWatchLDSUpdate(t *testing.T) {
|
||||||
// error (because only one is allowed). But the first watch still receives
|
// error (because only one is allowed). But the first watch still receives
|
||||||
// updates.
|
// updates.
|
||||||
func (s) TestServiceWatchSecond(t *testing.T) {
|
func (s) TestServiceWatchSecond(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -185,17 +182,17 @@ func (s) TestServiceWatchSecond(t *testing.T) {
|
||||||
|
|
||||||
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
|
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
||||||
|
|
@ -222,11 +219,11 @@ func (s) TestServiceWatchSecond(t *testing.T) {
|
||||||
|
|
||||||
// Send update again, first callback should be called, second should
|
// Send update again, first callback should be called, second should
|
||||||
// timeout.
|
// timeout.
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
||||||
|
|
@ -242,135 +239,130 @@ func (s) TestServiceWatchSecond(t *testing.T) {
|
||||||
// does not respond to the requests being sent out as part of registering a
|
// does not respond to the requests being sent out as part of registering a
|
||||||
// service update watcher. The callback will get an error.
|
// service update watcher. The callback will get an error.
|
||||||
func (s) TestServiceWatchWithNoResponseFromServer(t *testing.T) {
|
func (s) TestServiceWatchWithNoResponseFromServer(t *testing.T) {
|
||||||
fakeServer, cleanup, err := fakeserver.StartServer()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to start fake xDS server: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
xdsClient, err := New(clientOpts(fakeServer.Address))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("New returned error: %v", err)
|
|
||||||
}
|
|
||||||
defer xdsClient.Close()
|
|
||||||
t.Log("Created an xdsClient...")
|
|
||||||
|
|
||||||
oldWatchExpiryTimeout := defaultWatchExpiryTimeout
|
oldWatchExpiryTimeout := defaultWatchExpiryTimeout
|
||||||
defaultWatchExpiryTimeout = 500 * time.Millisecond
|
defaultWatchExpiryTimeout = 500 * time.Millisecond
|
||||||
defer func() {
|
defer func() {
|
||||||
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
||||||
}()
|
}()
|
||||||
|
|
||||||
callbackCh := testutils.NewChannel()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
cancelWatch := xdsClient.WatchService(goodLDSTarget1, func(su ServiceUpdate, err error) {
|
defer cleanup()
|
||||||
if su.Routes != nil {
|
|
||||||
callbackCh.Send(fmt.Errorf("got WeightedCluster: %+v, want nil", su.Routes))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
callbackCh.Send(errors.New("xdsClient.WatchService returned error non-nil error"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
callbackCh.Send(nil)
|
|
||||||
})
|
|
||||||
defer cancelWatch()
|
|
||||||
t.Log("Registered a watcher for service updates...")
|
|
||||||
|
|
||||||
// Wait for one request from the client, but send no reponses.
|
c, err := New(clientOpts(testXDSServer))
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Timeout expired when expecting an LDS request")
|
t.Fatalf("failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
v2Client := <-v2ClientCh
|
||||||
|
|
||||||
|
serviceUpdateCh := testutils.NewChannel()
|
||||||
|
c.WatchService(testLDSName, func(update ServiceUpdate, err error) {
|
||||||
|
serviceUpdateCh.Send(serviceUpdateErr{u: update, err: err})
|
||||||
|
})
|
||||||
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
|
}
|
||||||
|
u, err := serviceUpdateCh.TimedReceive(defaultWatchExpiryTimeout * 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get serviceUpdate: %v", err)
|
||||||
|
}
|
||||||
|
uu := u.(serviceUpdateErr)
|
||||||
|
if !cmp.Equal(uu.u, ServiceUpdate{}) {
|
||||||
|
t.Errorf("unexpected serviceUpdate: %v, want %v", uu.u, ServiceUpdate{})
|
||||||
|
}
|
||||||
|
if uu.err == nil {
|
||||||
|
t.Errorf("unexpected serviceError: <nil>, want error watcher timeout")
|
||||||
}
|
}
|
||||||
waitForNilErr(t, callbackCh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestServiceWatchEmptyRDS tests the case where the underlying v2Client
|
// TestServiceWatchEmptyRDS tests the case where the underlying v2Client
|
||||||
// receives an empty RDS response. The callback will get an error.
|
// receives an empty RDS response. The callback will get an error.
|
||||||
func (s) TestServiceWatchEmptyRDS(t *testing.T) {
|
func (s) TestServiceWatchEmptyRDS(t *testing.T) {
|
||||||
fakeServer, cleanup, err := fakeserver.StartServer()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to start fake xDS server: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
xdsClient, err := New(clientOpts(fakeServer.Address))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("New returned error: %v", err)
|
|
||||||
}
|
|
||||||
defer xdsClient.Close()
|
|
||||||
t.Log("Created an xdsClient...")
|
|
||||||
|
|
||||||
oldWatchExpiryTimeout := defaultWatchExpiryTimeout
|
oldWatchExpiryTimeout := defaultWatchExpiryTimeout
|
||||||
defaultWatchExpiryTimeout = 500 * time.Millisecond
|
defaultWatchExpiryTimeout = 500 * time.Millisecond
|
||||||
defer func() {
|
defer func() {
|
||||||
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
||||||
}()
|
}()
|
||||||
|
|
||||||
callbackCh := testutils.NewChannel()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
cancelWatch := xdsClient.WatchService(goodLDSTarget1, func(su ServiceUpdate, err error) {
|
defer cleanup()
|
||||||
if su.Routes != nil {
|
|
||||||
callbackCh.Send(fmt.Errorf("got WeightedCluster: %+v, want nil", su.Routes))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
return
|
if err != nil {
|
||||||
}
|
t.Fatalf("failed to create client: %v", err)
|
||||||
if err == nil {
|
}
|
||||||
callbackCh.Send(errors.New("xdsClient.WatchService returned error non-nil error"))
|
defer c.Close()
|
||||||
return
|
|
||||||
}
|
v2Client := <-v2ClientCh
|
||||||
callbackCh.Send(nil)
|
|
||||||
|
serviceUpdateCh := testutils.NewChannel()
|
||||||
|
c.WatchService(testLDSName, func(update ServiceUpdate, err error) {
|
||||||
|
serviceUpdateCh.Send(serviceUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
defer cancelWatch()
|
|
||||||
t.Log("Registered a watcher for service updates...")
|
|
||||||
|
|
||||||
// Make the fakeServer send LDS response.
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
t.Fatalf("Timeout expired when expecting an LDS request")
|
|
||||||
}
|
}
|
||||||
fakeServer.XDSResponseChan <- &fakeserver.Response{Resp: goodLDSResponse1}
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
// Make the fakeServer send an empty RDS response.
|
})
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("Timeout expired when expecting an RDS request")
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
|
}
|
||||||
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{})
|
||||||
|
u, err := serviceUpdateCh.TimedReceive(defaultWatchExpiryTimeout * 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get serviceUpdate: %v", err)
|
||||||
|
}
|
||||||
|
uu := u.(serviceUpdateErr)
|
||||||
|
if !cmp.Equal(uu.u, ServiceUpdate{}) {
|
||||||
|
t.Errorf("unexpected serviceUpdate: %v, want %v", uu.u, ServiceUpdate{})
|
||||||
|
}
|
||||||
|
if uu.err == nil {
|
||||||
|
t.Errorf("unexpected serviceError: <nil>, want error watcher timeout")
|
||||||
}
|
}
|
||||||
fakeServer.XDSResponseChan <- &fakeserver.Response{Resp: noVirtualHostsInRDSResponse}
|
|
||||||
waitForNilErr(t, callbackCh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestServiceWatchWithClientClose tests the case where xDS responses are
|
// TestServiceWatchWithClientClose tests the case where xDS responses are
|
||||||
// received after the client is closed, and we make sure that the registered
|
// received after the client is closed, and we make sure that the registered
|
||||||
// watcher callback is not invoked.
|
// watcher callback is not invoked.
|
||||||
func (s) TestServiceWatchWithClientClose(t *testing.T) {
|
func (s) TestServiceWatchWithClientClose(t *testing.T) {
|
||||||
fakeServer, cleanup, err := fakeserver.StartServer()
|
oldWatchExpiryTimeout := defaultWatchExpiryTimeout
|
||||||
if err != nil {
|
defaultWatchExpiryTimeout = 500 * time.Millisecond
|
||||||
t.Fatalf("Failed to start fake xDS server: %v", err)
|
defer func() {
|
||||||
}
|
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
||||||
|
}()
|
||||||
|
|
||||||
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
xdsClient, err := New(clientOpts(fakeServer.Address))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("New returned error: %v", err)
|
t.Fatalf("failed to create client: %v", err)
|
||||||
}
|
}
|
||||||
defer xdsClient.Close()
|
defer c.Close()
|
||||||
t.Log("Created an xdsClient...")
|
|
||||||
|
|
||||||
callbackCh := testutils.NewChannel()
|
v2Client := <-v2ClientCh
|
||||||
cancelWatch := xdsClient.WatchService(goodLDSTarget1, func(su ServiceUpdate, err error) {
|
|
||||||
callbackCh.Send(errors.New("watcher callback invoked after client close"))
|
serviceUpdateCh := testutils.NewChannel()
|
||||||
|
c.WatchService(testLDSName, func(update ServiceUpdate, err error) {
|
||||||
|
serviceUpdateCh.Send(serviceUpdateErr{u: update, err: err})
|
||||||
})
|
})
|
||||||
defer cancelWatch()
|
|
||||||
t.Log("Registered a watcher for service updates...")
|
|
||||||
|
|
||||||
// Make the fakeServer send LDS response.
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
t.Fatalf("Timeout expired when expecting an LDS request")
|
|
||||||
}
|
}
|
||||||
fakeServer.XDSResponseChan <- &fakeserver.Response{Resp: goodLDSResponse1}
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
xdsClient.Close()
|
})
|
||||||
t.Log("Closing the xdsClient...")
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
// Push an RDS response from the fakeserver
|
}
|
||||||
fakeServer.XDSResponseChan <- &fakeserver.Response{Resp: goodRDSResponse1}
|
// Client is closed before it receives the RDS response.
|
||||||
if cbErr, err := callbackCh.Receive(); err != testutils.ErrRecvTimeout {
|
c.Close()
|
||||||
t.Fatal(cbErr)
|
if u, err := serviceUpdateCh.TimedReceive(defaultWatchExpiryTimeout * 2); err != testutils.ErrRecvTimeout {
|
||||||
|
t.Errorf("unexpected serviceUpdate: %v, %v, want channel recv timeout", u, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,7 +370,7 @@ func (s) TestServiceWatchWithClientClose(t *testing.T) {
|
||||||
// update contains the same RDS name as the previous, the RDS watch isn't
|
// update contains the same RDS name as the previous, the RDS watch isn't
|
||||||
// canceled and restarted.
|
// canceled and restarted.
|
||||||
func (s) TestServiceNotCancelRDSOnSameLDSUpdate(t *testing.T) {
|
func (s) TestServiceNotCancelRDSOnSameLDSUpdate(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -396,17 +388,17 @@ func (s) TestServiceNotCancelRDSOnSameLDSUpdate(t *testing.T) {
|
||||||
|
|
||||||
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
|
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
||||||
|
|
@ -414,10 +406,10 @@ func (s) TestServiceNotCancelRDSOnSameLDSUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another LDS update with a the same RDS_name.
|
// Another LDS update with a the same RDS_name.
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
if v, err := v2Client.removeWatches[rdsURL].Receive(); err == nil {
|
if v, err := v2Client.removeWatches[version.V2RouteConfigURL].Receive(); err == nil {
|
||||||
t.Fatalf("unexpected rds watch cancel: %v", v)
|
t.Fatalf("unexpected rds watch cancel: %v", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,7 +421,7 @@ func (s) TestServiceNotCancelRDSOnSameLDSUpdate(t *testing.T) {
|
||||||
// - one more update without the removed resource
|
// - one more update without the removed resource
|
||||||
// - the callback (above) shouldn't receive any update
|
// - the callback (above) shouldn't receive any update
|
||||||
func (s) TestServiceResourceRemoved(t *testing.T) {
|
func (s) TestServiceResourceRemoved(t *testing.T) {
|
||||||
v2ClientCh, cleanup := overrideNewXDSV2Client()
|
v2ClientCh, cleanup := overrideNewAPIClient()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
c, err := New(clientOpts(testXDSServer))
|
c, err := New(clientOpts(testXDSServer))
|
||||||
|
|
@ -447,17 +439,17 @@ func (s) TestServiceResourceRemoved(t *testing.T) {
|
||||||
|
|
||||||
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
wantUpdate := ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}}
|
||||||
|
|
||||||
if _, err := v2Client.addWatches[ldsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2ListenerURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName: 1}}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{wantUpdate, nil}, serviceCmpOpts...) {
|
||||||
|
|
@ -466,8 +458,8 @@ func (s) TestServiceResourceRemoved(t *testing.T) {
|
||||||
|
|
||||||
// Remove LDS resource, should cancel the RDS watch, and trigger resource
|
// Remove LDS resource, should cancel the RDS watch, and trigger resource
|
||||||
// removed error.
|
// removed error.
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{})
|
v2Client.r.NewListeners(map[string]ListenerUpdate{})
|
||||||
if _, err := v2Client.removeWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.removeWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want watch to be canceled, got error %v", err)
|
t.Fatalf("want watch to be canceled, got error %v", err)
|
||||||
}
|
}
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || ErrType(u.(serviceUpdateErr).err) != ErrorTypeResourceNotFound {
|
if u, err := serviceUpdateCh.Receive(); err != nil || ErrType(u.(serviceUpdateErr).err) != ErrorTypeResourceNotFound {
|
||||||
|
|
@ -476,8 +468,8 @@ func (s) TestServiceResourceRemoved(t *testing.T) {
|
||||||
|
|
||||||
// Send RDS update for the removed LDS resource, expect no updates to
|
// Send RDS update for the removed LDS resource, expect no updates to
|
||||||
// callback, because RDS should be canceled.
|
// callback, because RDS should be canceled.
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "new": 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "new": 1}}}},
|
||||||
})
|
})
|
||||||
if u, err := serviceUpdateCh.Receive(); err != testutils.ErrRecvTimeout {
|
if u, err := serviceUpdateCh.Receive(); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected serviceUpdate: %v, want receiving from channel timeout", u)
|
t.Errorf("unexpected serviceUpdate: %v, want receiving from channel timeout", u)
|
||||||
|
|
@ -486,18 +478,18 @@ func (s) TestServiceResourceRemoved(t *testing.T) {
|
||||||
// Add LDS resource, but not RDS resource, should
|
// Add LDS resource, but not RDS resource, should
|
||||||
// - start a new RDS watch
|
// - start a new RDS watch
|
||||||
// - timeout on service channel, because RDS cache was cleared
|
// - timeout on service channel, because RDS cache was cleared
|
||||||
v2Client.r.newLDSUpdate(map[string]ldsUpdate{
|
v2Client.r.NewListeners(map[string]ListenerUpdate{
|
||||||
testLDSName: {routeName: testRDSName},
|
testLDSName: {RouteConfigName: testRDSName},
|
||||||
})
|
})
|
||||||
if _, err := v2Client.addWatches[rdsURL].Receive(); err != nil {
|
if _, err := v2Client.addWatches[version.V2RouteConfigURL].Receive(); err != nil {
|
||||||
t.Fatalf("want new watch to start, got error %v", err)
|
t.Fatalf("want new watch to start, got error %v", err)
|
||||||
}
|
}
|
||||||
if u, err := serviceUpdateCh.Receive(); err != testutils.ErrRecvTimeout {
|
if u, err := serviceUpdateCh.Receive(); err != testutils.ErrRecvTimeout {
|
||||||
t.Errorf("unexpected serviceUpdate: %v, want receiving from channel timeout", u)
|
t.Errorf("unexpected serviceUpdate: %v, want receiving from channel timeout", u)
|
||||||
}
|
}
|
||||||
|
|
||||||
v2Client.r.newRDSUpdate(map[string]rdsUpdate{
|
v2Client.r.NewRouteConfigs(map[string]RouteConfigUpdate{
|
||||||
testRDSName: {routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "new2": 1}}}},
|
testRDSName: {Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "new2": 1}}}},
|
||||||
})
|
})
|
||||||
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "new2": 1}}}}, nil}, serviceCmpOpts...) {
|
if u, err := serviceUpdateCh.Receive(); err != nil || !cmp.Equal(u, serviceUpdateErr{ServiceUpdate{Routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{testCDSName + "new2": 1}}}}, nil}, serviceCmpOpts...) {
|
||||||
t.Errorf("unexpected serviceUpdate: %v, error receiving from channel: %v", u, err)
|
t.Errorf("unexpected serviceUpdate: %v, error receiving from channel: %v", u, err)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,507 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||||
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
||||||
|
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
|
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||||
|
v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
|
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
|
"google.golang.org/grpc/internal/grpclog"
|
||||||
|
"google.golang.org/grpc/xds/internal"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalListener processes resources received in an LDS response, validates
|
||||||
|
// them, and transforms them into a native struct which contains only fields we
|
||||||
|
// are interested in.
|
||||||
|
func UnmarshalListener(resources []*anypb.Any, logger *grpclog.PrefixLogger) (map[string]ListenerUpdate, error) {
|
||||||
|
update := make(map[string]ListenerUpdate)
|
||||||
|
for _, r := range resources {
|
||||||
|
if t := r.GetTypeUrl(); t != version.V2ListenerURL && t != version.V3ListenerURL {
|
||||||
|
return nil, fmt.Errorf("xds: unexpected resource type: %s in LDS response", t)
|
||||||
|
}
|
||||||
|
lis := &v3listenerpb.Listener{}
|
||||||
|
if err := proto.Unmarshal(r.GetValue(), lis); err != nil {
|
||||||
|
return nil, fmt.Errorf("xds: failed to unmarshal resource in LDS response: %v", err)
|
||||||
|
}
|
||||||
|
logger.Infof("Resource with name: %v, type: %T, contains: %v", lis.GetName(), lis, lis)
|
||||||
|
routeName, err := getRouteConfigNameFromListener(lis, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
update[lis.GetName()] = ListenerUpdate{RouteConfigName: routeName}
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRouteConfigNameFromListener checks if the provided Listener proto meets
|
||||||
|
// the expected criteria. If so, it returns a non-empty routeConfigName.
|
||||||
|
func getRouteConfigNameFromListener(lis *v3listenerpb.Listener, logger *grpclog.PrefixLogger) (string, error) {
|
||||||
|
if lis.GetApiListener() == nil {
|
||||||
|
return "", fmt.Errorf("xds: no api_listener field in LDS response %+v", lis)
|
||||||
|
}
|
||||||
|
apiLisAny := lis.GetApiListener().GetApiListener()
|
||||||
|
if t := apiLisAny.GetTypeUrl(); t != version.V3HTTPConnManagerURL && t != version.V2HTTPConnManagerURL {
|
||||||
|
return "", fmt.Errorf("xds: unexpected resource type: %s in LDS response", t)
|
||||||
|
}
|
||||||
|
apiLis := &v3httppb.HttpConnectionManager{}
|
||||||
|
if err := proto.Unmarshal(apiLisAny.GetValue(), apiLis); err != nil {
|
||||||
|
return "", fmt.Errorf("xds: failed to unmarshal api_listner in LDS response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Resource with type %T, contains %v", apiLis, apiLis)
|
||||||
|
switch apiLis.RouteSpecifier.(type) {
|
||||||
|
case *v3httppb.HttpConnectionManager_Rds:
|
||||||
|
if apiLis.GetRds().GetConfigSource().GetAds() == nil {
|
||||||
|
return "", fmt.Errorf("xds: ConfigSource is not ADS in LDS response: %+v", lis)
|
||||||
|
}
|
||||||
|
name := apiLis.GetRds().GetRouteConfigName()
|
||||||
|
if name == "" {
|
||||||
|
return "", fmt.Errorf("xds: empty route_config_name in LDS response: %+v", lis)
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
case *v3httppb.HttpConnectionManager_RouteConfig:
|
||||||
|
// TODO: Add support for specifying the RouteConfiguration inline
|
||||||
|
// in the LDS response.
|
||||||
|
return "", fmt.Errorf("xds: LDS response contains RDS config inline. Not supported for now: %+v", apiLis)
|
||||||
|
case nil:
|
||||||
|
return "", fmt.Errorf("xds: no RouteSpecifier in received LDS response: %+v", apiLis)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("xds: unsupported type %T for RouteSpecifier in received LDS response", apiLis.RouteSpecifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalRouteConfig processes resources received in an RDS response,
|
||||||
|
// validates them, and transforms them into a native struct which contains only
|
||||||
|
// fields we are interested in. The provided hostname determines the route
|
||||||
|
// configuration resources of interest.
|
||||||
|
func UnmarshalRouteConfig(resources []*anypb.Any, hostname string, logger *grpclog.PrefixLogger) (map[string]RouteConfigUpdate, error) {
|
||||||
|
update := make(map[string]RouteConfigUpdate)
|
||||||
|
for _, r := range resources {
|
||||||
|
if t := r.GetTypeUrl(); t != version.V2RouteConfigURL && t != version.V3RouteConfigURL {
|
||||||
|
return nil, fmt.Errorf("xds: unexpected resource type: %s in RDS response", t)
|
||||||
|
}
|
||||||
|
rc := &v3routepb.RouteConfiguration{}
|
||||||
|
if err := proto.Unmarshal(r.GetValue(), rc); err != nil {
|
||||||
|
return nil, fmt.Errorf("xds: failed to unmarshal resource in RDS response: %v", err)
|
||||||
|
}
|
||||||
|
logger.Infof("Resource with name: %v, type: %T, contains: %v. Picking routes for current watching hostname %v", rc.GetName(), rc, rc, hostname)
|
||||||
|
|
||||||
|
// Use the hostname (resourceName for LDS) to find the routes.
|
||||||
|
u, err := generateRDSUpdateFromRouteConfiguration(rc, hostname, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("xds: received invalid RouteConfiguration in RDS response: %+v with err: %v", rc, err)
|
||||||
|
}
|
||||||
|
update[rc.GetName()] = u
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRDSUpdateFromRouteConfiguration checks if the provided
|
||||||
|
// RouteConfiguration meets the expected criteria. If so, it returns a
|
||||||
|
// RouteConfigUpdate with nil error.
|
||||||
|
//
|
||||||
|
// A RouteConfiguration resource is considered valid when only if it contains a
|
||||||
|
// VirtualHost whose domain field matches the server name from the URI passed
|
||||||
|
// to the gRPC channel, and it contains a clusterName or a weighted cluster.
|
||||||
|
//
|
||||||
|
// The RouteConfiguration includes a list of VirtualHosts, which may have zero
|
||||||
|
// or more elements. We are interested in the element whose domains field
|
||||||
|
// matches the server name specified in the "xds:" URI. The only field in the
|
||||||
|
// VirtualHost proto that the we are interested in is the list of routes. We
|
||||||
|
// only look at the last route in the list (the default route), whose match
|
||||||
|
// field must be empty and whose route field must be set. Inside that route
|
||||||
|
// message, the cluster field will contain the clusterName or weighted clusters
|
||||||
|
// we are looking for.
|
||||||
|
func generateRDSUpdateFromRouteConfiguration(rc *v3routepb.RouteConfiguration, host string, logger *grpclog.PrefixLogger) (RouteConfigUpdate, error) {
|
||||||
|
//
|
||||||
|
// Currently this returns "" on error, and the caller will return an error.
|
||||||
|
// But the error doesn't contain details of why the response is invalid
|
||||||
|
// (mismatch domain or empty route).
|
||||||
|
//
|
||||||
|
// For logging purposes, we can log in line. But if we want to populate
|
||||||
|
// error details for nack, a detailed error needs to be returned.
|
||||||
|
vh := findBestMatchingVirtualHost(host, rc.GetVirtualHosts())
|
||||||
|
if vh == nil {
|
||||||
|
// No matching virtual host found.
|
||||||
|
return RouteConfigUpdate{}, fmt.Errorf("no matching virtual host found")
|
||||||
|
}
|
||||||
|
if len(vh.Routes) == 0 {
|
||||||
|
// The matched virtual host has no routes, this is invalid because there
|
||||||
|
// should be at least one default route.
|
||||||
|
return RouteConfigUpdate{}, fmt.Errorf("matched virtual host has no routes")
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := routesProtoToSlice(vh.Routes, logger)
|
||||||
|
if err != nil {
|
||||||
|
return RouteConfigUpdate{}, fmt.Errorf("received route is invalid: %v", err)
|
||||||
|
}
|
||||||
|
return RouteConfigUpdate{Routes: routes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func routesProtoToSlice(routes []*v3routepb.Route, logger *grpclog.PrefixLogger) ([]*Route, error) {
|
||||||
|
var routesRet []*Route
|
||||||
|
|
||||||
|
for _, r := range routes {
|
||||||
|
match := r.GetMatch()
|
||||||
|
if match == nil {
|
||||||
|
return nil, fmt.Errorf("route %+v doesn't have a match", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(match.GetQueryParameters()) != 0 {
|
||||||
|
// Ignore route with query parameters.
|
||||||
|
logger.Warningf("route %+v has query parameter matchers, the route will be ignored", r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if caseSensitive := match.GetCaseSensitive(); caseSensitive != nil && !caseSensitive.Value {
|
||||||
|
return nil, fmt.Errorf("route %+v has case-sensitive false", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pathSp := match.GetPathSpecifier()
|
||||||
|
if pathSp == nil {
|
||||||
|
return nil, fmt.Errorf("route %+v doesn't have a path specifier", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
var route Route
|
||||||
|
switch pt := pathSp.(type) {
|
||||||
|
case *v3routepb.RouteMatch_Prefix:
|
||||||
|
route.Prefix = &pt.Prefix
|
||||||
|
case *v3routepb.RouteMatch_Path:
|
||||||
|
route.Path = &pt.Path
|
||||||
|
case *v3routepb.RouteMatch_SafeRegex:
|
||||||
|
route.Regex = &pt.SafeRegex.Regex
|
||||||
|
default:
|
||||||
|
logger.Warningf("route %+v has an unrecognized path specifier: %+v", r, pt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range match.GetHeaders() {
|
||||||
|
var header HeaderMatcher
|
||||||
|
switch ht := h.GetHeaderMatchSpecifier().(type) {
|
||||||
|
case *v3routepb.HeaderMatcher_ExactMatch:
|
||||||
|
header.ExactMatch = &ht.ExactMatch
|
||||||
|
case *v3routepb.HeaderMatcher_SafeRegexMatch:
|
||||||
|
header.RegexMatch = &ht.SafeRegexMatch.Regex
|
||||||
|
case *v3routepb.HeaderMatcher_RangeMatch:
|
||||||
|
header.RangeMatch = &Int64Range{
|
||||||
|
Start: ht.RangeMatch.Start,
|
||||||
|
End: ht.RangeMatch.End,
|
||||||
|
}
|
||||||
|
case *v3routepb.HeaderMatcher_PresentMatch:
|
||||||
|
header.PresentMatch = &ht.PresentMatch
|
||||||
|
case *v3routepb.HeaderMatcher_PrefixMatch:
|
||||||
|
header.PrefixMatch = &ht.PrefixMatch
|
||||||
|
case *v3routepb.HeaderMatcher_SuffixMatch:
|
||||||
|
header.SuffixMatch = &ht.SuffixMatch
|
||||||
|
default:
|
||||||
|
logger.Warningf("route %+v has an unrecognized header matcher: %+v", r, ht)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
header.Name = h.GetName()
|
||||||
|
invert := h.GetInvertMatch()
|
||||||
|
header.InvertMatch = &invert
|
||||||
|
route.Headers = append(route.Headers, &header)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fr := match.GetRuntimeFraction(); fr != nil {
|
||||||
|
d := fr.GetDefaultValue()
|
||||||
|
n := d.GetNumerator()
|
||||||
|
switch d.GetDenominator() {
|
||||||
|
case v3typepb.FractionalPercent_HUNDRED:
|
||||||
|
n *= 10000
|
||||||
|
case v3typepb.FractionalPercent_TEN_THOUSAND:
|
||||||
|
n *= 100
|
||||||
|
case v3typepb.FractionalPercent_MILLION:
|
||||||
|
}
|
||||||
|
route.Fraction = &n
|
||||||
|
}
|
||||||
|
|
||||||
|
clusters := make(map[string]uint32)
|
||||||
|
switch a := r.GetRoute().GetClusterSpecifier().(type) {
|
||||||
|
case *v3routepb.RouteAction_Cluster:
|
||||||
|
clusters[a.Cluster] = 1
|
||||||
|
case *v3routepb.RouteAction_WeightedClusters:
|
||||||
|
wcs := a.WeightedClusters
|
||||||
|
var totalWeight uint32
|
||||||
|
for _, c := range wcs.Clusters {
|
||||||
|
w := c.GetWeight().GetValue()
|
||||||
|
clusters[c.GetName()] = w
|
||||||
|
totalWeight += w
|
||||||
|
}
|
||||||
|
if totalWeight != wcs.GetTotalWeight().GetValue() {
|
||||||
|
return nil, fmt.Errorf("route %+v, action %+v, weights of clusters do not add up to total total weight, got: %v, want %v", r, a, wcs.GetTotalWeight().GetValue(), totalWeight)
|
||||||
|
}
|
||||||
|
case *v3routepb.RouteAction_ClusterHeader:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
route.Action = clusters
|
||||||
|
routesRet = append(routesRet, &route)
|
||||||
|
}
|
||||||
|
return routesRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type domainMatchType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
domainMatchTypeInvalid domainMatchType = iota
|
||||||
|
domainMatchTypeUniversal
|
||||||
|
domainMatchTypePrefix
|
||||||
|
domainMatchTypeSuffix
|
||||||
|
domainMatchTypeExact
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exact > Suffix > Prefix > Universal > Invalid.
|
||||||
|
func (t domainMatchType) betterThan(b domainMatchType) bool {
|
||||||
|
return t > b
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchTypeForDomain(d string) domainMatchType {
|
||||||
|
if d == "" {
|
||||||
|
return domainMatchTypeInvalid
|
||||||
|
}
|
||||||
|
if d == "*" {
|
||||||
|
return domainMatchTypeUniversal
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(d, "*") {
|
||||||
|
return domainMatchTypeSuffix
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(d, "*") {
|
||||||
|
return domainMatchTypePrefix
|
||||||
|
}
|
||||||
|
if strings.Contains(d, "*") {
|
||||||
|
return domainMatchTypeInvalid
|
||||||
|
}
|
||||||
|
return domainMatchTypeExact
|
||||||
|
}
|
||||||
|
|
||||||
|
func match(domain, host string) (domainMatchType, bool) {
|
||||||
|
switch typ := matchTypeForDomain(domain); typ {
|
||||||
|
case domainMatchTypeInvalid:
|
||||||
|
return typ, false
|
||||||
|
case domainMatchTypeUniversal:
|
||||||
|
return typ, true
|
||||||
|
case domainMatchTypePrefix:
|
||||||
|
// abc.*
|
||||||
|
return typ, strings.HasPrefix(host, strings.TrimSuffix(domain, "*"))
|
||||||
|
case domainMatchTypeSuffix:
|
||||||
|
// *.123
|
||||||
|
return typ, strings.HasSuffix(host, strings.TrimPrefix(domain, "*"))
|
||||||
|
case domainMatchTypeExact:
|
||||||
|
return typ, domain == host
|
||||||
|
default:
|
||||||
|
return domainMatchTypeInvalid, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findBestMatchingVirtualHost returns the virtual host whose domains field best
|
||||||
|
// matches host
|
||||||
|
//
|
||||||
|
// The domains field support 4 different matching pattern types:
|
||||||
|
// - Exact match
|
||||||
|
// - Suffix match (e.g. “*ABC”)
|
||||||
|
// - Prefix match (e.g. “ABC*)
|
||||||
|
// - Universal match (e.g. “*”)
|
||||||
|
//
|
||||||
|
// The best match is defined as:
|
||||||
|
// - A match is better if it’s matching pattern type is better
|
||||||
|
// - Exact match > suffix match > prefix match > universal match
|
||||||
|
// - If two matches are of the same pattern type, the longer match is better
|
||||||
|
// - This is to compare the length of the matching pattern, e.g. “*ABCDE” >
|
||||||
|
// “*ABC”
|
||||||
|
func findBestMatchingVirtualHost(host string, vHosts []*v3routepb.VirtualHost) *v3routepb.VirtualHost {
|
||||||
|
var (
|
||||||
|
matchVh *v3routepb.VirtualHost
|
||||||
|
matchType = domainMatchTypeInvalid
|
||||||
|
matchLen int
|
||||||
|
)
|
||||||
|
for _, vh := range vHosts {
|
||||||
|
for _, domain := range vh.GetDomains() {
|
||||||
|
typ, matched := match(domain, host)
|
||||||
|
if typ == domainMatchTypeInvalid {
|
||||||
|
// The rds response is invalid.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched {
|
||||||
|
// The previous match has better type, or the previous match has
|
||||||
|
// better length, or this domain isn't a match.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchVh = vh
|
||||||
|
matchType = typ
|
||||||
|
matchLen = len(domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchVh
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalCluster processes resources received in an CDS response, validates
|
||||||
|
// them, and transforms them into a native struct which contains only fields we
|
||||||
|
// are interested in.
|
||||||
|
func UnmarshalCluster(resources []*anypb.Any, logger *grpclog.PrefixLogger) (map[string]ClusterUpdate, error) {
|
||||||
|
update := make(map[string]ClusterUpdate)
|
||||||
|
for _, r := range resources {
|
||||||
|
if t := r.GetTypeUrl(); t != version.V2ClusterURL && t != version.V3ClusterURL {
|
||||||
|
return nil, fmt.Errorf("xds: unexpected resource type: %s in CDS response", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster := &v3clusterpb.Cluster{}
|
||||||
|
if err := proto.Unmarshal(r.GetValue(), cluster); err != nil {
|
||||||
|
return nil, fmt.Errorf("xds: failed to unmarshal resource in CDS response: %v", err)
|
||||||
|
}
|
||||||
|
logger.Infof("Resource with name: %v, type: %T, contains: %v", cluster.GetName(), cluster, cluster)
|
||||||
|
cu, err := validateCluster(cluster)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the Cluster message in the CDS response did not contain a
|
||||||
|
// serviceName, we will just use the clusterName for EDS.
|
||||||
|
if cu.ServiceName == "" {
|
||||||
|
cu.ServiceName = cluster.GetName()
|
||||||
|
}
|
||||||
|
logger.Debugf("Resource with name %v, value %+v added to cache", cluster.GetName(), cu)
|
||||||
|
update[cluster.GetName()] = cu
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCluster(cluster *v3clusterpb.Cluster) (ClusterUpdate, error) {
|
||||||
|
emptyUpdate := ClusterUpdate{ServiceName: "", EnableLRS: false}
|
||||||
|
switch {
|
||||||
|
case cluster.GetType() != v3clusterpb.Cluster_EDS:
|
||||||
|
return emptyUpdate, fmt.Errorf("xds: unexpected cluster type %v in response: %+v", cluster.GetType(), cluster)
|
||||||
|
case cluster.GetEdsClusterConfig().GetEdsConfig().GetAds() == nil:
|
||||||
|
return emptyUpdate, fmt.Errorf("xds: unexpected edsConfig in response: %+v", cluster)
|
||||||
|
case cluster.GetLbPolicy() != v3clusterpb.Cluster_ROUND_ROBIN:
|
||||||
|
return emptyUpdate, fmt.Errorf("xds: unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClusterUpdate{
|
||||||
|
ServiceName: cluster.GetEdsClusterConfig().GetServiceName(),
|
||||||
|
EnableLRS: cluster.GetLrsServer().GetSelf() != nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalEndpoints processes resources received in an EDS response,
|
||||||
|
// validates them, and transforms them into a native struct which contains only
|
||||||
|
// fields we are interested in.
|
||||||
|
func UnmarshalEndpoints(resources []*anypb.Any, logger *grpclog.PrefixLogger) (map[string]EndpointsUpdate, error) {
|
||||||
|
update := make(map[string]EndpointsUpdate)
|
||||||
|
for _, r := range resources {
|
||||||
|
if t := r.GetTypeUrl(); t != version.V2EndpointsURL && t != version.V3EndpointsURL {
|
||||||
|
return nil, fmt.Errorf("xds: unexpected resource type: %s in EDS response", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
cla := &v3endpointpb.ClusterLoadAssignment{}
|
||||||
|
if err := proto.Unmarshal(r.GetValue(), cla); err != nil {
|
||||||
|
return nil, fmt.Errorf("xds: failed to unmarshal resource in EDS response: %v", err)
|
||||||
|
}
|
||||||
|
logger.Infof("Resource with name: %v, type: %T, contains: %v", cla.GetClusterName(), cla, cla)
|
||||||
|
|
||||||
|
u, err := parseEDSRespProto(cla)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
update[cla.GetClusterName()] = u
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddress(socketAddress *v3corepb.SocketAddress) string {
|
||||||
|
return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDropPolicy(dropPolicy *v3endpointpb.ClusterLoadAssignment_Policy_DropOverload) OverloadDropConfig {
|
||||||
|
percentage := dropPolicy.GetDropPercentage()
|
||||||
|
var (
|
||||||
|
numerator = percentage.GetNumerator()
|
||||||
|
denominator uint32
|
||||||
|
)
|
||||||
|
switch percentage.GetDenominator() {
|
||||||
|
case v3typepb.FractionalPercent_HUNDRED:
|
||||||
|
denominator = 100
|
||||||
|
case v3typepb.FractionalPercent_TEN_THOUSAND:
|
||||||
|
denominator = 10000
|
||||||
|
case v3typepb.FractionalPercent_MILLION:
|
||||||
|
denominator = 1000000
|
||||||
|
}
|
||||||
|
return OverloadDropConfig{
|
||||||
|
Category: dropPolicy.GetCategory(),
|
||||||
|
Numerator: numerator,
|
||||||
|
Denominator: denominator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEndpoints(lbEndpoints []*v3endpointpb.LbEndpoint) []Endpoint {
|
||||||
|
endpoints := make([]Endpoint, 0, len(lbEndpoints))
|
||||||
|
for _, lbEndpoint := range lbEndpoints {
|
||||||
|
endpoints = append(endpoints, Endpoint{
|
||||||
|
HealthStatus: EndpointHealthStatus(lbEndpoint.GetHealthStatus()),
|
||||||
|
Address: parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress()),
|
||||||
|
Weight: lbEndpoint.GetLoadBalancingWeight().GetValue(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return endpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate, error) {
|
||||||
|
ret := EndpointsUpdate{}
|
||||||
|
for _, dropPolicy := range m.GetPolicy().GetDropOverloads() {
|
||||||
|
ret.Drops = append(ret.Drops, parseDropPolicy(dropPolicy))
|
||||||
|
}
|
||||||
|
priorities := make(map[uint32]struct{})
|
||||||
|
for _, locality := range m.Endpoints {
|
||||||
|
l := locality.GetLocality()
|
||||||
|
if l == nil {
|
||||||
|
return EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality)
|
||||||
|
}
|
||||||
|
lid := internal.LocalityID{
|
||||||
|
Region: l.Region,
|
||||||
|
Zone: l.Zone,
|
||||||
|
SubZone: l.SubZone,
|
||||||
|
}
|
||||||
|
priority := locality.GetPriority()
|
||||||
|
priorities[priority] = struct{}{}
|
||||||
|
ret.Localities = append(ret.Localities, Locality{
|
||||||
|
ID: lid,
|
||||||
|
Endpoints: parseEndpoints(locality.GetLbEndpoints()),
|
||||||
|
Weight: locality.GetLoadBalancingWeight().GetValue(),
|
||||||
|
Priority: priority,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := 0; i < len(priorities); i++ {
|
||||||
|
if _, ok := priorities[uint32(i)]; !ok {
|
||||||
|
return EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/internal/grpctest"
|
||||||
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
|
"google.golang.org/grpc/xds/internal/client/bootstrap"
|
||||||
|
_ "google.golang.org/grpc/xds/internal/client/v2" // Register the v2 API client.
|
||||||
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type s struct {
|
||||||
|
grpctest.Tester
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
grpctest.RunSubTests(t, s{})
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testXDSServer = "xds-server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func clientOpts(balancerName string) xdsclient.Options {
|
||||||
|
return xdsclient.Options{
|
||||||
|
Config: bootstrap.Config{
|
||||||
|
BalancerName: balancerName,
|
||||||
|
Creds: grpc.WithInsecure(),
|
||||||
|
NodeProto: testutils.EmptyNodeProtoV2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestNew(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
opts xdsclient.Options
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "empty-opts", opts: xdsclient.Options{}, wantErr: true},
|
||||||
|
{
|
||||||
|
name: "empty-balancer-name",
|
||||||
|
opts: xdsclient.Options{
|
||||||
|
Config: bootstrap.Config{
|
||||||
|
Creds: grpc.WithInsecure(),
|
||||||
|
NodeProto: testutils.EmptyNodeProtoV2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty-dial-creds",
|
||||||
|
opts: xdsclient.Options{
|
||||||
|
Config: bootstrap.Config{
|
||||||
|
BalancerName: testXDSServer,
|
||||||
|
NodeProto: testutils.EmptyNodeProtoV2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty-node-proto",
|
||||||
|
opts: xdsclient.Options{
|
||||||
|
Config: bootstrap.Config{
|
||||||
|
BalancerName: testXDSServer,
|
||||||
|
Creds: grpc.WithInsecure(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "node-proto-version-mismatch",
|
||||||
|
opts: xdsclient.Options{
|
||||||
|
Config: bootstrap.Config{
|
||||||
|
BalancerName: testXDSServer,
|
||||||
|
Creds: grpc.WithInsecure(),
|
||||||
|
NodeProto: testutils.EmptyNodeProtoV3,
|
||||||
|
TransportAPI: version.TransportV2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
// TODO(easwars): Add cases for v3 API client.
|
||||||
|
{
|
||||||
|
name: "happy-case",
|
||||||
|
opts: clientOpts(testXDSServer),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
c, err := xdsclient.New(test.opts)
|
||||||
|
if (err != nil) != test.wantErr {
|
||||||
|
t.Fatalf("New(%+v) = %v, wantErr: %v", test.opts, err, test.wantErr)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* 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 (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type watchHandleTestcase struct {
|
|
||||||
typeURL string
|
|
||||||
resourceName string
|
|
||||||
|
|
||||||
responseToHandle *xdspb.DiscoveryResponse
|
|
||||||
wantHandleErr bool
|
|
||||||
wantUpdate interface{}
|
|
||||||
wantUpdateErr bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type testUpdateReceiver struct {
|
|
||||||
f func(typeURL string, d map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testUpdateReceiver) newLDSUpdate(d map[string]ldsUpdate) {
|
|
||||||
dd := make(map[string]interface{})
|
|
||||||
for k, v := range d {
|
|
||||||
dd[k] = v
|
|
||||||
}
|
|
||||||
t.newUpdate(ldsURL, dd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testUpdateReceiver) newRDSUpdate(d map[string]rdsUpdate) {
|
|
||||||
dd := make(map[string]interface{})
|
|
||||||
for k, v := range d {
|
|
||||||
dd[k] = v
|
|
||||||
}
|
|
||||||
t.newUpdate(rdsURL, dd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testUpdateReceiver) newCDSUpdate(d map[string]ClusterUpdate) {
|
|
||||||
dd := make(map[string]interface{})
|
|
||||||
for k, v := range d {
|
|
||||||
dd[k] = v
|
|
||||||
}
|
|
||||||
t.newUpdate(cdsURL, dd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testUpdateReceiver) newEDSUpdate(d map[string]EndpointsUpdate) {
|
|
||||||
dd := make(map[string]interface{})
|
|
||||||
for k, v := range d {
|
|
||||||
dd[k] = v
|
|
||||||
}
|
|
||||||
t.newUpdate(edsURL, dd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testUpdateReceiver) newUpdate(typeURL string, d map[string]interface{}) {
|
|
||||||
t.f(typeURL, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testWatchHandle is called to test response handling for each xDS.
|
|
||||||
//
|
|
||||||
// It starts the xDS watch as configured in test, waits for the fake xds server
|
|
||||||
// to receive the request (so watch callback is installed), and calls
|
|
||||||
// handleXDSResp with responseToHandle (if it's set). It then compares the
|
|
||||||
// update received by watch callback with the expected results.
|
|
||||||
func testWatchHandle(t *testing.T, test *watchHandleTestcase) {
|
|
||||||
fakeServer, cc, cleanup := startServerAndGetCC(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
type updateErr struct {
|
|
||||||
u interface{}
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
gotUpdateCh := testutils.NewChannel()
|
|
||||||
|
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
|
||||||
f: func(typeURL string, d map[string]interface{}) {
|
|
||||||
if typeURL == test.typeURL {
|
|
||||||
if u, ok := d[test.resourceName]; ok {
|
|
||||||
gotUpdateCh.Send(updateErr{u, nil})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
|
||||||
defer v2c.close()
|
|
||||||
|
|
||||||
// RDS needs an existin LDS watch for the hostname.
|
|
||||||
if test.typeURL == rdsURL {
|
|
||||||
doLDS(t, v2c, fakeServer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the watcher, this will also trigger the v2Client to send the xDS
|
|
||||||
// request.
|
|
||||||
v2c.addWatch(test.typeURL, test.resourceName)
|
|
||||||
|
|
||||||
// Wait till the request makes it to the fakeServer. This ensures that
|
|
||||||
// the watch request has been processed by the v2Client.
|
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
|
||||||
t.Fatalf("Timeout waiting for an xDS request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directly push the response through a call to handleXDSResp. This bypasses
|
|
||||||
// the fakeServer, so it's only testing the handle logic. Client response
|
|
||||||
// processing is covered elsewhere.
|
|
||||||
//
|
|
||||||
// Also note that this won't trigger ACK, so there's no need to clear the
|
|
||||||
// request channel afterwards.
|
|
||||||
var handleXDSResp func(response *xdspb.DiscoveryResponse) error
|
|
||||||
switch test.typeURL {
|
|
||||||
case ldsURL:
|
|
||||||
handleXDSResp = v2c.handleLDSResponse
|
|
||||||
case rdsURL:
|
|
||||||
handleXDSResp = v2c.handleRDSResponse
|
|
||||||
case cdsURL:
|
|
||||||
handleXDSResp = v2c.handleCDSResponse
|
|
||||||
case edsURL:
|
|
||||||
handleXDSResp = v2c.handleEDSResponse
|
|
||||||
}
|
|
||||||
if err := handleXDSResp(test.responseToHandle); (err != nil) != test.wantHandleErr {
|
|
||||||
t.Fatalf("v2c.handleRDSResponse() returned err: %v, wantErr: %v", err, test.wantHandleErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the test doesn't expect the callback to be invoked, verify that no
|
|
||||||
// update or error is pushed to the callback.
|
|
||||||
//
|
|
||||||
// Cannot directly compare test.wantUpdate with nil (typed vs non-typed nil:
|
|
||||||
// https://golang.org/doc/faq#nil_error).
|
|
||||||
if c := test.wantUpdate; c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil()) {
|
|
||||||
update, err := gotUpdateCh.Receive()
|
|
||||||
if err == testutils.ErrRecvTimeout {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Fatalf("Unexpected update: +%v", update)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantUpdate := reflect.ValueOf(test.wantUpdate).Elem().Interface()
|
|
||||||
uErr, err := gotUpdateCh.Receive()
|
|
||||||
if err == testutils.ErrRecvTimeout {
|
|
||||||
t.Fatal("Timeout expecting xDS update")
|
|
||||||
}
|
|
||||||
gotUpdate := uErr.(updateErr).u
|
|
||||||
opt := cmp.AllowUnexported(rdsUpdate{}, ldsUpdate{}, ClusterUpdate{}, EndpointsUpdate{})
|
|
||||||
if diff := cmp.Diff(gotUpdate, wantUpdate, opt); diff != "" {
|
|
||||||
t.Fatalf("got update : %+v, want %+v, diff: %s", gotUpdate, wantUpdate, diff)
|
|
||||||
}
|
|
||||||
gotUpdateErr := uErr.(updateErr).err
|
|
||||||
if (gotUpdateErr != nil) != test.wantUpdateErr {
|
|
||||||
t.Fatalf("got xDS update error {%v}, wantErr: %v", gotUpdateErr, test.wantUpdateErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// startServerAndGetCC starts a fake XDS server and also returns a ClientConn
|
|
||||||
// connected to it.
|
|
||||||
func startServerAndGetCC(t *testing.T) (*fakeserver.Server, *grpc.ClientConn, func()) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
fs, sCleanup, err := fakeserver.StartServer()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to start fake xDS server: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cc, ccCleanup, err := fs.XDSClientConn()
|
|
||||||
if err != nil {
|
|
||||||
sCleanup()
|
|
||||||
t.Fatalf("Failed to get a clientConn to the fake xDS server: %v", err)
|
|
||||||
}
|
|
||||||
return fs, cc, func() {
|
|
||||||
sCleanup()
|
|
||||||
ccCleanup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForNilErr waits for a nil error value to be received on the
|
|
||||||
// provided channel.
|
|
||||||
func waitForNilErr(t *testing.T, ch *testutils.Channel) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
val, err := ch.Receive()
|
|
||||||
if err == testutils.ErrRecvTimeout {
|
|
||||||
t.Fatalf("Timeout expired when expecting update")
|
|
||||||
}
|
|
||||||
if val != nil {
|
|
||||||
if cbErr := val.(error); cbErr != nil {
|
|
||||||
t.Fatal(cbErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,38 +16,68 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
// Package v2 provides xDS v2 transport protocol specific functionality.
|
||||||
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/internal/buffer"
|
"google.golang.org/grpc/internal/buffer"
|
||||||
"google.golang.org/grpc/internal/grpclog"
|
"google.golang.org/grpc/internal/grpclog"
|
||||||
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
|
v2adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type adsStream adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient
|
func init() {
|
||||||
|
xdsclient.RegisterAPIClientBuilder(clientBuilder{})
|
||||||
var _ xdsv2Client = &v2Client{}
|
|
||||||
|
|
||||||
// updateHandler handles the update (parsed from xds responses). It's
|
|
||||||
// implemented by the upper level Client.
|
|
||||||
//
|
|
||||||
// It's an interface to be overridden in test.
|
|
||||||
type updateHandler interface {
|
|
||||||
newLDSUpdate(d map[string]ldsUpdate)
|
|
||||||
newRDSUpdate(d map[string]rdsUpdate)
|
|
||||||
newCDSUpdate(d map[string]ClusterUpdate)
|
|
||||||
newEDSUpdate(d map[string]EndpointsUpdate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2Client performs the actual xDS RPCs using the xDS v2 API. It creates a
|
type clientBuilder struct{}
|
||||||
|
|
||||||
|
func (clientBuilder) Build(cc *grpc.ClientConn, opts xdsclient.BuildOptions) (xdsclient.APIClient, error) {
|
||||||
|
return newClient(cc, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clientBuilder) Version() version.TransportAPI {
|
||||||
|
return version.TransportV2
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(cc *grpc.ClientConn, opts xdsclient.BuildOptions) (xdsclient.APIClient, error) {
|
||||||
|
nodeProto, ok := opts.NodeProto.(*v2corepb.Node)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("xds: unsupported Node proto type: %T, want %T", opts.NodeProto, v2corepb.Node{})
|
||||||
|
}
|
||||||
|
v2c := &client{
|
||||||
|
cc: cc,
|
||||||
|
parent: opts.Parent,
|
||||||
|
nodeProto: nodeProto,
|
||||||
|
backoff: opts.Backoff,
|
||||||
|
logger: opts.Logger,
|
||||||
|
|
||||||
|
streamCh: make(chan adsStream, 1),
|
||||||
|
sendCh: buffer.NewUnbounded(),
|
||||||
|
|
||||||
|
watchMap: make(map[string]map[string]bool),
|
||||||
|
versionMap: make(map[string]string),
|
||||||
|
nonceMap: make(map[string]string),
|
||||||
|
}
|
||||||
|
v2c.ctx, v2c.cancelCtx = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
go v2c.run()
|
||||||
|
return v2c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type adsStream v2adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient
|
||||||
|
|
||||||
|
// client performs the actual xDS RPCs using the xDS v2 API. It creates a
|
||||||
// single ADS stream on which the different types of xDS requests and responses
|
// single ADS stream on which the different types of xDS requests and responses
|
||||||
// are multiplexed.
|
// are multiplexed.
|
||||||
//
|
//
|
||||||
|
|
@ -55,18 +85,14 @@ type updateHandler interface {
|
||||||
// and do ACK/NACK. It's a naive implementation that sends whatever the upper
|
// and do ACK/NACK. It's a naive implementation that sends whatever the upper
|
||||||
// layer tells it to send. It will call the callback with everything in every
|
// layer tells it to send. It will call the callback with everything in every
|
||||||
// response. It doesn't keep a cache of responses, or check for duplicates.
|
// response. It doesn't keep a cache of responses, or check for duplicates.
|
||||||
//
|
type client struct {
|
||||||
// The reason for splitting this out from the top level xdsClient object is
|
|
||||||
// because there is already an xDS v3Aplha API in development. If and when we
|
|
||||||
// want to switch to that, this separation will ease that process.
|
|
||||||
type v2Client struct {
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelCtx context.CancelFunc
|
cancelCtx context.CancelFunc
|
||||||
parent updateHandler
|
parent xdsclient.UpdateHandler
|
||||||
|
|
||||||
// ClientConn to the xDS gRPC server. Owned by the parent xdsClient.
|
// ClientConn to the xDS gRPC server. Owned by the parent xdsClient.
|
||||||
cc *grpc.ClientConn
|
cc *grpc.ClientConn
|
||||||
nodeProto *corepb.Node
|
nodeProto *v2corepb.Node
|
||||||
backoff func(int) time.Duration
|
backoff func(int) time.Duration
|
||||||
|
|
||||||
logger *grpclog.PrefixLogger
|
logger *grpclog.PrefixLogger
|
||||||
|
|
@ -102,38 +128,31 @@ type v2Client struct {
|
||||||
hostname string
|
hostname string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newV2Client creates a new v2Client initialized with the passed arguments.
|
func (v2c *client) AddWatch(resourceType, resourceName string) {
|
||||||
func newV2Client(parent updateHandler, cc *grpc.ClientConn, nodeProto *corepb.Node, backoff func(int) time.Duration, logger *grpclog.PrefixLogger) *v2Client {
|
v2c.sendCh.Put(&watchAction{
|
||||||
v2c := &v2Client{
|
typeURL: resourceType,
|
||||||
cc: cc,
|
remove: false,
|
||||||
parent: parent,
|
resource: resourceName,
|
||||||
nodeProto: nodeProto,
|
})
|
||||||
backoff: backoff,
|
}
|
||||||
|
|
||||||
logger: logger,
|
func (v2c *client) RemoveWatch(resourceType, resourceName string) {
|
||||||
|
v2c.sendCh.Put(&watchAction{
|
||||||
streamCh: make(chan adsStream, 1),
|
typeURL: resourceType,
|
||||||
sendCh: buffer.NewUnbounded(),
|
remove: true,
|
||||||
|
resource: resourceName,
|
||||||
watchMap: make(map[string]map[string]bool),
|
})
|
||||||
versionMap: make(map[string]string),
|
|
||||||
nonceMap: make(map[string]string),
|
|
||||||
}
|
|
||||||
v2c.ctx, v2c.cancelCtx = context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
go v2c.run()
|
|
||||||
return v2c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// close cleans up resources and goroutines allocated by this client.
|
// close cleans up resources and goroutines allocated by this client.
|
||||||
func (v2c *v2Client) close() {
|
func (v2c *client) Close() {
|
||||||
v2c.cancelCtx()
|
v2c.cancelCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
// run starts an ADS stream (and backs off exponentially, if the previous
|
// run starts an ADS stream (and backs off exponentially, if the previous
|
||||||
// stream failed without receiving a single reply) and runs the sender and
|
// stream failed without receiving a single reply) and runs the sender and
|
||||||
// receiver routines to send and receive data from the stream respectively.
|
// receiver routines to send and receive data from the stream respectively.
|
||||||
func (v2c *v2Client) run() {
|
func (v2c *client) run() {
|
||||||
go v2c.send()
|
go v2c.send()
|
||||||
// TODO: start a goroutine monitoring ClientConn's connectivity state, and
|
// TODO: start a goroutine monitoring ClientConn's connectivity state, and
|
||||||
// report error (and log) when stats is transient failure.
|
// report error (and log) when stats is transient failure.
|
||||||
|
|
@ -159,7 +178,7 @@ func (v2c *v2Client) run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
retries++
|
retries++
|
||||||
cli := adsgrpc.NewAggregatedDiscoveryServiceClient(v2c.cc)
|
cli := v2adsgrpc.NewAggregatedDiscoveryServiceClient(v2c.cc)
|
||||||
stream, err := cli.StreamAggregatedResources(v2c.ctx, grpc.WaitForReady(true))
|
stream, err := cli.StreamAggregatedResources(v2c.ctx, grpc.WaitForReady(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v2c.logger.Warningf("xds: ADS stream creation failed: %v", err)
|
v2c.logger.Warningf("xds: ADS stream creation failed: %v", err)
|
||||||
|
|
@ -187,8 +206,8 @@ func (v2c *v2Client) run() {
|
||||||
// - If this is an ack, version will be the version from the response
|
// - If this is an ack, version will be the version from the response
|
||||||
// - If this is a nack, version will be the previous acked version (from
|
// - If this is a nack, version will be the previous acked version (from
|
||||||
// versionMap). If there was no ack before, it will be an empty string
|
// versionMap). If there was no ack before, it will be an empty string
|
||||||
func (v2c *v2Client) sendRequest(stream adsStream, resourceNames []string, typeURL, version, nonce string) bool {
|
func (v2c *client) sendRequest(stream adsStream, resourceNames []string, typeURL, version, nonce string) bool {
|
||||||
req := &xdspb.DiscoveryRequest{
|
req := &v2xdspb.DiscoveryRequest{
|
||||||
Node: v2c.nodeProto,
|
Node: v2c.nodeProto,
|
||||||
TypeUrl: typeURL,
|
TypeUrl: typeURL,
|
||||||
ResourceNames: resourceNames,
|
ResourceNames: resourceNames,
|
||||||
|
|
@ -210,7 +229,7 @@ func (v2c *v2Client) sendRequest(stream adsStream, resourceNames []string, typeU
|
||||||
// that here because the stream has just started and Send() usually returns
|
// that here because the stream has just started and Send() usually returns
|
||||||
// quickly (once it pushes the message onto the transport layer) and is only
|
// quickly (once it pushes the message onto the transport layer) and is only
|
||||||
// ever blocked if we don't have enough flow control quota.
|
// ever blocked if we don't have enough flow control quota.
|
||||||
func (v2c *v2Client) sendExisting(stream adsStream) bool {
|
func (v2c *client) sendExisting(stream adsStream) bool {
|
||||||
v2c.mu.Lock()
|
v2c.mu.Lock()
|
||||||
defer v2c.mu.Unlock()
|
defer v2c.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -236,7 +255,7 @@ type watchAction struct {
|
||||||
// processWatchInfo pulls the fields needed by the request from a watchAction.
|
// processWatchInfo pulls the fields needed by the request from a watchAction.
|
||||||
//
|
//
|
||||||
// It also updates the watch map in v2c.
|
// It also updates the watch map in v2c.
|
||||||
func (v2c *v2Client) processWatchInfo(t *watchAction) (target []string, typeURL, version, nonce string, send bool) {
|
func (v2c *client) processWatchInfo(t *watchAction) (target []string, typeURL, ver, nonce string, send bool) {
|
||||||
v2c.mu.Lock()
|
v2c.mu.Lock()
|
||||||
defer v2c.mu.Unlock()
|
defer v2c.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -258,7 +277,7 @@ func (v2c *v2Client) processWatchInfo(t *watchAction) (target []string, typeURL,
|
||||||
|
|
||||||
// Special handling for LDS, because RDS needs the LDS resource_name for
|
// Special handling for LDS, because RDS needs the LDS resource_name for
|
||||||
// response host matching.
|
// response host matching.
|
||||||
if t.typeURL == ldsURL {
|
if t.typeURL == version.V2ListenerURL {
|
||||||
// Set hostname to the first LDS resource_name, and reset it when the
|
// Set hostname to the first LDS resource_name, and reset it when the
|
||||||
// last LDS watch is removed. The upper level Client isn't expected to
|
// last LDS watch is removed. The upper level Client isn't expected to
|
||||||
// watchLDS more than once.
|
// watchLDS more than once.
|
||||||
|
|
@ -275,9 +294,9 @@ func (v2c *v2Client) processWatchInfo(t *watchAction) (target []string, typeURL,
|
||||||
// We don't reset version or nonce when a new watch is started. The version
|
// We don't reset version or nonce when a new watch is started. The version
|
||||||
// and nonce from previous response are carried by the request unless the
|
// and nonce from previous response are carried by the request unless the
|
||||||
// stream is recreated.
|
// stream is recreated.
|
||||||
version = v2c.versionMap[typeURL]
|
ver = v2c.versionMap[typeURL]
|
||||||
nonce = v2c.nonceMap[typeURL]
|
nonce = v2c.nonceMap[typeURL]
|
||||||
return target, typeURL, version, nonce, send
|
return target, typeURL, ver, nonce, send
|
||||||
}
|
}
|
||||||
|
|
||||||
type ackAction struct {
|
type ackAction struct {
|
||||||
|
|
@ -293,7 +312,7 @@ type ackAction struct {
|
||||||
// processAckInfo pulls the fields needed by the ack request from a ackAction.
|
// processAckInfo pulls the fields needed by the ack request from a ackAction.
|
||||||
//
|
//
|
||||||
// If no active watch is found for this ack, it returns false for send.
|
// If no active watch is found for this ack, it returns false for send.
|
||||||
func (v2c *v2Client) processAckInfo(t *ackAction, stream adsStream) (target []string, typeURL, version, nonce string, send bool) {
|
func (v2c *client) processAckInfo(t *ackAction, stream adsStream) (target []string, typeURL, version, nonce string, send bool) {
|
||||||
if t.stream != stream {
|
if t.stream != stream {
|
||||||
// If ACK's stream isn't the current sending stream, this means the ACK
|
// If ACK's stream isn't the current sending stream, this means the ACK
|
||||||
// was pushed to queue before the old stream broke, and a new stream has
|
// was pushed to queue before the old stream broke, and a new stream has
|
||||||
|
|
@ -353,7 +372,7 @@ func (v2c *v2Client) processAckInfo(t *ackAction, stream adsStream) (target []st
|
||||||
// Note that this goroutine doesn't do anything to the old stream when there's a
|
// Note that this goroutine doesn't do anything to the old stream when there's a
|
||||||
// new one. In fact, there should be only one stream in progress, and new one
|
// new one. In fact, there should be only one stream in progress, and new one
|
||||||
// should only be created when the old one fails (recv returns an error).
|
// should only be created when the old one fails (recv returns an error).
|
||||||
func (v2c *v2Client) send() {
|
func (v2c *client) send() {
|
||||||
var stream adsStream
|
var stream adsStream
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -398,7 +417,7 @@ func (v2c *v2Client) send() {
|
||||||
|
|
||||||
// recv receives xDS responses on the provided ADS stream and branches out to
|
// recv receives xDS responses on the provided ADS stream and branches out to
|
||||||
// message specific handlers.
|
// message specific handlers.
|
||||||
func (v2c *v2Client) recv(stream adsStream) bool {
|
func (v2c *client) recv(stream adsStream) bool {
|
||||||
success := false
|
success := false
|
||||||
for {
|
for {
|
||||||
resp, err := stream.Recv()
|
resp, err := stream.Recv()
|
||||||
|
|
@ -409,15 +428,20 @@ func (v2c *v2Client) recv(stream adsStream) bool {
|
||||||
}
|
}
|
||||||
v2c.logger.Infof("ADS response received, type: %v", resp.GetTypeUrl())
|
v2c.logger.Infof("ADS response received, type: %v", resp.GetTypeUrl())
|
||||||
v2c.logger.Debugf("ADS response received: %v", resp)
|
v2c.logger.Debugf("ADS response received: %v", resp)
|
||||||
|
|
||||||
|
// Note that the xDS transport protocol is versioned independently of
|
||||||
|
// the resource types, and it is supported to transfer older versions
|
||||||
|
// of resource types using new versions of the transport protocol, or
|
||||||
|
// vice-versa. Hence we need to handle v3 type_urls as well here.
|
||||||
var respHandleErr error
|
var respHandleErr error
|
||||||
switch resp.GetTypeUrl() {
|
switch resp.GetTypeUrl() {
|
||||||
case ldsURL:
|
case version.V2ListenerURL, version.V3ListenerURL:
|
||||||
respHandleErr = v2c.handleLDSResponse(resp)
|
respHandleErr = v2c.handleLDSResponse(resp)
|
||||||
case rdsURL:
|
case version.V2RouteConfigURL, version.V3RouteConfigURL:
|
||||||
respHandleErr = v2c.handleRDSResponse(resp)
|
respHandleErr = v2c.handleRDSResponse(resp)
|
||||||
case cdsURL:
|
case version.V2ClusterURL, version.V3ClusterURL:
|
||||||
respHandleErr = v2c.handleCDSResponse(resp)
|
respHandleErr = v2c.handleCDSResponse(resp)
|
||||||
case edsURL:
|
case version.V2EndpointsURL, version.V3EndpointsURL:
|
||||||
respHandleErr = v2c.handleEDSResponse(resp)
|
respHandleErr = v2c.handleEDSResponse(resp)
|
||||||
default:
|
default:
|
||||||
v2c.logger.Warningf("Resource type %v unknown in response from server", resp.GetTypeUrl())
|
v2c.logger.Warningf("Resource type %v unknown in response from server", resp.GetTypeUrl())
|
||||||
|
|
@ -446,25 +470,56 @@ func (v2c *v2Client) recv(stream adsStream) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v2c *v2Client) addWatch(resourceType, resourceName string) {
|
|
||||||
v2c.sendCh.Put(&watchAction{
|
|
||||||
typeURL: resourceType,
|
|
||||||
remove: false,
|
|
||||||
resource: resourceName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v2c *v2Client) removeWatch(resourceType, resourceName string) {
|
|
||||||
v2c.sendCh.Put(&watchAction{
|
|
||||||
typeURL: resourceType,
|
|
||||||
remove: true,
|
|
||||||
resource: resourceName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapToSlice(m map[string]bool) (ret []string) {
|
func mapToSlice(m map[string]bool) (ret []string) {
|
||||||
for i := range m {
|
for i := range m {
|
||||||
ret = append(ret, i)
|
ret = append(ret, i)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleLDSResponse processes an LDS response received from the xDS server. On
|
||||||
|
// receipt of a good response, it also invokes the registered watcher callback.
|
||||||
|
func (v2c *client) handleLDSResponse(resp *v2xdspb.DiscoveryResponse) error {
|
||||||
|
update, err := xdsclient.UnmarshalListener(resp.GetResources(), v2c.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v2c.parent.NewListeners(update)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRDSResponse processes an RDS response received from the xDS server. On
|
||||||
|
// receipt of a good response, it caches validated resources and also invokes
|
||||||
|
// the registered watcher callback.
|
||||||
|
func (v2c *client) handleRDSResponse(resp *v2xdspb.DiscoveryResponse) error {
|
||||||
|
v2c.mu.Lock()
|
||||||
|
hostname := v2c.hostname
|
||||||
|
v2c.mu.Unlock()
|
||||||
|
|
||||||
|
update, err := xdsclient.UnmarshalRouteConfig(resp.GetResources(), hostname, v2c.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v2c.parent.NewRouteConfigs(update)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCDSResponse processes an CDS response received from the xDS server. On
|
||||||
|
// receipt of a good response, it also invokes the registered watcher callback.
|
||||||
|
func (v2c *client) handleCDSResponse(resp *v2xdspb.DiscoveryResponse) error {
|
||||||
|
update, err := xdsclient.UnmarshalCluster(resp.GetResources(), v2c.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v2c.parent.NewClusters(update)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v2c *client) handleEDSResponse(resp *v2xdspb.DiscoveryResponse) error {
|
||||||
|
update, err := xdsclient.UnmarshalEndpoints(resp.GetResources(), v2c.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v2c.parent.NewEndpoints(update)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -30,42 +30,46 @@ import (
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startXDSV2Client(t *testing.T, cc *grpc.ClientConn) (v2c *v2Client, cbLDS, cbRDS, cbCDS, cbEDS *testutils.Channel, cleanup func()) {
|
func startXDSV2Client(t *testing.T, cc *grpc.ClientConn) (v2c *client, cbLDS, cbRDS, cbCDS, cbEDS *testutils.Channel, cleanup func()) {
|
||||||
cbLDS = testutils.NewChannel()
|
cbLDS = testutils.NewChannel()
|
||||||
cbRDS = testutils.NewChannel()
|
cbRDS = testutils.NewChannel()
|
||||||
cbCDS = testutils.NewChannel()
|
cbCDS = testutils.NewChannel()
|
||||||
cbEDS = testutils.NewChannel()
|
cbEDS = testutils.NewChannel()
|
||||||
v2c = newV2Client(&testUpdateReceiver{
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
f: func(typeURL string, d map[string]interface{}) {
|
f: func(typeURL string, d map[string]interface{}) {
|
||||||
t.Logf("Received %s callback with {%+v}", typeURL, d)
|
t.Logf("Received %s callback with {%+v}", typeURL, d)
|
||||||
switch typeURL {
|
switch typeURL {
|
||||||
case ldsURL:
|
case version.V2ListenerURL:
|
||||||
if _, ok := d[goodLDSTarget1]; ok {
|
if _, ok := d[goodLDSTarget1]; ok {
|
||||||
cbLDS.Send(struct{}{})
|
cbLDS.Send(struct{}{})
|
||||||
}
|
}
|
||||||
case rdsURL:
|
case version.V2RouteConfigURL:
|
||||||
if _, ok := d[goodRouteName1]; ok {
|
if _, ok := d[goodRouteName1]; ok {
|
||||||
cbRDS.Send(struct{}{})
|
cbRDS.Send(struct{}{})
|
||||||
}
|
}
|
||||||
case cdsURL:
|
case version.V2ClusterURL:
|
||||||
if _, ok := d[goodClusterName1]; ok {
|
if _, ok := d[goodClusterName1]; ok {
|
||||||
cbCDS.Send(struct{}{})
|
cbCDS.Send(struct{}{})
|
||||||
}
|
}
|
||||||
case edsURL:
|
case version.V2EndpointsURL:
|
||||||
if _, ok := d[goodEDSName]; ok {
|
if _, ok := d[goodEDSName]; ok {
|
||||||
cbEDS.Send(struct{}{})
|
cbEDS.Send(struct{}{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
t.Log("Started xds v2Client...")
|
if err != nil {
|
||||||
return v2c, cbLDS, cbRDS, cbCDS, cbEDS, v2c.close
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("Started xds client...")
|
||||||
|
return v2c, cbLDS, cbRDS, cbCDS, cbEDS, v2c.Close
|
||||||
}
|
}
|
||||||
|
|
||||||
// compareXDSRequest reads requests from channel, compare it with want.
|
// compareXDSRequest reads requests from channel, compare it with want.
|
||||||
func compareXDSRequest(ch *testutils.Channel, want *xdspb.DiscoveryRequest, version, nonce string) error {
|
func compareXDSRequest(ch *testutils.Channel, want *xdspb.DiscoveryRequest, ver, nonce string) error {
|
||||||
val, err := ch.Receive()
|
val, err := ch.Receive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -75,7 +79,7 @@ func compareXDSRequest(ch *testutils.Channel, want *xdspb.DiscoveryRequest, vers
|
||||||
return fmt.Errorf("unexpected error from request: %v", req.Err)
|
return fmt.Errorf("unexpected error from request: %v", req.Err)
|
||||||
}
|
}
|
||||||
wantClone := proto.Clone(want).(*xdspb.DiscoveryRequest)
|
wantClone := proto.Clone(want).(*xdspb.DiscoveryRequest)
|
||||||
wantClone.VersionInfo = version
|
wantClone.VersionInfo = ver
|
||||||
wantClone.ResponseNonce = nonce
|
wantClone.ResponseNonce = nonce
|
||||||
if !cmp.Equal(req.Req, wantClone, cmp.Comparer(proto.Equal)) {
|
if !cmp.Equal(req.Req, wantClone, cmp.Comparer(proto.Equal)) {
|
||||||
return fmt.Errorf("received request different from want, diff: %s", cmp.Diff(req.Req, wantClone))
|
return fmt.Errorf("received request different from want, diff: %s", cmp.Diff(req.Req, wantClone))
|
||||||
|
|
@ -83,9 +87,9 @@ func compareXDSRequest(ch *testutils.Channel, want *xdspb.DiscoveryRequest, vers
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendXDSRespWithVersion(ch chan<- *fakeserver.Response, respWithoutVersion *xdspb.DiscoveryResponse, version int) (nonce string) {
|
func sendXDSRespWithVersion(ch chan<- *fakeserver.Response, respWithoutVersion *xdspb.DiscoveryResponse, ver int) (nonce string) {
|
||||||
respToSend := proto.Clone(respWithoutVersion).(*xdspb.DiscoveryResponse)
|
respToSend := proto.Clone(respWithoutVersion).(*xdspb.DiscoveryResponse)
|
||||||
respToSend.VersionInfo = strconv.Itoa(version)
|
respToSend.VersionInfo = strconv.Itoa(ver)
|
||||||
nonce = strconv.Itoa(int(time.Now().UnixNano()))
|
nonce = strconv.Itoa(int(time.Now().UnixNano()))
|
||||||
respToSend.Nonce = nonce
|
respToSend.Nonce = nonce
|
||||||
ch <- &fakeserver.Response{Resp: respToSend}
|
ch <- &fakeserver.Response{Resp: respToSend}
|
||||||
|
|
@ -94,25 +98,25 @@ func sendXDSRespWithVersion(ch chan<- *fakeserver.Response, respWithoutVersion *
|
||||||
|
|
||||||
// startXDS calls watch to send the first request. It then sends a good response
|
// startXDS calls watch to send the first request. It then sends a good response
|
||||||
// and checks for ack.
|
// and checks for ack.
|
||||||
func startXDS(t *testing.T, xdsname string, v2c *v2Client, reqChan *testutils.Channel, req *xdspb.DiscoveryRequest, preVersion string, preNonce string) {
|
func startXDS(t *testing.T, xdsname string, v2c *client, reqChan *testutils.Channel, req *xdspb.DiscoveryRequest, preVersion string, preNonce string) {
|
||||||
var (
|
var (
|
||||||
nameToWatch, typeURLToWatch string
|
nameToWatch, typeURLToWatch string
|
||||||
)
|
)
|
||||||
switch xdsname {
|
switch xdsname {
|
||||||
case "LDS":
|
case "LDS":
|
||||||
typeURLToWatch = ldsURL
|
typeURLToWatch = version.V2ListenerURL
|
||||||
nameToWatch = goodLDSTarget1
|
nameToWatch = goodLDSTarget1
|
||||||
case "RDS":
|
case "RDS":
|
||||||
typeURLToWatch = rdsURL
|
typeURLToWatch = version.V2RouteConfigURL
|
||||||
nameToWatch = goodRouteName1
|
nameToWatch = goodRouteName1
|
||||||
case "CDS":
|
case "CDS":
|
||||||
typeURLToWatch = cdsURL
|
typeURLToWatch = version.V2ClusterURL
|
||||||
nameToWatch = goodClusterName1
|
nameToWatch = goodClusterName1
|
||||||
case "EDS":
|
case "EDS":
|
||||||
typeURLToWatch = edsURL
|
typeURLToWatch = version.V2EndpointsURL
|
||||||
nameToWatch = goodEDSName
|
nameToWatch = goodEDSName
|
||||||
}
|
}
|
||||||
v2c.addWatch(typeURLToWatch, nameToWatch)
|
v2c.AddWatch(typeURLToWatch, nameToWatch)
|
||||||
|
|
||||||
if err := compareXDSRequest(reqChan, req, preVersion, preNonce); err != nil {
|
if err := compareXDSRequest(reqChan, req, preVersion, preNonce); err != nil {
|
||||||
t.Fatalf("Failed to receive %s request: %v", xdsname, err)
|
t.Fatalf("Failed to receive %s request: %v", xdsname, err)
|
||||||
|
|
@ -125,11 +129,11 @@ func startXDS(t *testing.T, xdsname string, v2c *v2Client, reqChan *testutils.Ch
|
||||||
//
|
//
|
||||||
// It also waits and checks that the ack request contains the given version, and
|
// It also waits and checks that the ack request contains the given version, and
|
||||||
// the generated nonce.
|
// the generated nonce.
|
||||||
func sendGoodResp(t *testing.T, xdsname string, fakeServer *fakeserver.Server, version int, goodResp *xdspb.DiscoveryResponse, wantReq *xdspb.DiscoveryRequest, callbackCh *testutils.Channel) (string, error) {
|
func sendGoodResp(t *testing.T, xdsname string, fakeServer *fakeserver.Server, ver int, goodResp *xdspb.DiscoveryResponse, wantReq *xdspb.DiscoveryRequest, callbackCh *testutils.Channel) (string, error) {
|
||||||
nonce := sendXDSRespWithVersion(fakeServer.XDSResponseChan, goodResp, version)
|
nonce := sendXDSRespWithVersion(fakeServer.XDSResponseChan, goodResp, ver)
|
||||||
t.Logf("Good %s response pushed to fakeServer...", xdsname)
|
t.Logf("Good %s response pushed to fakeServer...", xdsname)
|
||||||
|
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, wantReq, strconv.Itoa(version), nonce); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, wantReq, strconv.Itoa(ver), nonce); err != nil {
|
||||||
return "", fmt.Errorf("failed to receive %s request: %v", xdsname, err)
|
return "", fmt.Errorf("failed to receive %s request: %v", xdsname, err)
|
||||||
}
|
}
|
||||||
t.Logf("Good %s response acked", xdsname)
|
t.Logf("Good %s response acked", xdsname)
|
||||||
|
|
@ -145,24 +149,24 @@ func sendGoodResp(t *testing.T, xdsname string, fakeServer *fakeserver.Server, v
|
||||||
// be nacked, so we expect a request with the previous version (version-1).
|
// be nacked, so we expect a request with the previous version (version-1).
|
||||||
//
|
//
|
||||||
// But the nonce in request should be the new nonce.
|
// But the nonce in request should be the new nonce.
|
||||||
func sendBadResp(t *testing.T, xdsname string, fakeServer *fakeserver.Server, version int, wantReq *xdspb.DiscoveryRequest) error {
|
func sendBadResp(t *testing.T, xdsname string, fakeServer *fakeserver.Server, ver int, wantReq *xdspb.DiscoveryRequest) error {
|
||||||
var typeURL string
|
var typeURL string
|
||||||
switch xdsname {
|
switch xdsname {
|
||||||
case "LDS":
|
case "LDS":
|
||||||
typeURL = ldsURL
|
typeURL = version.V2ListenerURL
|
||||||
case "RDS":
|
case "RDS":
|
||||||
typeURL = rdsURL
|
typeURL = version.V2RouteConfigURL
|
||||||
case "CDS":
|
case "CDS":
|
||||||
typeURL = cdsURL
|
typeURL = version.V2ClusterURL
|
||||||
case "EDS":
|
case "EDS":
|
||||||
typeURL = edsURL
|
typeURL = version.V2EndpointsURL
|
||||||
}
|
}
|
||||||
nonce := sendXDSRespWithVersion(fakeServer.XDSResponseChan, &xdspb.DiscoveryResponse{
|
nonce := sendXDSRespWithVersion(fakeServer.XDSResponseChan, &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{{}},
|
Resources: []*anypb.Any{{}},
|
||||||
TypeUrl: typeURL,
|
TypeUrl: typeURL,
|
||||||
}, version)
|
}, ver)
|
||||||
t.Logf("Bad %s response pushed to fakeServer...", xdsname)
|
t.Logf("Bad %s response pushed to fakeServer...", xdsname)
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, wantReq, strconv.Itoa(version-1), nonce); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, wantReq, strconv.Itoa(ver-1), nonce); err != nil {
|
||||||
return fmt.Errorf("failed to receive %s request: %v", xdsname, err)
|
return fmt.Errorf("failed to receive %s request: %v", xdsname, err)
|
||||||
}
|
}
|
||||||
t.Logf("Bad %s response nacked", xdsname)
|
t.Logf("Bad %s response nacked", xdsname)
|
||||||
|
|
@ -262,7 +266,7 @@ func (s) TestV2ClientAckFirstIsNack(t *testing.T) {
|
||||||
|
|
||||||
nonce := sendXDSRespWithVersion(fakeServer.XDSResponseChan, &xdspb.DiscoveryResponse{
|
nonce := sendXDSRespWithVersion(fakeServer.XDSResponseChan, &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{{}},
|
Resources: []*anypb.Any{{}},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}, versionLDS)
|
}, versionLDS)
|
||||||
t.Logf("Bad response pushed to fakeServer...")
|
t.Logf("Bad response pushed to fakeServer...")
|
||||||
|
|
||||||
|
|
@ -303,7 +307,7 @@ func (s) TestV2ClientAckNackAfterNewWatch(t *testing.T) {
|
||||||
// This is an invalid response after the new watch.
|
// This is an invalid response after the new watch.
|
||||||
nonce = sendXDSRespWithVersion(fakeServer.XDSResponseChan, &xdspb.DiscoveryResponse{
|
nonce = sendXDSRespWithVersion(fakeServer.XDSResponseChan, &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{{}},
|
Resources: []*anypb.Any{{}},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}, versionLDS)
|
}, versionLDS)
|
||||||
t.Logf("Bad response pushed to fakeServer...")
|
t.Logf("Bad response pushed to fakeServer...")
|
||||||
|
|
||||||
|
|
@ -332,7 +336,7 @@ func (s) TestV2ClientAckNewWatchAfterCancel(t *testing.T) {
|
||||||
defer v2cCleanup()
|
defer v2cCleanup()
|
||||||
|
|
||||||
// Start a CDS watch.
|
// Start a CDS watch.
|
||||||
v2c.addWatch(cdsURL, goodClusterName1)
|
v2c.AddWatch(version.V2ClusterURL, goodClusterName1)
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, "", ""); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, "", ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -346,14 +350,14 @@ func (s) TestV2ClientAckNewWatchAfterCancel(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Cancel the CDS watch, and start a new one. The new watch should have the
|
// Cancel the CDS watch, and start a new one. The new watch should have the
|
||||||
// version from the response above.
|
// version from the response above.
|
||||||
v2c.removeWatch(cdsURL, goodClusterName1)
|
v2c.RemoveWatch(version.V2ClusterURL, goodClusterName1)
|
||||||
// Wait for a request with no resource names, because the only watch was
|
// Wait for a request with no resource names, because the only watch was
|
||||||
// removed.
|
// removed.
|
||||||
emptyReq := &xdspb.DiscoveryRequest{Node: goodNodeProto, TypeUrl: cdsURL}
|
emptyReq := &xdspb.DiscoveryRequest{Node: goodNodeProto, TypeUrl: version.V2ClusterURL}
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, emptyReq, strconv.Itoa(versionCDS), nonce); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, emptyReq, strconv.Itoa(versionCDS), nonce); err != nil {
|
||||||
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
||||||
}
|
}
|
||||||
v2c.addWatch(cdsURL, goodClusterName1)
|
v2c.AddWatch(version.V2ClusterURL, goodClusterName1)
|
||||||
// Wait for a request with correct resource names and version.
|
// Wait for a request with correct resource names and version.
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, strconv.Itoa(versionCDS), nonce); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, strconv.Itoa(versionCDS), nonce); err != nil {
|
||||||
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
||||||
|
|
@ -387,7 +391,7 @@ func (s) TestV2ClientAckCancelResponseRace(t *testing.T) {
|
||||||
defer v2cCleanup()
|
defer v2cCleanup()
|
||||||
|
|
||||||
// Start a CDS watch.
|
// Start a CDS watch.
|
||||||
v2c.addWatch(cdsURL, goodClusterName1)
|
v2c.AddWatch(version.V2ClusterURL, goodClusterName1)
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, "", ""); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, "", ""); err != nil {
|
||||||
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
||||||
}
|
}
|
||||||
|
|
@ -400,10 +404,10 @@ func (s) TestV2ClientAckCancelResponseRace(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Cancel the watch before the next response is sent. This mimics the case
|
// Cancel the watch before the next response is sent. This mimics the case
|
||||||
// watch is canceled while response is on wire.
|
// watch is canceled while response is on wire.
|
||||||
v2c.removeWatch(cdsURL, goodClusterName1)
|
v2c.RemoveWatch(version.V2ClusterURL, goodClusterName1)
|
||||||
// Wait for a request with no resource names, because the only watch was
|
// Wait for a request with no resource names, because the only watch was
|
||||||
// removed.
|
// removed.
|
||||||
emptyReq := &xdspb.DiscoveryRequest{Node: goodNodeProto, TypeUrl: cdsURL}
|
emptyReq := &xdspb.DiscoveryRequest{Node: goodNodeProto, TypeUrl: version.V2ClusterURL}
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, emptyReq, strconv.Itoa(versionCDS), nonce); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, emptyReq, strconv.Itoa(versionCDS), nonce); err != nil {
|
||||||
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
||||||
}
|
}
|
||||||
|
|
@ -428,7 +432,7 @@ func (s) TestV2ClientAckCancelResponseRace(t *testing.T) {
|
||||||
|
|
||||||
// Start a new watch. The new watch should have the nonce from the response
|
// Start a new watch. The new watch should have the nonce from the response
|
||||||
// above, and version from the first good response.
|
// above, and version from the first good response.
|
||||||
v2c.addWatch(cdsURL, goodClusterName1)
|
v2c.AddWatch(version.V2ClusterURL, goodClusterName1)
|
||||||
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, strconv.Itoa(versionCDS-1), nonce); err != nil {
|
if err := compareXDSRequest(fakeServer.XDSRequestChan, goodCDSRequest, strconv.Itoa(versionCDS-1), nonce); err != nil {
|
||||||
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
t.Fatalf("Failed to receive %s request: %v", "CDS", err)
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -26,7 +26,8 @@ import (
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
anypb "github.com/golang/protobuf/ptypes/any"
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
"github.com/google/go-cmp/cmp"
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -34,115 +35,68 @@ const (
|
||||||
serviceName2 = "bar-service"
|
serviceName2 = "bar-service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s) TestValidateCluster(t *testing.T) {
|
var (
|
||||||
emptyUpdate := ClusterUpdate{ServiceName: "", EnableLRS: false}
|
badlyMarshaledCDSResponse = &xdspb.DiscoveryResponse{
|
||||||
tests := []struct {
|
Resources: []*anypb.Any{
|
||||||
name string
|
{
|
||||||
cluster *xdspb.Cluster
|
TypeUrl: version.V2ClusterURL,
|
||||||
wantUpdate ClusterUpdate
|
Value: []byte{1, 2, 3, 4},
|
||||||
wantErr bool
|
},
|
||||||
}{
|
},
|
||||||
{
|
TypeUrl: version.V2ClusterURL,
|
||||||
name: "non-eds-cluster-type",
|
}
|
||||||
cluster: &xdspb.Cluster{
|
goodCluster1 = &xdspb.Cluster{
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_STATIC},
|
Name: goodClusterName1,
|
||||||
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
||||||
EdsConfig: &corepb.ConfigSource{
|
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
||||||
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
EdsConfig: &corepb.ConfigSource{
|
||||||
Ads: &corepb.AggregatedConfigSource{},
|
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
||||||
},
|
Ads: &corepb.AggregatedConfigSource{},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
LbPolicy: xdspb.Cluster_LEAST_REQUEST,
|
|
||||||
},
|
},
|
||||||
wantUpdate: emptyUpdate,
|
ServiceName: serviceName1,
|
||||||
wantErr: true,
|
|
||||||
},
|
},
|
||||||
{
|
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
||||||
name: "no-eds-config",
|
LrsServer: &corepb.ConfigSource{
|
||||||
cluster: &xdspb.Cluster{
|
ConfigSourceSpecifier: &corepb.ConfigSource_Self{
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
Self: &corepb.SelfConfigSource{},
|
||||||
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
|
||||||
},
|
},
|
||||||
wantUpdate: emptyUpdate,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no-ads-config-source",
|
|
||||||
cluster: &xdspb.Cluster{
|
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
|
||||||
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{},
|
|
||||||
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
|
||||||
},
|
|
||||||
wantUpdate: emptyUpdate,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-round-robin-lb-policy",
|
|
||||||
cluster: &xdspb.Cluster{
|
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
|
||||||
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
|
||||||
EdsConfig: &corepb.ConfigSource{
|
|
||||||
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
|
||||||
Ads: &corepb.AggregatedConfigSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LbPolicy: xdspb.Cluster_LEAST_REQUEST,
|
|
||||||
},
|
|
||||||
wantUpdate: emptyUpdate,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy-case-no-service-name-no-lrs",
|
|
||||||
cluster: &xdspb.Cluster{
|
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
|
||||||
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
|
||||||
EdsConfig: &corepb.ConfigSource{
|
|
||||||
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
|
||||||
Ads: &corepb.AggregatedConfigSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
|
||||||
},
|
|
||||||
wantUpdate: emptyUpdate,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy-case-no-lrs",
|
|
||||||
cluster: &xdspb.Cluster{
|
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
|
||||||
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
|
||||||
EdsConfig: &corepb.ConfigSource{
|
|
||||||
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
|
||||||
Ads: &corepb.AggregatedConfigSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ServiceName: serviceName1,
|
|
||||||
},
|
|
||||||
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
|
||||||
},
|
|
||||||
wantUpdate: ClusterUpdate{ServiceName: serviceName1, EnableLRS: false},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happiest-case",
|
|
||||||
cluster: goodCluster1,
|
|
||||||
wantUpdate: ClusterUpdate{ServiceName: serviceName1, EnableLRS: true},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
marshaledCluster1, _ = proto.Marshal(goodCluster1)
|
||||||
for _, test := range tests {
|
goodCluster2 = &xdspb.Cluster{
|
||||||
t.Run(test.name, func(t *testing.T) {
|
Name: goodClusterName2,
|
||||||
gotUpdate, gotErr := validateCluster(test.cluster)
|
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
||||||
if (gotErr != nil) != test.wantErr {
|
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
||||||
t.Errorf("validateCluster(%+v) returned error: %v, wantErr: %v", test.cluster, gotErr, test.wantErr)
|
EdsConfig: &corepb.ConfigSource{
|
||||||
}
|
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
||||||
if !cmp.Equal(gotUpdate, test.wantUpdate) {
|
Ads: &corepb.AggregatedConfigSource{},
|
||||||
t.Errorf("validateCluster(%+v) = %v, want: %v", test.cluster, gotUpdate, test.wantUpdate)
|
},
|
||||||
}
|
},
|
||||||
})
|
ServiceName: serviceName2,
|
||||||
|
},
|
||||||
|
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
||||||
}
|
}
|
||||||
}
|
marshaledCluster2, _ = proto.Marshal(goodCluster2)
|
||||||
|
goodCDSResponse1 = &xdspb.DiscoveryResponse{
|
||||||
|
Resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V2ClusterURL,
|
||||||
|
Value: marshaledCluster1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TypeUrl: version.V2ClusterURL,
|
||||||
|
}
|
||||||
|
goodCDSResponse2 = &xdspb.DiscoveryResponse{
|
||||||
|
Resources: []*anypb.Any{
|
||||||
|
{
|
||||||
|
TypeUrl: version.V2ClusterURL,
|
||||||
|
Value: marshaledCluster2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TypeUrl: version.V2ClusterURL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// TestCDSHandleResponse starts a fake xDS server, makes a ClientConn to it,
|
// TestCDSHandleResponse starts a fake xDS server, makes a ClientConn to it,
|
||||||
// and creates a v2Client using it. Then, it registers a CDS watcher and tests
|
// and creates a v2Client using it. Then, it registers a CDS watcher and tests
|
||||||
|
|
@ -152,7 +106,7 @@ func (s) TestCDSHandleResponse(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
cdsResponse *xdspb.DiscoveryResponse
|
cdsResponse *xdspb.DiscoveryResponse
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantUpdate *ClusterUpdate
|
wantUpdate *xdsclient.ClusterUpdate
|
||||||
wantUpdateErr bool
|
wantUpdateErr bool
|
||||||
}{
|
}{
|
||||||
// Badly marshaled CDS response.
|
// Badly marshaled CDS response.
|
||||||
|
|
@ -192,14 +146,14 @@ func (s) TestCDSHandleResponse(t *testing.T) {
|
||||||
name: "one-good-cluster",
|
name: "one-good-cluster",
|
||||||
cdsResponse: goodCDSResponse1,
|
cdsResponse: goodCDSResponse1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
wantUpdate: &ClusterUpdate{ServiceName: serviceName1, EnableLRS: true},
|
wantUpdate: &xdsclient.ClusterUpdate{ServiceName: serviceName1, EnableLRS: true},
|
||||||
wantUpdateErr: false,
|
wantUpdateErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
testWatchHandle(t, &watchHandleTestcase{
|
testWatchHandle(t, &watchHandleTestcase{
|
||||||
typeURL: cdsURL,
|
typeURL: version.V2ClusterURL,
|
||||||
resourceName: goodClusterName1,
|
resourceName: goodClusterName1,
|
||||||
|
|
||||||
responseToHandle: test.cdsResponse,
|
responseToHandle: test.cdsResponse,
|
||||||
|
|
@ -217,10 +171,13 @@ func (s) TestCDSHandleResponseWithoutWatch(t *testing.T) {
|
||||||
_, cc, cleanup := startServerAndGetCC(t)
|
_, cc, cleanup := startServerAndGetCC(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
f: func(string, map[string]interface{}) {},
|
f: func(string, map[string]interface{}) {},
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
defer v2c.close()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
|
|
||||||
if v2c.handleCDSResponse(badResourceTypeInLDSResponse) == nil {
|
if v2c.handleCDSResponse(badResourceTypeInLDSResponse) == nil {
|
||||||
t.Fatal("v2c.handleCDSResponse() succeeded, should have failed")
|
t.Fatal("v2c.handleCDSResponse() succeeded, should have failed")
|
||||||
|
|
@ -230,66 +187,3 @@ func (s) TestCDSHandleResponseWithoutWatch(t *testing.T) {
|
||||||
t.Fatal("v2c.handleCDSResponse() succeeded, should have failed")
|
t.Fatal("v2c.handleCDSResponse() succeeded, should have failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
badlyMarshaledCDSResponse = &xdspb.DiscoveryResponse{
|
|
||||||
Resources: []*anypb.Any{
|
|
||||||
{
|
|
||||||
TypeUrl: cdsURL,
|
|
||||||
Value: []byte{1, 2, 3, 4},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TypeUrl: cdsURL,
|
|
||||||
}
|
|
||||||
goodCluster1 = &xdspb.Cluster{
|
|
||||||
Name: goodClusterName1,
|
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
|
||||||
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
|
||||||
EdsConfig: &corepb.ConfigSource{
|
|
||||||
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
|
||||||
Ads: &corepb.AggregatedConfigSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ServiceName: serviceName1,
|
|
||||||
},
|
|
||||||
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
|
||||||
LrsServer: &corepb.ConfigSource{
|
|
||||||
ConfigSourceSpecifier: &corepb.ConfigSource_Self{
|
|
||||||
Self: &corepb.SelfConfigSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
marshaledCluster1, _ = proto.Marshal(goodCluster1)
|
|
||||||
goodCluster2 = &xdspb.Cluster{
|
|
||||||
Name: goodClusterName2,
|
|
||||||
ClusterDiscoveryType: &xdspb.Cluster_Type{Type: xdspb.Cluster_EDS},
|
|
||||||
EdsClusterConfig: &xdspb.Cluster_EdsClusterConfig{
|
|
||||||
EdsConfig: &corepb.ConfigSource{
|
|
||||||
ConfigSourceSpecifier: &corepb.ConfigSource_Ads{
|
|
||||||
Ads: &corepb.AggregatedConfigSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ServiceName: serviceName2,
|
|
||||||
},
|
|
||||||
LbPolicy: xdspb.Cluster_ROUND_ROBIN,
|
|
||||||
}
|
|
||||||
marshaledCluster2, _ = proto.Marshal(goodCluster2)
|
|
||||||
goodCDSResponse1 = &xdspb.DiscoveryResponse{
|
|
||||||
Resources: []*anypb.Any{
|
|
||||||
{
|
|
||||||
TypeUrl: cdsURL,
|
|
||||||
Value: marshaledCluster1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TypeUrl: cdsURL,
|
|
||||||
}
|
|
||||||
goodCDSResponse2 = &xdspb.DiscoveryResponse{
|
|
||||||
Resources: []*anypb.Any{
|
|
||||||
{
|
|
||||||
TypeUrl: cdsURL,
|
|
||||||
Value: marshaledCluster2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TypeUrl: cdsURL,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@ -13,157 +13,75 @@
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
|
||||||
"github.com/golang/protobuf/ptypes"
|
"github.com/golang/protobuf/ptypes"
|
||||||
anypb "github.com/golang/protobuf/ptypes/any"
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"google.golang.org/grpc/xds/internal"
|
"google.golang.org/grpc/xds/internal"
|
||||||
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s) TestEDSParseRespProto(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
m *xdspb.ClusterLoadAssignment
|
|
||||||
want EndpointsUpdate
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing-priority",
|
|
||||||
m: func() *xdspb.ClusterLoadAssignment {
|
|
||||||
clab0 := NewClusterLoadAssignmentBuilder("test", nil)
|
|
||||||
clab0.AddLocality("locality-1", 1, 0, []string{"addr1:314"}, nil)
|
|
||||||
clab0.AddLocality("locality-2", 1, 2, []string{"addr2:159"}, nil)
|
|
||||||
return clab0.Build()
|
|
||||||
}(),
|
|
||||||
want: EndpointsUpdate{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing-locality-ID",
|
|
||||||
m: func() *xdspb.ClusterLoadAssignment {
|
|
||||||
clab0 := NewClusterLoadAssignmentBuilder("test", nil)
|
|
||||||
clab0.AddLocality("", 1, 0, []string{"addr1:314"}, nil)
|
|
||||||
return clab0.Build()
|
|
||||||
}(),
|
|
||||||
want: EndpointsUpdate{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "good",
|
|
||||||
m: func() *xdspb.ClusterLoadAssignment {
|
|
||||||
clab0 := NewClusterLoadAssignmentBuilder("test", nil)
|
|
||||||
clab0.AddLocality("locality-1", 1, 1, []string{"addr1:314"}, &AddLocalityOptions{
|
|
||||||
Health: []corepb.HealthStatus{corepb.HealthStatus_UNHEALTHY},
|
|
||||||
Weight: []uint32{271},
|
|
||||||
})
|
|
||||||
clab0.AddLocality("locality-2", 1, 0, []string{"addr2:159"}, &AddLocalityOptions{
|
|
||||||
Health: []corepb.HealthStatus{corepb.HealthStatus_DRAINING},
|
|
||||||
Weight: []uint32{828},
|
|
||||||
})
|
|
||||||
return clab0.Build()
|
|
||||||
}(),
|
|
||||||
want: EndpointsUpdate{
|
|
||||||
Drops: nil,
|
|
||||||
Localities: []Locality{
|
|
||||||
{
|
|
||||||
Endpoints: []Endpoint{{
|
|
||||||
Address: "addr1:314",
|
|
||||||
HealthStatus: EndpointHealthStatusUnhealthy,
|
|
||||||
Weight: 271,
|
|
||||||
}},
|
|
||||||
ID: internal.LocalityID{SubZone: "locality-1"},
|
|
||||||
Priority: 1,
|
|
||||||
Weight: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Endpoints: []Endpoint{{
|
|
||||||
Address: "addr2:159",
|
|
||||||
HealthStatus: EndpointHealthStatusDraining,
|
|
||||||
Weight: 828,
|
|
||||||
}},
|
|
||||||
ID: internal.LocalityID{SubZone: "locality-2"},
|
|
||||||
Priority: 0,
|
|
||||||
Weight: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := ParseEDSRespProto(tt.m)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("ParseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if d := cmp.Diff(got, tt.want); d != "" {
|
|
||||||
t.Errorf("ParseEDSRespProto() got = %v, want %v, diff: %v", got, tt.want, d)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
badlyMarshaledEDSResponse = &xdspb.DiscoveryResponse{
|
badlyMarshaledEDSResponse = &v2xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: edsURL,
|
TypeUrl: version.V2EndpointsURL,
|
||||||
Value: []byte{1, 2, 3, 4},
|
Value: []byte{1, 2, 3, 4},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: edsURL,
|
TypeUrl: version.V2EndpointsURL,
|
||||||
}
|
}
|
||||||
badResourceTypeInEDSResponse = &xdspb.DiscoveryResponse{
|
badResourceTypeInEDSResponse = &v2xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: httpConnManagerURL,
|
TypeUrl: httpConnManagerURL,
|
||||||
Value: marshaledConnMgr1,
|
Value: marshaledConnMgr1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: edsURL,
|
TypeUrl: version.V2EndpointsURL,
|
||||||
}
|
}
|
||||||
goodEDSResponse1 = &xdspb.DiscoveryResponse{
|
goodEDSResponse1 = &v2xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
func() *anypb.Any {
|
func() *anypb.Any {
|
||||||
clab0 := NewClusterLoadAssignmentBuilder(goodEDSName, nil)
|
clab0 := testutils.NewClusterLoadAssignmentBuilder(goodEDSName, nil)
|
||||||
clab0.AddLocality("locality-1", 1, 1, []string{"addr1:314"}, nil)
|
clab0.AddLocality("locality-1", 1, 1, []string{"addr1:314"}, nil)
|
||||||
clab0.AddLocality("locality-2", 1, 0, []string{"addr2:159"}, nil)
|
clab0.AddLocality("locality-2", 1, 0, []string{"addr2:159"}, nil)
|
||||||
a, _ := ptypes.MarshalAny(clab0.Build())
|
a, _ := ptypes.MarshalAny(clab0.Build())
|
||||||
return a
|
return a
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
TypeUrl: edsURL,
|
TypeUrl: version.V2EndpointsURL,
|
||||||
}
|
}
|
||||||
goodEDSResponse2 = &xdspb.DiscoveryResponse{
|
goodEDSResponse2 = &v2xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
func() *anypb.Any {
|
func() *anypb.Any {
|
||||||
clab0 := NewClusterLoadAssignmentBuilder("not-goodEDSName", nil)
|
clab0 := testutils.NewClusterLoadAssignmentBuilder("not-goodEDSName", nil)
|
||||||
clab0.AddLocality("locality-1", 1, 1, []string{"addr1:314"}, nil)
|
clab0.AddLocality("locality-1", 1, 1, []string{"addr1:314"}, nil)
|
||||||
clab0.AddLocality("locality-2", 1, 0, []string{"addr2:159"}, nil)
|
clab0.AddLocality("locality-2", 1, 0, []string{"addr2:159"}, nil)
|
||||||
a, _ := ptypes.MarshalAny(clab0.Build())
|
a, _ := ptypes.MarshalAny(clab0.Build())
|
||||||
return a
|
return a
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
TypeUrl: edsURL,
|
TypeUrl: version.V2EndpointsURL,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s) TestEDSHandleResponse(t *testing.T) {
|
func (s) TestEDSHandleResponse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
edsResponse *xdspb.DiscoveryResponse
|
edsResponse *v2xdspb.DiscoveryResponse
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantUpdate *EndpointsUpdate
|
wantUpdate *xdsclient.EndpointsUpdate
|
||||||
wantUpdateErr bool
|
wantUpdateErr bool
|
||||||
}{
|
}{
|
||||||
// Any in resource is badly marshaled.
|
// Any in resource is badly marshaled.
|
||||||
|
|
@ -195,16 +113,16 @@ func (s) TestEDSHandleResponse(t *testing.T) {
|
||||||
name: "one-good-assignment",
|
name: "one-good-assignment",
|
||||||
edsResponse: goodEDSResponse1,
|
edsResponse: goodEDSResponse1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
wantUpdate: &EndpointsUpdate{
|
wantUpdate: &xdsclient.EndpointsUpdate{
|
||||||
Localities: []Locality{
|
Localities: []xdsclient.Locality{
|
||||||
{
|
{
|
||||||
Endpoints: []Endpoint{{Address: "addr1:314"}},
|
Endpoints: []xdsclient.Endpoint{{Address: "addr1:314"}},
|
||||||
ID: internal.LocalityID{SubZone: "locality-1"},
|
ID: internal.LocalityID{SubZone: "locality-1"},
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
Weight: 1,
|
Weight: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Endpoints: []Endpoint{{Address: "addr2:159"}},
|
Endpoints: []xdsclient.Endpoint{{Address: "addr2:159"}},
|
||||||
ID: internal.LocalityID{SubZone: "locality-2"},
|
ID: internal.LocalityID{SubZone: "locality-2"},
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Weight: 1,
|
Weight: 1,
|
||||||
|
|
@ -217,7 +135,7 @@ func (s) TestEDSHandleResponse(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
testWatchHandle(t, &watchHandleTestcase{
|
testWatchHandle(t, &watchHandleTestcase{
|
||||||
typeURL: edsURL,
|
typeURL: version.V2EndpointsURL,
|
||||||
resourceName: goodEDSName,
|
resourceName: goodEDSName,
|
||||||
responseToHandle: test.edsResponse,
|
responseToHandle: test.edsResponse,
|
||||||
wantHandleErr: test.wantErr,
|
wantHandleErr: test.wantErr,
|
||||||
|
|
@ -234,10 +152,13 @@ func (s) TestEDSHandleResponseWithoutWatch(t *testing.T) {
|
||||||
_, cc, cleanup := startServerAndGetCC(t)
|
_, cc, cleanup := startServerAndGetCC(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
f: func(string, map[string]interface{}) {},
|
f: func(string, map[string]interface{}) {},
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
defer v2c.close()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
|
|
||||||
if v2c.handleEDSResponse(badResourceTypeInEDSResponse) == nil {
|
if v2c.handleEDSResponse(badResourceTypeInEDSResponse) == nil {
|
||||||
t.Fatal("v2c.handleEDSResponse() succeeded, should have failed")
|
t.Fatal("v2c.handleEDSResponse() succeeded, should have failed")
|
||||||
|
|
@ -16,114 +16,26 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
basepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
|
||||||
httppb "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
|
|
||||||
listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v2"
|
|
||||||
anypb "github.com/golang/protobuf/ptypes/any"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s) TestLDSGetRouteConfig(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: "rds.ConfigSource-in-apiListener-is-not-ADS",
|
|
||||||
lis: &xdspb.Listener{
|
|
||||||
Name: goodLDSTarget1,
|
|
||||||
ApiListener: &listenerpb.ApiListener{
|
|
||||||
ApiListener: &anypb.Any{
|
|
||||||
TypeUrl: httpConnManagerURL,
|
|
||||||
Value: func() []byte {
|
|
||||||
cm := &httppb.HttpConnectionManager{
|
|
||||||
RouteSpecifier: &httppb.HttpConnectionManager_Rds{
|
|
||||||
Rds: &httppb.Rds{
|
|
||||||
ConfigSource: &basepb.ConfigSource{
|
|
||||||
ConfigSourceSpecifier: &basepb.ConfigSource_Path{
|
|
||||||
Path: "/some/path",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RouteConfigName: goodRouteName1}}}
|
|
||||||
mcm, _ := proto.Marshal(cm)
|
|
||||||
return mcm
|
|
||||||
}()}}},
|
|
||||||
wantRoute: "",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "goodListener1",
|
|
||||||
lis: goodListener1,
|
|
||||||
wantRoute: goodRouteName1,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, cc, cleanup := startServerAndGetCC(t)
|
|
||||||
defer cleanup()
|
|
||||||
v2c := newV2Client(nil, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
|
||||||
defer v2c.close()
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
gotRoute, err := v2c.getRouteConfigNameFromListener(test.lis)
|
|
||||||
if gotRoute != test.wantRoute {
|
|
||||||
t.Errorf("getRouteConfigNameFromListener(%+v) = %v, want %v", test.lis, gotRoute, test.wantRoute)
|
|
||||||
}
|
|
||||||
if (err != nil) != test.wantErr {
|
|
||||||
t.Errorf("getRouteConfigNameFromListener(%+v) = %v, want %v", test.lis, err, test.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestLDSHandleResponse starts a fake xDS server, makes a ClientConn to it,
|
// TestLDSHandleResponse starts a fake xDS server, makes a ClientConn to it,
|
||||||
// and creates a v2Client using it. Then, it registers a watchLDS and tests
|
// and creates a client using it. Then, it registers a watchLDS and tests
|
||||||
// different LDS responses.
|
// different LDS responses.
|
||||||
func (s) TestLDSHandleResponse(t *testing.T) {
|
func (s) TestLDSHandleResponse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ldsResponse *xdspb.DiscoveryResponse
|
ldsResponse *v2xdspb.DiscoveryResponse
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantUpdate *ldsUpdate
|
wantUpdate *xdsclient.ListenerUpdate
|
||||||
wantUpdateErr bool
|
wantUpdateErr bool
|
||||||
}{
|
}{
|
||||||
// Badly marshaled LDS response.
|
// Badly marshaled LDS response.
|
||||||
|
|
@ -157,7 +69,7 @@ func (s) TestLDSHandleResponse(t *testing.T) {
|
||||||
name: "one-good-listener",
|
name: "one-good-listener",
|
||||||
ldsResponse: goodLDSResponse1,
|
ldsResponse: goodLDSResponse1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
wantUpdate: &ldsUpdate{routeName: goodRouteName1},
|
wantUpdate: &xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1},
|
||||||
wantUpdateErr: false,
|
wantUpdateErr: false,
|
||||||
},
|
},
|
||||||
// Response contains multiple good listeners, including the one we are
|
// Response contains multiple good listeners, including the one we are
|
||||||
|
|
@ -166,7 +78,7 @@ func (s) TestLDSHandleResponse(t *testing.T) {
|
||||||
name: "multiple-good-listener",
|
name: "multiple-good-listener",
|
||||||
ldsResponse: ldsResponseWithMultipleResources,
|
ldsResponse: ldsResponseWithMultipleResources,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
wantUpdate: &ldsUpdate{routeName: goodRouteName1},
|
wantUpdate: &xdsclient.ListenerUpdate{RouteConfigName: goodRouteName1},
|
||||||
wantUpdateErr: false,
|
wantUpdateErr: false,
|
||||||
},
|
},
|
||||||
// Response contains two good listeners (one interesting and one
|
// Response contains two good listeners (one interesting and one
|
||||||
|
|
@ -201,7 +113,7 @@ func (s) TestLDSHandleResponse(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
testWatchHandle(t, &watchHandleTestcase{
|
testWatchHandle(t, &watchHandleTestcase{
|
||||||
typeURL: ldsURL,
|
typeURL: version.V2ListenerURL,
|
||||||
resourceName: goodLDSTarget1,
|
resourceName: goodLDSTarget1,
|
||||||
responseToHandle: test.ldsResponse,
|
responseToHandle: test.ldsResponse,
|
||||||
wantHandleErr: test.wantErr,
|
wantHandleErr: test.wantErr,
|
||||||
|
|
@ -212,16 +124,19 @@ func (s) TestLDSHandleResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLDSHandleResponseWithoutWatch tests the case where the v2Client receives
|
// TestLDSHandleResponseWithoutWatch tests the case where the client receives
|
||||||
// an LDS response without a registered watcher.
|
// an LDS response without a registered watcher.
|
||||||
func (s) TestLDSHandleResponseWithoutWatch(t *testing.T) {
|
func (s) TestLDSHandleResponseWithoutWatch(t *testing.T) {
|
||||||
_, cc, cleanup := startServerAndGetCC(t)
|
_, cc, cleanup := startServerAndGetCC(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
f: func(string, map[string]interface{}) {},
|
f: func(string, map[string]interface{}) {},
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
defer v2c.close()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
|
|
||||||
if v2c.handleLDSResponse(badResourceTypeInLDSResponse) == nil {
|
if v2c.handleLDSResponse(badResourceTypeInLDSResponse) == nil {
|
||||||
t.Fatal("v2c.handleLDSResponse() succeeded, should have failed")
|
t.Fatal("v2c.handleLDSResponse() succeeded, should have failed")
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
|
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// doLDS makes a LDS watch, and waits for the response and ack to finish.
|
||||||
|
//
|
||||||
|
// This is called by RDS tests to start LDS first, because LDS is a
|
||||||
|
// pre-requirement for RDS, and RDS handle would fail without an existing LDS
|
||||||
|
// watch.
|
||||||
|
func doLDS(t *testing.T, v2c xdsclient.APIClient, fakeServer *fakeserver.Server) {
|
||||||
|
v2c.AddWatch(version.V2ListenerURL, goodLDSTarget1)
|
||||||
|
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
||||||
|
t.Fatalf("Timeout waiting for LDS request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRDSHandleResponseWithRouting starts a fake xDS server, makes a ClientConn
|
||||||
|
// to it, and creates a v2Client using it. Then, it registers an LDS and RDS
|
||||||
|
// watcher and tests different RDS responses.
|
||||||
|
func (s) TestRDSHandleResponseWithRouting(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rdsResponse *xdspb.DiscoveryResponse
|
||||||
|
wantErr bool
|
||||||
|
wantUpdate *xdsclient.RouteConfigUpdate
|
||||||
|
wantUpdateErr bool
|
||||||
|
}{
|
||||||
|
// Badly marshaled RDS response.
|
||||||
|
{
|
||||||
|
name: "badly-marshaled-response",
|
||||||
|
rdsResponse: badlyMarshaledRDSResponse,
|
||||||
|
wantErr: true,
|
||||||
|
wantUpdate: nil,
|
||||||
|
wantUpdateErr: false,
|
||||||
|
},
|
||||||
|
// Response does not contain RouteConfiguration proto.
|
||||||
|
{
|
||||||
|
name: "no-route-config-in-response",
|
||||||
|
rdsResponse: badResourceTypeInRDSResponse,
|
||||||
|
wantErr: true,
|
||||||
|
wantUpdate: nil,
|
||||||
|
wantUpdateErr: false,
|
||||||
|
},
|
||||||
|
// No VirtualHosts in the response. Just one test case here for a bad
|
||||||
|
// RouteConfiguration, since the others are covered in
|
||||||
|
// TestGetClusterFromRouteConfiguration.
|
||||||
|
{
|
||||||
|
name: "no-virtual-hosts-in-response",
|
||||||
|
rdsResponse: noVirtualHostsInRDSResponse,
|
||||||
|
wantErr: true,
|
||||||
|
wantUpdate: nil,
|
||||||
|
wantUpdateErr: false,
|
||||||
|
},
|
||||||
|
// Response contains one good RouteConfiguration, uninteresting though.
|
||||||
|
{
|
||||||
|
name: "one-uninteresting-route-config",
|
||||||
|
rdsResponse: goodRDSResponse2,
|
||||||
|
wantErr: false,
|
||||||
|
wantUpdate: nil,
|
||||||
|
wantUpdateErr: false,
|
||||||
|
},
|
||||||
|
// Response contains one good interesting RouteConfiguration.
|
||||||
|
{
|
||||||
|
name: "one-good-route-config",
|
||||||
|
rdsResponse: goodRDSResponse1,
|
||||||
|
wantErr: false,
|
||||||
|
wantUpdate: &xdsclient.RouteConfigUpdate{Routes: []*xdsclient.Route{{Prefix: newStringP(""), Action: map[string]uint32{goodClusterName1: 1}}}},
|
||||||
|
wantUpdateErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one-good-route-config with routes",
|
||||||
|
rdsResponse: goodRDSResponse1,
|
||||||
|
wantErr: false,
|
||||||
|
wantUpdate: &xdsclient.RouteConfigUpdate{
|
||||||
|
// Instead of just weighted targets when routing is disabled,
|
||||||
|
// this result contains a route with perfix "", and action as
|
||||||
|
// weighted targets.
|
||||||
|
Routes: []*xdsclient.Route{{
|
||||||
|
Prefix: newStringP(""),
|
||||||
|
Action: map[string]uint32{goodClusterName1: 1},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantUpdateErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
testWatchHandle(t, &watchHandleTestcase{
|
||||||
|
typeURL: version.V2RouteConfigURL,
|
||||||
|
resourceName: goodRouteName1,
|
||||||
|
responseToHandle: test.rdsResponse,
|
||||||
|
wantHandleErr: test.wantErr,
|
||||||
|
wantUpdate: test.wantUpdate,
|
||||||
|
wantUpdateErr: test.wantUpdateErr,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRDSHandleResponseWithoutLDSWatch tests the case where the v2Client
|
||||||
|
// receives an RDS response without a registered LDS watcher.
|
||||||
|
func (s) TestRDSHandleResponseWithoutLDSWatch(t *testing.T) {
|
||||||
|
_, cc, cleanup := startServerAndGetCC(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
|
f: func(string, map[string]interface{}) {},
|
||||||
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
|
|
||||||
|
if v2c.handleRDSResponse(goodRDSResponse1) == nil {
|
||||||
|
t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRDSHandleResponseWithoutRDSWatch tests the case where the v2Client
|
||||||
|
// receives an RDS response without a registered RDS watcher.
|
||||||
|
func (s) TestRDSHandleResponseWithoutRDSWatch(t *testing.T) {
|
||||||
|
fakeServer, cc, cleanup := startServerAndGetCC(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
|
f: func(string, map[string]interface{}) {},
|
||||||
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
|
doLDS(t, v2c, fakeServer)
|
||||||
|
|
||||||
|
if v2c.handleRDSResponse(badResourceTypeInRDSResponse) == nil {
|
||||||
|
t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v2c.handleRDSResponse(goodRDSResponse1) != nil {
|
||||||
|
t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,19 +16,25 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/internal/grpclog"
|
||||||
|
"google.golang.org/grpc/internal/grpctest"
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
"google.golang.org/grpc/resolver/manual"
|
"google.golang.org/grpc/resolver/manual"
|
||||||
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
"google.golang.org/grpc/xds/internal/testutils"
|
"google.golang.org/grpc/xds/internal/testutils"
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
||||||
|
"google.golang.org/grpc/xds/internal/version"
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
basepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
basepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
|
|
@ -39,8 +45,15 @@ import (
|
||||||
structpb "github.com/golang/protobuf/ptypes/struct"
|
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type s struct {
|
||||||
|
grpctest.Tester
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
grpctest.RunSubTests(t, s{})
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultTestTimeout = 1 * time.Second
|
|
||||||
goodLDSTarget1 = "lds.target.good:1111"
|
goodLDSTarget1 = "lds.target.good:1111"
|
||||||
goodLDSTarget2 = "lds.target.good:2222"
|
goodLDSTarget2 = "lds.target.good:2222"
|
||||||
goodRouteName1 = "GoodRouteConfig1"
|
goodRouteName1 = "GoodRouteConfig1"
|
||||||
|
|
@ -67,22 +80,22 @@ var (
|
||||||
}
|
}
|
||||||
goodLDSRequest = &xdspb.DiscoveryRequest{
|
goodLDSRequest = &xdspb.DiscoveryRequest{
|
||||||
Node: goodNodeProto,
|
Node: goodNodeProto,
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
ResourceNames: []string{goodLDSTarget1},
|
ResourceNames: []string{goodLDSTarget1},
|
||||||
}
|
}
|
||||||
goodRDSRequest = &xdspb.DiscoveryRequest{
|
goodRDSRequest = &xdspb.DiscoveryRequest{
|
||||||
Node: goodNodeProto,
|
Node: goodNodeProto,
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
ResourceNames: []string{goodRouteName1},
|
ResourceNames: []string{goodRouteName1},
|
||||||
}
|
}
|
||||||
goodCDSRequest = &xdspb.DiscoveryRequest{
|
goodCDSRequest = &xdspb.DiscoveryRequest{
|
||||||
Node: goodNodeProto,
|
Node: goodNodeProto,
|
||||||
TypeUrl: cdsURL,
|
TypeUrl: version.V2ClusterURL,
|
||||||
ResourceNames: []string{goodClusterName1},
|
ResourceNames: []string{goodClusterName1},
|
||||||
}
|
}
|
||||||
goodEDSRequest = &xdspb.DiscoveryRequest{
|
goodEDSRequest = &xdspb.DiscoveryRequest{
|
||||||
Node: goodNodeProto,
|
Node: goodNodeProto,
|
||||||
TypeUrl: edsURL,
|
TypeUrl: version.V2EndpointsURL,
|
||||||
ResourceNames: []string{goodEDSName},
|
ResourceNames: []string{goodEDSName},
|
||||||
}
|
}
|
||||||
goodHTTPConnManager1 = &httppb.HttpConnectionManager{
|
goodHTTPConnManager1 = &httppb.HttpConnectionManager{
|
||||||
|
|
@ -96,17 +109,7 @@ var (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
marshaledConnMgr1, _ = proto.Marshal(goodHTTPConnManager1)
|
marshaledConnMgr1, _ = proto.Marshal(goodHTTPConnManager1)
|
||||||
emptyHTTPConnManager = &httppb.HttpConnectionManager{
|
goodListener1 = &xdspb.Listener{
|
||||||
RouteSpecifier: &httppb.HttpConnectionManager_Rds{
|
|
||||||
Rds: &httppb.Rds{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
emptyMarshaledConnMgr, _ = proto.Marshal(emptyHTTPConnManager)
|
|
||||||
connMgrWithScopedRoutes = &httppb.HttpConnectionManager{
|
|
||||||
RouteSpecifier: &httppb.HttpConnectionManager_ScopedRoutes{},
|
|
||||||
}
|
|
||||||
marshaledConnMgrWithScopedRoutes, _ = proto.Marshal(connMgrWithScopedRoutes)
|
|
||||||
goodListener1 = &xdspb.Listener{
|
|
||||||
Name: goodLDSTarget1,
|
Name: goodLDSTarget1,
|
||||||
ApiListener: &listenerpb.ApiListener{
|
ApiListener: &listenerpb.ApiListener{
|
||||||
ApiListener: &anypb.Any{
|
ApiListener: &anypb.Any{
|
||||||
|
|
@ -128,16 +131,7 @@ var (
|
||||||
marshaledListener2, _ = proto.Marshal(goodListener2)
|
marshaledListener2, _ = proto.Marshal(goodListener2)
|
||||||
noAPIListener = &xdspb.Listener{Name: goodLDSTarget1}
|
noAPIListener = &xdspb.Listener{Name: goodLDSTarget1}
|
||||||
marshaledNoAPIListener, _ = proto.Marshal(noAPIListener)
|
marshaledNoAPIListener, _ = proto.Marshal(noAPIListener)
|
||||||
badAPIListener1 = &xdspb.Listener{
|
badAPIListener2 = &xdspb.Listener{
|
||||||
Name: goodLDSTarget1,
|
|
||||||
ApiListener: &listenerpb.ApiListener{
|
|
||||||
ApiListener: &anypb.Any{
|
|
||||||
TypeUrl: httpConnManagerURL,
|
|
||||||
Value: []byte{1, 2, 3, 4},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
badAPIListener2 = &xdspb.Listener{
|
|
||||||
Name: goodLDSTarget2,
|
Name: goodLDSTarget2,
|
||||||
ApiListener: &listenerpb.ApiListener{
|
ApiListener: &listenerpb.ApiListener{
|
||||||
ApiListener: &anypb.Any{
|
ApiListener: &anypb.Any{
|
||||||
|
|
@ -147,60 +141,33 @@ var (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
badlyMarshaledAPIListener2, _ = proto.Marshal(badAPIListener2)
|
badlyMarshaledAPIListener2, _ = proto.Marshal(badAPIListener2)
|
||||||
badResourceListener = &xdspb.Listener{
|
goodLDSResponse1 = &xdspb.DiscoveryResponse{
|
||||||
Name: goodLDSTarget1,
|
|
||||||
ApiListener: &listenerpb.ApiListener{
|
|
||||||
ApiListener: &anypb.Any{
|
|
||||||
TypeUrl: ldsURL,
|
|
||||||
Value: marshaledListener1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
listenerWithEmptyHTTPConnMgr = &xdspb.Listener{
|
|
||||||
Name: goodLDSTarget1,
|
|
||||||
ApiListener: &listenerpb.ApiListener{
|
|
||||||
ApiListener: &anypb.Any{
|
|
||||||
TypeUrl: httpConnManagerURL,
|
|
||||||
Value: emptyMarshaledConnMgr,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
listenerWithScopedRoutesRouteConfig = &xdspb.Listener{
|
|
||||||
Name: goodLDSTarget1,
|
|
||||||
ApiListener: &listenerpb.ApiListener{
|
|
||||||
ApiListener: &anypb.Any{
|
|
||||||
TypeUrl: httpConnManagerURL,
|
|
||||||
Value: marshaledConnMgrWithScopedRoutes,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
goodLDSResponse1 = &xdspb.DiscoveryResponse{
|
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: marshaledListener1,
|
Value: marshaledListener1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}
|
}
|
||||||
goodLDSResponse2 = &xdspb.DiscoveryResponse{
|
goodLDSResponse2 = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: marshaledListener2,
|
Value: marshaledListener2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}
|
}
|
||||||
emptyLDSResponse = &xdspb.DiscoveryResponse{TypeUrl: ldsURL}
|
emptyLDSResponse = &xdspb.DiscoveryResponse{TypeUrl: version.V2ListenerURL}
|
||||||
badlyMarshaledLDSResponse = &xdspb.DiscoveryResponse{
|
badlyMarshaledLDSResponse = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: []byte{1, 2, 3, 4},
|
Value: []byte{1, 2, 3, 4},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}
|
}
|
||||||
badResourceTypeInLDSResponse = &xdspb.DiscoveryResponse{
|
badResourceTypeInLDSResponse = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
|
|
@ -209,55 +176,55 @@ var (
|
||||||
Value: marshaledConnMgr1,
|
Value: marshaledConnMgr1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}
|
}
|
||||||
ldsResponseWithMultipleResources = &xdspb.DiscoveryResponse{
|
ldsResponseWithMultipleResources = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: marshaledListener2,
|
Value: marshaledListener2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: marshaledListener1,
|
Value: marshaledListener1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}
|
}
|
||||||
noAPIListenerLDSResponse = &xdspb.DiscoveryResponse{
|
noAPIListenerLDSResponse = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: marshaledNoAPIListener,
|
Value: marshaledNoAPIListener,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}
|
}
|
||||||
goodBadUglyLDSResponse = &xdspb.DiscoveryResponse{
|
goodBadUglyLDSResponse = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: marshaledListener2,
|
Value: marshaledListener2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: marshaledListener1,
|
Value: marshaledListener1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
Value: badlyMarshaledAPIListener2,
|
Value: badlyMarshaledAPIListener2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: ldsURL,
|
TypeUrl: version.V2ListenerURL,
|
||||||
}
|
}
|
||||||
badlyMarshaledRDSResponse = &xdspb.DiscoveryResponse{
|
badlyMarshaledRDSResponse = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
Value: []byte{1, 2, 3, 4},
|
Value: []byte{1, 2, 3, 4},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
}
|
}
|
||||||
badResourceTypeInRDSResponse = &xdspb.DiscoveryResponse{
|
badResourceTypeInRDSResponse = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
|
|
@ -266,21 +233,18 @@ var (
|
||||||
Value: marshaledConnMgr1,
|
Value: marshaledConnMgr1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
}
|
}
|
||||||
emptyRouteConfig = &xdspb.RouteConfiguration{}
|
emptyRouteConfig = &xdspb.RouteConfiguration{}
|
||||||
marshaledEmptyRouteConfig, _ = proto.Marshal(emptyRouteConfig)
|
marshaledEmptyRouteConfig, _ = proto.Marshal(emptyRouteConfig)
|
||||||
noDomainsInRouteConfig = &xdspb.RouteConfiguration{
|
noVirtualHostsInRDSResponse = &xdspb.DiscoveryResponse{
|
||||||
VirtualHosts: []*routepb.VirtualHost{{}},
|
|
||||||
}
|
|
||||||
noVirtualHostsInRDSResponse = &xdspb.DiscoveryResponse{
|
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
Value: marshaledEmptyRouteConfig,
|
Value: marshaledEmptyRouteConfig,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
}
|
}
|
||||||
goodRouteConfig1 = &xdspb.RouteConfiguration{
|
goodRouteConfig1 = &xdspb.RouteConfiguration{
|
||||||
Name: goodRouteName1,
|
Name: goodRouteName1,
|
||||||
|
|
@ -349,23 +313,201 @@ var (
|
||||||
goodRDSResponse1 = &xdspb.DiscoveryResponse{
|
goodRDSResponse1 = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
Value: marshaledGoodRouteConfig1,
|
Value: marshaledGoodRouteConfig1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
}
|
}
|
||||||
goodRDSResponse2 = &xdspb.DiscoveryResponse{
|
goodRDSResponse2 = &xdspb.DiscoveryResponse{
|
||||||
Resources: []*anypb.Any{
|
Resources: []*anypb.Any{
|
||||||
{
|
{
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
Value: marshaledGoodRouteConfig2,
|
Value: marshaledGoodRouteConfig2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypeUrl: rdsURL,
|
TypeUrl: version.V2RouteConfigURL,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type watchHandleTestcase struct {
|
||||||
|
typeURL string
|
||||||
|
resourceName string
|
||||||
|
|
||||||
|
responseToHandle *xdspb.DiscoveryResponse
|
||||||
|
wantHandleErr bool
|
||||||
|
wantUpdate interface{}
|
||||||
|
wantUpdateErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type testUpdateReceiver struct {
|
||||||
|
f func(typeURL string, d map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testUpdateReceiver) NewListeners(d map[string]xdsclient.ListenerUpdate) {
|
||||||
|
dd := make(map[string]interface{})
|
||||||
|
for k, v := range d {
|
||||||
|
dd[k] = v
|
||||||
|
}
|
||||||
|
t.newUpdate(version.V2ListenerURL, dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testUpdateReceiver) NewRouteConfigs(d map[string]xdsclient.RouteConfigUpdate) {
|
||||||
|
dd := make(map[string]interface{})
|
||||||
|
for k, v := range d {
|
||||||
|
dd[k] = v
|
||||||
|
}
|
||||||
|
t.newUpdate(version.V2RouteConfigURL, dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testUpdateReceiver) NewClusters(d map[string]xdsclient.ClusterUpdate) {
|
||||||
|
dd := make(map[string]interface{})
|
||||||
|
for k, v := range d {
|
||||||
|
dd[k] = v
|
||||||
|
}
|
||||||
|
t.newUpdate(version.V2ClusterURL, dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testUpdateReceiver) NewEndpoints(d map[string]xdsclient.EndpointsUpdate) {
|
||||||
|
dd := make(map[string]interface{})
|
||||||
|
for k, v := range d {
|
||||||
|
dd[k] = v
|
||||||
|
}
|
||||||
|
t.newUpdate(version.V2EndpointsURL, dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testUpdateReceiver) newUpdate(typeURL string, d map[string]interface{}) {
|
||||||
|
t.f(typeURL, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testWatchHandle is called to test response handling for each xDS.
|
||||||
|
//
|
||||||
|
// It starts the xDS watch as configured in test, waits for the fake xds server
|
||||||
|
// to receive the request (so watch callback is installed), and calls
|
||||||
|
// handleXDSResp with responseToHandle (if it's set). It then compares the
|
||||||
|
// update received by watch callback with the expected results.
|
||||||
|
func testWatchHandle(t *testing.T, test *watchHandleTestcase) {
|
||||||
|
fakeServer, cc, cleanup := startServerAndGetCC(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
type updateErr struct {
|
||||||
|
u interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
gotUpdateCh := testutils.NewChannel()
|
||||||
|
|
||||||
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
|
f: func(typeURL string, d map[string]interface{}) {
|
||||||
|
if typeURL == test.typeURL {
|
||||||
|
if u, ok := d[test.resourceName]; ok {
|
||||||
|
gotUpdateCh.Send(updateErr{u, nil})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
|
|
||||||
|
// RDS needs an existin LDS watch for the hostname.
|
||||||
|
if test.typeURL == version.V2RouteConfigURL {
|
||||||
|
doLDS(t, v2c, fakeServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the watcher, this will also trigger the v2Client to send the xDS
|
||||||
|
// request.
|
||||||
|
v2c.AddWatch(test.typeURL, test.resourceName)
|
||||||
|
|
||||||
|
// Wait till the request makes it to the fakeServer. This ensures that
|
||||||
|
// the watch request has been processed by the v2Client.
|
||||||
|
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
||||||
|
t.Fatalf("Timeout waiting for an xDS request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly push the response through a call to handleXDSResp. This bypasses
|
||||||
|
// the fakeServer, so it's only testing the handle logic. Client response
|
||||||
|
// processing is covered elsewhere.
|
||||||
|
//
|
||||||
|
// Also note that this won't trigger ACK, so there's no need to clear the
|
||||||
|
// request channel afterwards.
|
||||||
|
var handleXDSResp func(response *xdspb.DiscoveryResponse) error
|
||||||
|
switch test.typeURL {
|
||||||
|
case version.V2ListenerURL:
|
||||||
|
handleXDSResp = v2c.handleLDSResponse
|
||||||
|
case version.V2RouteConfigURL:
|
||||||
|
handleXDSResp = v2c.handleRDSResponse
|
||||||
|
case version.V2ClusterURL:
|
||||||
|
handleXDSResp = v2c.handleCDSResponse
|
||||||
|
case version.V2EndpointsURL:
|
||||||
|
handleXDSResp = v2c.handleEDSResponse
|
||||||
|
}
|
||||||
|
if err := handleXDSResp(test.responseToHandle); (err != nil) != test.wantHandleErr {
|
||||||
|
t.Fatalf("v2c.handleRDSResponse() returned err: %v, wantErr: %v", err, test.wantHandleErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the test doesn't expect the callback to be invoked, verify that no
|
||||||
|
// update or error is pushed to the callback.
|
||||||
|
//
|
||||||
|
// Cannot directly compare test.wantUpdate with nil (typed vs non-typed nil:
|
||||||
|
// https://golang.org/doc/faq#nil_error).
|
||||||
|
if c := test.wantUpdate; c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil()) {
|
||||||
|
update, err := gotUpdateCh.Receive()
|
||||||
|
if err == testutils.ErrRecvTimeout {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("Unexpected update: +%v", update)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantUpdate := reflect.ValueOf(test.wantUpdate).Elem().Interface()
|
||||||
|
uErr, err := gotUpdateCh.Receive()
|
||||||
|
if err == testutils.ErrRecvTimeout {
|
||||||
|
t.Fatal("Timeout expecting xDS update")
|
||||||
|
}
|
||||||
|
gotUpdate := uErr.(updateErr).u
|
||||||
|
if diff := cmp.Diff(gotUpdate, wantUpdate); diff != "" {
|
||||||
|
t.Fatalf("got update : %+v, want %+v, diff: %s", gotUpdate, wantUpdate, diff)
|
||||||
|
}
|
||||||
|
gotUpdateErr := uErr.(updateErr).err
|
||||||
|
if (gotUpdateErr != nil) != test.wantUpdateErr {
|
||||||
|
t.Fatalf("got xDS update error {%v}, wantErr: %v", gotUpdateErr, test.wantUpdateErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startServerAndGetCC starts a fake XDS server and also returns a ClientConn
|
||||||
|
// connected to it.
|
||||||
|
func startServerAndGetCC(t *testing.T) (*fakeserver.Server, *grpc.ClientConn, func()) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
fs, sCleanup, err := fakeserver.StartServer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to start fake xDS server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, ccCleanup, err := fs.XDSClientConn()
|
||||||
|
if err != nil {
|
||||||
|
sCleanup()
|
||||||
|
t.Fatalf("Failed to get a clientConn to the fake xDS server: %v", err)
|
||||||
|
}
|
||||||
|
return fs, cc, func() {
|
||||||
|
sCleanup()
|
||||||
|
ccCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newV2Client(p xdsclient.UpdateHandler, cc *grpc.ClientConn, n *basepb.Node, b func(int) time.Duration, l *grpclog.PrefixLogger) (*client, error) {
|
||||||
|
c, err := newClient(cc, xdsclient.BuildOptions{
|
||||||
|
Parent: p,
|
||||||
|
NodeProto: n,
|
||||||
|
Backoff: b,
|
||||||
|
Logger: l,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.(*client), nil
|
||||||
|
}
|
||||||
|
|
||||||
// TestV2ClientBackoffAfterRecvError verifies if the v2Client backoffs when it
|
// TestV2ClientBackoffAfterRecvError verifies if the v2Client backoffs when it
|
||||||
// encounters a Recv error while receiving an LDS response.
|
// encounters a Recv error while receiving an LDS response.
|
||||||
func (s) TestV2ClientBackoffAfterRecvError(t *testing.T) {
|
func (s) TestV2ClientBackoffAfterRecvError(t *testing.T) {
|
||||||
|
|
@ -381,14 +523,17 @@ func (s) TestV2ClientBackoffAfterRecvError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackCh := make(chan struct{})
|
callbackCh := make(chan struct{})
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
f: func(string, map[string]interface{}) { close(callbackCh) },
|
f: func(string, map[string]interface{}) { close(callbackCh) },
|
||||||
}, cc, goodNodeProto, clientBackoff, nil)
|
}, cc, goodNodeProto, clientBackoff, nil)
|
||||||
defer v2c.close()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
t.Log("Started xds v2Client...")
|
t.Log("Started xds v2Client...")
|
||||||
|
|
||||||
// v2c.watchLDS(goodLDSTarget1, func(u ldsUpdate, err error) {})
|
// v2c.watchLDS(goodLDSTarget1, func(u ldsUpdate, err error) {})
|
||||||
v2c.addWatch(ldsURL, goodLDSTarget1)
|
v2c.AddWatch(version.V2ListenerURL, goodLDSTarget1)
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
||||||
t.Fatalf("Timeout expired when expecting an LDS request")
|
t.Fatalf("Timeout expired when expecting an LDS request")
|
||||||
}
|
}
|
||||||
|
|
@ -397,7 +542,7 @@ func (s) TestV2ClientBackoffAfterRecvError(t *testing.T) {
|
||||||
fakeServer.XDSResponseChan <- &fakeserver.Response{Err: errors.New("RPC error")}
|
fakeServer.XDSResponseChan <- &fakeserver.Response{Err: errors.New("RPC error")}
|
||||||
t.Log("Bad LDS response pushed to fakeServer...")
|
t.Log("Bad LDS response pushed to fakeServer...")
|
||||||
|
|
||||||
timer := time.NewTimer(defaultTestTimeout)
|
timer := time.NewTimer(1 * time.Second)
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
t.Fatal("Timeout when expecting LDS update")
|
t.Fatal("Timeout when expecting LDS update")
|
||||||
|
|
@ -417,9 +562,9 @@ func (s) TestV2ClientRetriesAfterBrokenStream(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
callbackCh := testutils.NewChannel()
|
callbackCh := testutils.NewChannel()
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
f: func(typeURL string, d map[string]interface{}) {
|
f: func(typeURL string, d map[string]interface{}) {
|
||||||
if typeURL == ldsURL {
|
if typeURL == version.V2ListenerURL {
|
||||||
if u, ok := d[goodLDSTarget1]; ok {
|
if u, ok := d[goodLDSTarget1]; ok {
|
||||||
t.Logf("Received LDS callback with ldsUpdate {%+v}", u)
|
t.Logf("Received LDS callback with ldsUpdate {%+v}", u)
|
||||||
callbackCh.Send(struct{}{})
|
callbackCh.Send(struct{}{})
|
||||||
|
|
@ -427,10 +572,13 @@ func (s) TestV2ClientRetriesAfterBrokenStream(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
defer v2c.close()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
t.Log("Started xds v2Client...")
|
t.Log("Started xds v2Client...")
|
||||||
|
|
||||||
v2c.addWatch(ldsURL, goodLDSTarget1)
|
v2c.AddWatch(version.V2ListenerURL, goodLDSTarget1)
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
||||||
t.Fatalf("Timeout expired when expecting an LDS request")
|
t.Fatalf("Timeout expired when expecting an LDS request")
|
||||||
}
|
}
|
||||||
|
|
@ -467,12 +615,6 @@ func (s) TestV2ClientRetriesAfterBrokenStream(t *testing.T) {
|
||||||
// level). And when the stream is re-created, the watcher should get future
|
// level). And when the stream is re-created, the watcher should get future
|
||||||
// updates.
|
// updates.
|
||||||
func (s) TestV2ClientWatchWithoutStream(t *testing.T) {
|
func (s) TestV2ClientWatchWithoutStream(t *testing.T) {
|
||||||
oldWatchExpiryTimeout := defaultWatchExpiryTimeout
|
|
||||||
defaultWatchExpiryTimeout = 500 * time.Millisecond
|
|
||||||
defer func() {
|
|
||||||
defaultWatchExpiryTimeout = oldWatchExpiryTimeout
|
|
||||||
}()
|
|
||||||
|
|
||||||
fakeServer, sCleanup, err := fakeserver.StartServer()
|
fakeServer, sCleanup, err := fakeserver.StartServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to start fake xDS server: %v", err)
|
t.Fatalf("Failed to start fake xDS server: %v", err)
|
||||||
|
|
@ -490,9 +632,9 @@ func (s) TestV2ClientWatchWithoutStream(t *testing.T) {
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
callbackCh := testutils.NewChannel()
|
callbackCh := testutils.NewChannel()
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
v2c, err := newV2Client(&testUpdateReceiver{
|
||||||
f: func(typeURL string, d map[string]interface{}) {
|
f: func(typeURL string, d map[string]interface{}) {
|
||||||
if typeURL == ldsURL {
|
if typeURL == version.V2ListenerURL {
|
||||||
if u, ok := d[goodLDSTarget1]; ok {
|
if u, ok := d[goodLDSTarget1]; ok {
|
||||||
t.Logf("Received LDS callback with ldsUpdate {%+v}", u)
|
t.Logf("Received LDS callback with ldsUpdate {%+v}", u)
|
||||||
callbackCh.Send(u)
|
callbackCh.Send(u)
|
||||||
|
|
@ -500,12 +642,15 @@ func (s) TestV2ClientWatchWithoutStream(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
||||||
defer v2c.close()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer v2c.Close()
|
||||||
t.Log("Started xds v2Client...")
|
t.Log("Started xds v2Client...")
|
||||||
|
|
||||||
// This watch is started when the xds-ClientConn is in Transient Failure,
|
// This watch is started when the xds-ClientConn is in Transient Failure,
|
||||||
// and no xds stream is created.
|
// and no xds stream is created.
|
||||||
v2c.addWatch(ldsURL, goodLDSTarget1)
|
v2c.AddWatch(version.V2ListenerURL, goodLDSTarget1)
|
||||||
|
|
||||||
// The watcher should receive an update, with a timeout error in it.
|
// The watcher should receive an update, with a timeout error in it.
|
||||||
if v, err := callbackCh.TimedReceive(100 * time.Millisecond); err == nil {
|
if v, err := callbackCh.TimedReceive(100 * time.Millisecond); err == nil {
|
||||||
|
|
@ -528,7 +673,11 @@ func (s) TestV2ClientWatchWithoutStream(t *testing.T) {
|
||||||
|
|
||||||
if v, err := callbackCh.Receive(); err != nil {
|
if v, err := callbackCh.Receive(); err != nil {
|
||||||
t.Fatal("Timeout when expecting LDS update")
|
t.Fatal("Timeout when expecting LDS update")
|
||||||
} else if _, ok := v.(ldsUpdate); !ok {
|
} else if _, ok := v.(xdsclient.ListenerUpdate); !ok {
|
||||||
t.Fatalf("Expect an LDS update from watcher, got %v", v)
|
t.Fatalf("Expect an LDS update from watcher, got %v", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newStringP(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* 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 (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
||||||
"github.com/golang/protobuf/ptypes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// handleCDSResponse processes an CDS response received from the xDS server. On
|
|
||||||
// receipt of a good response, it also invokes the registered watcher callback.
|
|
||||||
func (v2c *v2Client) handleCDSResponse(resp *xdspb.DiscoveryResponse) error {
|
|
||||||
returnUpdate := make(map[string]ClusterUpdate)
|
|
||||||
for _, r := range resp.GetResources() {
|
|
||||||
var resource ptypes.DynamicAny
|
|
||||||
if err := ptypes.UnmarshalAny(r, &resource); err != nil {
|
|
||||||
return fmt.Errorf("xds: failed to unmarshal resource in CDS response: %v", err)
|
|
||||||
}
|
|
||||||
cluster, ok := resource.Message.(*xdspb.Cluster)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("xds: unexpected resource type: %T in CDS response", resource.Message)
|
|
||||||
}
|
|
||||||
v2c.logger.Infof("Resource with name: %v, type: %T, contains: %v", cluster.GetName(), cluster, cluster)
|
|
||||||
update, err := validateCluster(cluster)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the Cluster message in the CDS response did not contain a
|
|
||||||
// serviceName, we will just use the clusterName for EDS.
|
|
||||||
if update.ServiceName == "" {
|
|
||||||
update.ServiceName = cluster.GetName()
|
|
||||||
}
|
|
||||||
v2c.logger.Debugf("Resource with name %v, type %T, value %+v added to cache", cluster.GetName(), update, update)
|
|
||||||
returnUpdate[cluster.GetName()] = update
|
|
||||||
}
|
|
||||||
|
|
||||||
v2c.parent.newCDSUpdate(returnUpdate)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateCluster(cluster *xdspb.Cluster) (ClusterUpdate, error) {
|
|
||||||
emptyUpdate := ClusterUpdate{ServiceName: "", EnableLRS: false}
|
|
||||||
switch {
|
|
||||||
case cluster.GetType() != xdspb.Cluster_EDS:
|
|
||||||
return emptyUpdate, fmt.Errorf("xds: unexpected cluster type %v in response: %+v", cluster.GetType(), cluster)
|
|
||||||
case cluster.GetEdsClusterConfig().GetEdsConfig().GetAds() == nil:
|
|
||||||
return emptyUpdate, fmt.Errorf("xds: unexpected edsConfig in response: %+v", cluster)
|
|
||||||
case cluster.GetLbPolicy() != xdspb.Cluster_ROUND_ROBIN:
|
|
||||||
return emptyUpdate, fmt.Errorf("xds: unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClusterUpdate{
|
|
||||||
ServiceName: cluster.GetEdsClusterConfig().GetServiceName(),
|
|
||||||
EnableLRS: cluster.GetLrsServer().GetSelf() != nil,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// All structs/functions in this file should be unexported. They are used in EDS
|
|
||||||
// balancer tests now, to generate test inputs. Eventually, EDS balancer tests
|
|
||||||
// should generate EndpointsUpdate directly, instead of generating and parsing the
|
|
||||||
// proto message.
|
|
||||||
// TODO: unexported everything in this file.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
|
||||||
endpointpb "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
|
|
||||||
typepb "github.com/envoyproxy/go-control-plane/envoy/type"
|
|
||||||
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClusterLoadAssignmentBuilder builds a ClusterLoadAssignment, aka EDS
|
|
||||||
// response.
|
|
||||||
type ClusterLoadAssignmentBuilder struct {
|
|
||||||
v *xdspb.ClusterLoadAssignment
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClusterLoadAssignmentBuilder creates a ClusterLoadAssignmentBuilder.
|
|
||||||
func NewClusterLoadAssignmentBuilder(clusterName string, dropPercents []uint32) *ClusterLoadAssignmentBuilder {
|
|
||||||
var drops []*xdspb.ClusterLoadAssignment_Policy_DropOverload
|
|
||||||
for i, d := range dropPercents {
|
|
||||||
drops = append(drops, &xdspb.ClusterLoadAssignment_Policy_DropOverload{
|
|
||||||
Category: fmt.Sprintf("test-drop-%d", i),
|
|
||||||
DropPercentage: &typepb.FractionalPercent{
|
|
||||||
Numerator: d,
|
|
||||||
Denominator: typepb.FractionalPercent_HUNDRED,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ClusterLoadAssignmentBuilder{
|
|
||||||
v: &xdspb.ClusterLoadAssignment{
|
|
||||||
ClusterName: clusterName,
|
|
||||||
Policy: &xdspb.ClusterLoadAssignment_Policy{
|
|
||||||
DropOverloads: drops,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLocalityOptions contains options when adding locality to the builder.
|
|
||||||
type AddLocalityOptions struct {
|
|
||||||
Health []corepb.HealthStatus
|
|
||||||
Weight []uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLocality adds a locality to the builder.
|
|
||||||
func (clab *ClusterLoadAssignmentBuilder) AddLocality(subzone string, weight uint32, priority uint32, addrsWithPort []string, opts *AddLocalityOptions) {
|
|
||||||
var lbEndPoints []*endpointpb.LbEndpoint
|
|
||||||
for i, a := range addrsWithPort {
|
|
||||||
host, portStr, err := net.SplitHostPort(a)
|
|
||||||
if err != nil {
|
|
||||||
panic("failed to split " + a)
|
|
||||||
}
|
|
||||||
port, err := strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
panic("failed to atoi " + portStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
lbe := &endpointpb.LbEndpoint{
|
|
||||||
HostIdentifier: &endpointpb.LbEndpoint_Endpoint{
|
|
||||||
Endpoint: &endpointpb.Endpoint{
|
|
||||||
Address: &corepb.Address{
|
|
||||||
Address: &corepb.Address_SocketAddress{
|
|
||||||
SocketAddress: &corepb.SocketAddress{
|
|
||||||
Protocol: corepb.SocketAddress_TCP,
|
|
||||||
Address: host,
|
|
||||||
PortSpecifier: &corepb.SocketAddress_PortValue{
|
|
||||||
PortValue: uint32(port)}}}}}},
|
|
||||||
}
|
|
||||||
if opts != nil {
|
|
||||||
if i < len(opts.Health) {
|
|
||||||
lbe.HealthStatus = opts.Health[i]
|
|
||||||
}
|
|
||||||
if i < len(opts.Weight) {
|
|
||||||
lbe.LoadBalancingWeight = &wrapperspb.UInt32Value{Value: opts.Weight[i]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lbEndPoints = append(lbEndPoints, lbe)
|
|
||||||
}
|
|
||||||
|
|
||||||
var localityID *corepb.Locality
|
|
||||||
if subzone != "" {
|
|
||||||
localityID = &corepb.Locality{
|
|
||||||
Region: "",
|
|
||||||
Zone: "",
|
|
||||||
SubZone: subzone,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clab.v.Endpoints = append(clab.v.Endpoints, &endpointpb.LocalityLbEndpoints{
|
|
||||||
Locality: localityID,
|
|
||||||
LbEndpoints: lbEndPoints,
|
|
||||||
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: weight},
|
|
||||||
Priority: priority,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build builds ClusterLoadAssignment.
|
|
||||||
func (clab *ClusterLoadAssignmentBuilder) Build() *xdspb.ClusterLoadAssignment {
|
|
||||||
return clab.v
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* 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 (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
||||||
httppb "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
|
|
||||||
"github.com/golang/protobuf/ptypes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// handleLDSResponse processes an LDS response received from the xDS server. On
|
|
||||||
// receipt of a good response, it also invokes the registered watcher callback.
|
|
||||||
func (v2c *v2Client) handleLDSResponse(resp *xdspb.DiscoveryResponse) error {
|
|
||||||
returnUpdate := make(map[string]ldsUpdate)
|
|
||||||
for _, r := range resp.GetResources() {
|
|
||||||
var resource ptypes.DynamicAny
|
|
||||||
if err := ptypes.UnmarshalAny(r, &resource); err != nil {
|
|
||||||
return fmt.Errorf("xds: failed to unmarshal resource in LDS response: %v", err)
|
|
||||||
}
|
|
||||||
lis, ok := resource.Message.(*xdspb.Listener)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("xds: unexpected resource type: %T in LDS response", resource.Message)
|
|
||||||
}
|
|
||||||
v2c.logger.Infof("Resource with name: %v, type: %T, contains: %v", lis.GetName(), lis, lis)
|
|
||||||
routeName, err := v2c.getRouteConfigNameFromListener(lis)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
returnUpdate[lis.GetName()] = ldsUpdate{routeName: routeName}
|
|
||||||
}
|
|
||||||
|
|
||||||
v2c.parent.newLDSUpdate(returnUpdate)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRouteConfigNameFromListener checks if the provided Listener proto meets
|
|
||||||
// the expected criteria. If so, it returns a non-empty routeConfigName.
|
|
||||||
func (v2c *v2Client) getRouteConfigNameFromListener(lis *xdspb.Listener) (string, error) {
|
|
||||||
if lis.GetApiListener() == nil {
|
|
||||||
return "", fmt.Errorf("xds: no api_listener field in LDS response %+v", lis)
|
|
||||||
}
|
|
||||||
var apiAny ptypes.DynamicAny
|
|
||||||
if err := ptypes.UnmarshalAny(lis.GetApiListener().GetApiListener(), &apiAny); err != nil {
|
|
||||||
return "", fmt.Errorf("xds: failed to unmarshal api_listner in LDS response: %v", err)
|
|
||||||
}
|
|
||||||
apiLis, ok := apiAny.Message.(*httppb.HttpConnectionManager)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("xds: unexpected api_listener type: %T in LDS response", apiAny.Message)
|
|
||||||
}
|
|
||||||
v2c.logger.Infof("Resource with type %T, contains %v", apiLis, apiLis)
|
|
||||||
switch apiLis.RouteSpecifier.(type) {
|
|
||||||
case *httppb.HttpConnectionManager_Rds:
|
|
||||||
if apiLis.GetRds().GetConfigSource().GetAds() == nil {
|
|
||||||
return "", fmt.Errorf("xds: ConfigSource is not ADS in LDS response: %+v", lis)
|
|
||||||
}
|
|
||||||
name := apiLis.GetRds().GetRouteConfigName()
|
|
||||||
if name == "" {
|
|
||||||
return "", fmt.Errorf("xds: empty route_config_name in LDS response: %+v", lis)
|
|
||||||
}
|
|
||||||
return name, nil
|
|
||||||
case *httppb.HttpConnectionManager_RouteConfig:
|
|
||||||
// TODO: Add support for specifying the RouteConfiguration inline
|
|
||||||
// in the LDS response.
|
|
||||||
return "", fmt.Errorf("xds: LDS response contains RDS config inline. Not supported for now: %+v", apiLis)
|
|
||||||
case nil:
|
|
||||||
return "", fmt.Errorf("xds: no RouteSpecifier in received LDS response: %+v", apiLis)
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("xds: unsupported type %T for RouteSpecifier in received LDS response", apiLis.RouteSpecifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,323 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* 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 (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
||||||
routepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
|
|
||||||
typepb "github.com/envoyproxy/go-control-plane/envoy/type"
|
|
||||||
"github.com/golang/protobuf/ptypes"
|
|
||||||
"google.golang.org/grpc/internal/grpclog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// handleRDSResponse processes an RDS response received from the xDS server. On
|
|
||||||
// receipt of a good response, it caches validated resources and also invokes
|
|
||||||
// the registered watcher callback.
|
|
||||||
func (v2c *v2Client) handleRDSResponse(resp *xdspb.DiscoveryResponse) error {
|
|
||||||
v2c.mu.Lock()
|
|
||||||
hostname := v2c.hostname
|
|
||||||
v2c.mu.Unlock()
|
|
||||||
|
|
||||||
returnUpdate := make(map[string]rdsUpdate)
|
|
||||||
for _, r := range resp.GetResources() {
|
|
||||||
var resource ptypes.DynamicAny
|
|
||||||
if err := ptypes.UnmarshalAny(r, &resource); err != nil {
|
|
||||||
return fmt.Errorf("xds: failed to unmarshal resource in RDS response: %v", err)
|
|
||||||
}
|
|
||||||
rc, ok := resource.Message.(*xdspb.RouteConfiguration)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("xds: unexpected resource type: %T in RDS response", resource.Message)
|
|
||||||
}
|
|
||||||
v2c.logger.Infof("Resource with name: %v, type: %T, contains: %v. Picking routes for current watching hostname %v", rc.GetName(), rc, rc, v2c.hostname)
|
|
||||||
|
|
||||||
// Use the hostname (resourceName for LDS) to find the routes.
|
|
||||||
u, err := generateRDSUpdateFromRouteConfiguration(rc, hostname, v2c.logger)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("xds: received invalid RouteConfiguration in RDS response: %+v with err: %v", rc, err)
|
|
||||||
}
|
|
||||||
// If we get here, it means that this resource was a good one.
|
|
||||||
returnUpdate[rc.GetName()] = u
|
|
||||||
}
|
|
||||||
|
|
||||||
v2c.parent.newRDSUpdate(returnUpdate)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateRDSUpdateFromRouteConfiguration checks if the provided
|
|
||||||
// RouteConfiguration meets the expected criteria. If so, it returns a rdsUpdate
|
|
||||||
// with nil error.
|
|
||||||
//
|
|
||||||
// A RouteConfiguration resource is considered valid when only if it contains a
|
|
||||||
// VirtualHost whose domain field matches the server name from the URI passed
|
|
||||||
// to the gRPC channel, and it contains a clusterName or a weighted cluster.
|
|
||||||
//
|
|
||||||
// The RouteConfiguration includes a list of VirtualHosts, which may have zero
|
|
||||||
// or more elements. We are interested in the element whose domains field
|
|
||||||
// matches the server name specified in the "xds:" URI. The only field in the
|
|
||||||
// VirtualHost proto that the we are interested in is the list of routes. We
|
|
||||||
// only look at the last route in the list (the default route), whose match
|
|
||||||
// field must be empty and whose route field must be set. Inside that route
|
|
||||||
// message, the cluster field will contain the clusterName or weighted clusters
|
|
||||||
// we are looking for.
|
|
||||||
func generateRDSUpdateFromRouteConfiguration(rc *xdspb.RouteConfiguration, host string, logger *grpclog.PrefixLogger) (rdsUpdate, error) {
|
|
||||||
//
|
|
||||||
// Currently this returns "" on error, and the caller will return an error.
|
|
||||||
// But the error doesn't contain details of why the response is invalid
|
|
||||||
// (mismatch domain or empty route).
|
|
||||||
//
|
|
||||||
// For logging purposes, we can log in line. But if we want to populate
|
|
||||||
// error details for nack, a detailed error needs to be returned.
|
|
||||||
vh := findBestMatchingVirtualHost(host, rc.GetVirtualHosts())
|
|
||||||
if vh == nil {
|
|
||||||
// No matching virtual host found.
|
|
||||||
return rdsUpdate{}, fmt.Errorf("no matching virtual host found")
|
|
||||||
}
|
|
||||||
if len(vh.Routes) == 0 {
|
|
||||||
// The matched virtual host has no routes, this is invalid because there
|
|
||||||
// should be at least one default route.
|
|
||||||
return rdsUpdate{}, fmt.Errorf("matched virtual host has no routes")
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err := routesProtoToSlice(vh.Routes, logger)
|
|
||||||
if err != nil {
|
|
||||||
return rdsUpdate{}, fmt.Errorf("received route is invalid: %v", err)
|
|
||||||
}
|
|
||||||
return rdsUpdate{routes: routes}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func routesProtoToSlice(routes []*routepb.Route, logger *grpclog.PrefixLogger) ([]*Route, error) {
|
|
||||||
var routesRet []*Route
|
|
||||||
|
|
||||||
for _, r := range routes {
|
|
||||||
match := r.GetMatch()
|
|
||||||
if match == nil {
|
|
||||||
return nil, fmt.Errorf("route %+v doesn't have a match", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(match.GetQueryParameters()) != 0 {
|
|
||||||
// Ignore route with query parameters.
|
|
||||||
logger.Warningf("route %+v has query parameter matchers, the route will be ignored", r)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if caseSensitive := match.GetCaseSensitive(); caseSensitive != nil && !caseSensitive.Value {
|
|
||||||
return nil, fmt.Errorf("route %+v has case-sensitive false", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
pathSp := match.GetPathSpecifier()
|
|
||||||
if pathSp == nil {
|
|
||||||
return nil, fmt.Errorf("route %+v doesn't have a path specifier", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
var route Route
|
|
||||||
switch pt := pathSp.(type) {
|
|
||||||
case *routepb.RouteMatch_Prefix:
|
|
||||||
route.Prefix = &pt.Prefix
|
|
||||||
case *routepb.RouteMatch_Path:
|
|
||||||
route.Path = &pt.Path
|
|
||||||
case *routepb.RouteMatch_SafeRegex:
|
|
||||||
route.Regex = &pt.SafeRegex.Regex
|
|
||||||
case *routepb.RouteMatch_Regex:
|
|
||||||
return nil, fmt.Errorf("route %+v has Regex, expected SafeRegex instead", r)
|
|
||||||
default:
|
|
||||||
logger.Warningf("route %+v has an unrecognized path specifier: %+v", r, pt)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, h := range match.GetHeaders() {
|
|
||||||
var header HeaderMatcher
|
|
||||||
switch ht := h.GetHeaderMatchSpecifier().(type) {
|
|
||||||
case *routepb.HeaderMatcher_ExactMatch:
|
|
||||||
header.ExactMatch = &ht.ExactMatch
|
|
||||||
case *routepb.HeaderMatcher_SafeRegexMatch:
|
|
||||||
header.RegexMatch = &ht.SafeRegexMatch.Regex
|
|
||||||
case *routepb.HeaderMatcher_RangeMatch:
|
|
||||||
header.RangeMatch = &Int64Range{
|
|
||||||
Start: ht.RangeMatch.Start,
|
|
||||||
End: ht.RangeMatch.End,
|
|
||||||
}
|
|
||||||
case *routepb.HeaderMatcher_PresentMatch:
|
|
||||||
header.PresentMatch = &ht.PresentMatch
|
|
||||||
case *routepb.HeaderMatcher_PrefixMatch:
|
|
||||||
header.PrefixMatch = &ht.PrefixMatch
|
|
||||||
case *routepb.HeaderMatcher_SuffixMatch:
|
|
||||||
header.SuffixMatch = &ht.SuffixMatch
|
|
||||||
case *routepb.HeaderMatcher_RegexMatch:
|
|
||||||
return nil, fmt.Errorf("route %+v has a header matcher with Regex, expected SafeRegex instead", r)
|
|
||||||
default:
|
|
||||||
logger.Warningf("route %+v has an unrecognized header matcher: %+v", r, ht)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
header.Name = h.GetName()
|
|
||||||
invert := h.GetInvertMatch()
|
|
||||||
header.InvertMatch = &invert
|
|
||||||
route.Headers = append(route.Headers, &header)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fr := match.GetRuntimeFraction(); fr != nil {
|
|
||||||
d := fr.GetDefaultValue()
|
|
||||||
n := d.GetNumerator()
|
|
||||||
switch d.GetDenominator() {
|
|
||||||
case typepb.FractionalPercent_HUNDRED:
|
|
||||||
n *= 10000
|
|
||||||
case typepb.FractionalPercent_TEN_THOUSAND:
|
|
||||||
n *= 100
|
|
||||||
case typepb.FractionalPercent_MILLION:
|
|
||||||
}
|
|
||||||
route.Fraction = &n
|
|
||||||
}
|
|
||||||
|
|
||||||
clusters := make(map[string]uint32)
|
|
||||||
switch a := r.GetRoute().GetClusterSpecifier().(type) {
|
|
||||||
case *routepb.RouteAction_Cluster:
|
|
||||||
clusters[a.Cluster] = 1
|
|
||||||
case *routepb.RouteAction_WeightedClusters:
|
|
||||||
wcs := a.WeightedClusters
|
|
||||||
var totalWeight uint32
|
|
||||||
for _, c := range wcs.Clusters {
|
|
||||||
w := c.GetWeight().GetValue()
|
|
||||||
clusters[c.GetName()] = w
|
|
||||||
totalWeight += w
|
|
||||||
}
|
|
||||||
if totalWeight != wcs.GetTotalWeight().GetValue() {
|
|
||||||
return nil, fmt.Errorf("route %+v, action %+v, weights of clusters do not add up to total total weight, got: %v, want %v", r, a, wcs.GetTotalWeight().GetValue(), totalWeight)
|
|
||||||
}
|
|
||||||
case *routepb.RouteAction_ClusterHeader:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
route.Action = clusters
|
|
||||||
routesRet = append(routesRet, &route)
|
|
||||||
}
|
|
||||||
return routesRet, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func weightedClustersProtoToMap(wc *routepb.WeightedCluster) (map[string]uint32, error) {
|
|
||||||
ret := make(map[string]uint32)
|
|
||||||
var totalWeight uint32 = 100
|
|
||||||
if t := wc.GetTotalWeight().GetValue(); t != 0 {
|
|
||||||
totalWeight = t
|
|
||||||
}
|
|
||||||
for _, cw := range wc.Clusters {
|
|
||||||
w := cw.Weight.GetValue()
|
|
||||||
ret[cw.Name] = w
|
|
||||||
totalWeight -= w
|
|
||||||
}
|
|
||||||
if totalWeight != 0 {
|
|
||||||
return nil, fmt.Errorf("weights of clusters do not add up to total total weight, difference: %v", totalWeight)
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type domainMatchType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
domainMatchTypeInvalid domainMatchType = iota
|
|
||||||
domainMatchTypeUniversal
|
|
||||||
domainMatchTypePrefix
|
|
||||||
domainMatchTypeSuffix
|
|
||||||
domainMatchTypeExact
|
|
||||||
)
|
|
||||||
|
|
||||||
// Exact > Suffix > Prefix > Universal > Invalid.
|
|
||||||
func (t domainMatchType) betterThan(b domainMatchType) bool {
|
|
||||||
return t > b
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchTypeForDomain(d string) domainMatchType {
|
|
||||||
if d == "" {
|
|
||||||
return domainMatchTypeInvalid
|
|
||||||
}
|
|
||||||
if d == "*" {
|
|
||||||
return domainMatchTypeUniversal
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(d, "*") {
|
|
||||||
return domainMatchTypeSuffix
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(d, "*") {
|
|
||||||
return domainMatchTypePrefix
|
|
||||||
}
|
|
||||||
if strings.Contains(d, "*") {
|
|
||||||
return domainMatchTypeInvalid
|
|
||||||
}
|
|
||||||
return domainMatchTypeExact
|
|
||||||
}
|
|
||||||
|
|
||||||
func match(domain, host string) (domainMatchType, bool) {
|
|
||||||
switch typ := matchTypeForDomain(domain); typ {
|
|
||||||
case domainMatchTypeInvalid:
|
|
||||||
return typ, false
|
|
||||||
case domainMatchTypeUniversal:
|
|
||||||
return typ, true
|
|
||||||
case domainMatchTypePrefix:
|
|
||||||
// abc.*
|
|
||||||
return typ, strings.HasPrefix(host, strings.TrimSuffix(domain, "*"))
|
|
||||||
case domainMatchTypeSuffix:
|
|
||||||
// *.123
|
|
||||||
return typ, strings.HasSuffix(host, strings.TrimPrefix(domain, "*"))
|
|
||||||
case domainMatchTypeExact:
|
|
||||||
return typ, domain == host
|
|
||||||
default:
|
|
||||||
return domainMatchTypeInvalid, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// findBestMatchingVirtualHost returns the virtual host whose domains field best
|
|
||||||
// matches host
|
|
||||||
//
|
|
||||||
// The domains field support 4 different matching pattern types:
|
|
||||||
// - Exact match
|
|
||||||
// - Suffix match (e.g. “*ABC”)
|
|
||||||
// - Prefix match (e.g. “ABC*)
|
|
||||||
// - Universal match (e.g. “*”)
|
|
||||||
//
|
|
||||||
// The best match is defined as:
|
|
||||||
// - A match is better if it’s matching pattern type is better
|
|
||||||
// - Exact match > suffix match > prefix match > universal match
|
|
||||||
// - If two matches are of the same pattern type, the longer match is better
|
|
||||||
// - This is to compare the length of the matching pattern, e.g. “*ABCDE” >
|
|
||||||
// “*ABC”
|
|
||||||
func findBestMatchingVirtualHost(host string, vHosts []*routepb.VirtualHost) *routepb.VirtualHost {
|
|
||||||
var (
|
|
||||||
matchVh *routepb.VirtualHost
|
|
||||||
matchType = domainMatchTypeInvalid
|
|
||||||
matchLen int
|
|
||||||
)
|
|
||||||
for _, vh := range vHosts {
|
|
||||||
for _, domain := range vh.GetDomains() {
|
|
||||||
typ, matched := match(domain, host)
|
|
||||||
if typ == domainMatchTypeInvalid {
|
|
||||||
// The rds response is invalid.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched {
|
|
||||||
// The previous match has better type, or the previous match has
|
|
||||||
// better length, or this domain isn't a match.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matchVh = vh
|
|
||||||
matchType = typ
|
|
||||||
matchLen = len(domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matchVh
|
|
||||||
}
|
|
||||||
|
|
@ -1,699 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* 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 (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
|
||||||
corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
|
||||||
routepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
|
|
||||||
typepb "github.com/envoyproxy/go-control-plane/envoy/type"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
|
||||||
"google.golang.org/grpc/xds/internal/testutils/fakeserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
rc *xdspb.RouteConfiguration
|
|
||||||
wantUpdate rdsUpdate
|
|
||||||
wantError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no-virtual-hosts-in-rc",
|
|
||||||
rc: emptyRouteConfig,
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no-domains-in-rc",
|
|
||||||
rc: noDomainsInRouteConfig,
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-matching-domain-in-rc",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{
|
|
||||||
{Domains: []string{uninterestingDomain}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no-routes-in-rc",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{
|
|
||||||
{Domains: []string{goodLDSTarget1}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default-route-match-field-is-nil",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{
|
|
||||||
{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{
|
|
||||||
{
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_Cluster{Cluster: goodClusterName1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default-route-match-field-is-non-nil",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{
|
|
||||||
{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{
|
|
||||||
{
|
|
||||||
Match: &routepb.RouteMatch{},
|
|
||||||
Action: &routepb.Route_Route{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default-route-routeaction-field-is-nil",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{
|
|
||||||
{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default-route-cluster-field-is-empty",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{
|
|
||||||
{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{
|
|
||||||
{
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_ClusterHeader{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// default route's match sets case-sensitive to false.
|
|
||||||
name: "good-route-config-but-with-casesensitive-false",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
Name: goodRouteName1,
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{
|
|
||||||
PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/"},
|
|
||||||
CaseSensitive: &wrapperspb.BoolValue{Value: false},
|
|
||||||
},
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_Cluster{Cluster: goodClusterName1},
|
|
||||||
}}}}}}},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "good-route-config-with-empty-string-route",
|
|
||||||
rc: goodRouteConfig1,
|
|
||||||
wantUpdate: rdsUpdate{routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{goodClusterName1: 1}}}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// default route's match is not empty string, but "/".
|
|
||||||
name: "good-route-config-with-slash-string-route",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
Name: goodRouteName1,
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/"}},
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_Cluster{Cluster: goodClusterName1},
|
|
||||||
}}}}}}},
|
|
||||||
wantUpdate: rdsUpdate{routes: []*Route{{Prefix: newStringP("/"), Action: map[string]uint32{goodClusterName1: 1}}}},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
// weights not add up to total-weight.
|
|
||||||
name: "route-config-with-weighted_clusters_weights_not_add_up",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
Name: goodRouteName1,
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/"}},
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_WeightedClusters{
|
|
||||||
WeightedClusters: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 2}},
|
|
||||||
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 3}},
|
|
||||||
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 5}},
|
|
||||||
},
|
|
||||||
TotalWeight: &wrapperspb.UInt32Value{Value: 30},
|
|
||||||
}}}}}}}}},
|
|
||||||
wantError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "good-route-config-with-weighted_clusters",
|
|
||||||
rc: &xdspb.RouteConfiguration{
|
|
||||||
Name: goodRouteName1,
|
|
||||||
VirtualHosts: []*routepb.VirtualHost{{
|
|
||||||
Domains: []string{goodLDSTarget1},
|
|
||||||
Routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/"}},
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_WeightedClusters{
|
|
||||||
WeightedClusters: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 2}},
|
|
||||||
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 3}},
|
|
||||||
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 5}},
|
|
||||||
},
|
|
||||||
TotalWeight: &wrapperspb.UInt32Value{Value: 10},
|
|
||||||
}}}}}}}}},
|
|
||||||
wantUpdate: rdsUpdate{routes: []*Route{{Prefix: newStringP("/"), Action: map[string]uint32{"a": 2, "b": 3, "c": 5}}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
gotUpdate, gotError := generateRDSUpdateFromRouteConfiguration(test.rc, goodLDSTarget1, nil)
|
|
||||||
if !cmp.Equal(gotUpdate, test.wantUpdate, cmp.AllowUnexported(rdsUpdate{})) || (gotError != nil) != test.wantError {
|
|
||||||
t.Errorf("generateRDSUpdateFromRouteConfiguration(%+v, %v) = %v, want %v", test.rc, goodLDSTarget1, gotUpdate, test.wantUpdate)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// doLDS makes a LDS watch, and waits for the response and ack to finish.
|
|
||||||
//
|
|
||||||
// This is called by RDS tests to start LDS first, because LDS is a
|
|
||||||
// pre-requirement for RDS, and RDS handle would fail without an existing LDS
|
|
||||||
// watch.
|
|
||||||
func doLDS(t *testing.T, v2c *v2Client, fakeServer *fakeserver.Server) {
|
|
||||||
v2c.addWatch(ldsURL, goodLDSTarget1)
|
|
||||||
if _, err := fakeServer.XDSRequestChan.Receive(); err != nil {
|
|
||||||
t.Fatalf("Timeout waiting for LDS request: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRDSHandleResponseWithRouting starts a fake xDS server, makes a ClientConn
|
|
||||||
// to it, and creates a v2Client using it. Then, it registers an LDS and RDS
|
|
||||||
// watcher and tests different RDS responses.
|
|
||||||
func (s) TestRDSHandleResponseWithRouting(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
rdsResponse *xdspb.DiscoveryResponse
|
|
||||||
wantErr bool
|
|
||||||
wantUpdate *rdsUpdate
|
|
||||||
wantUpdateErr bool
|
|
||||||
}{
|
|
||||||
// Badly marshaled RDS response.
|
|
||||||
{
|
|
||||||
name: "badly-marshaled-response",
|
|
||||||
rdsResponse: badlyMarshaledRDSResponse,
|
|
||||||
wantErr: true,
|
|
||||||
wantUpdate: nil,
|
|
||||||
wantUpdateErr: false,
|
|
||||||
},
|
|
||||||
// Response does not contain RouteConfiguration proto.
|
|
||||||
{
|
|
||||||
name: "no-route-config-in-response",
|
|
||||||
rdsResponse: badResourceTypeInRDSResponse,
|
|
||||||
wantErr: true,
|
|
||||||
wantUpdate: nil,
|
|
||||||
wantUpdateErr: false,
|
|
||||||
},
|
|
||||||
// No VirtualHosts in the response. Just one test case here for a bad
|
|
||||||
// RouteConfiguration, since the others are covered in
|
|
||||||
// TestGetClusterFromRouteConfiguration.
|
|
||||||
{
|
|
||||||
name: "no-virtual-hosts-in-response",
|
|
||||||
rdsResponse: noVirtualHostsInRDSResponse,
|
|
||||||
wantErr: true,
|
|
||||||
wantUpdate: nil,
|
|
||||||
wantUpdateErr: false,
|
|
||||||
},
|
|
||||||
// Response contains one good RouteConfiguration, uninteresting though.
|
|
||||||
{
|
|
||||||
name: "one-uninteresting-route-config",
|
|
||||||
rdsResponse: goodRDSResponse2,
|
|
||||||
wantErr: false,
|
|
||||||
wantUpdate: nil,
|
|
||||||
wantUpdateErr: false,
|
|
||||||
},
|
|
||||||
// Response contains one good interesting RouteConfiguration.
|
|
||||||
{
|
|
||||||
name: "one-good-route-config",
|
|
||||||
rdsResponse: goodRDSResponse1,
|
|
||||||
wantErr: false,
|
|
||||||
wantUpdate: &rdsUpdate{routes: []*Route{{Prefix: newStringP(""), Action: map[string]uint32{goodClusterName1: 1}}}},
|
|
||||||
wantUpdateErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one-good-route-config with routes",
|
|
||||||
rdsResponse: goodRDSResponse1,
|
|
||||||
wantErr: false,
|
|
||||||
wantUpdate: &rdsUpdate{
|
|
||||||
// Instead of just weighted targets when routing is disabled,
|
|
||||||
// this result contains a route with perfix "", and action as
|
|
||||||
// weighted targets.
|
|
||||||
routes: []*Route{{
|
|
||||||
Prefix: newStringP(""),
|
|
||||||
Action: map[string]uint32{goodClusterName1: 1},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
wantUpdateErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
testWatchHandle(t, &watchHandleTestcase{
|
|
||||||
typeURL: rdsURL,
|
|
||||||
resourceName: goodRouteName1,
|
|
||||||
responseToHandle: test.rdsResponse,
|
|
||||||
wantHandleErr: test.wantErr,
|
|
||||||
wantUpdate: test.wantUpdate,
|
|
||||||
wantUpdateErr: test.wantUpdateErr,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRDSHandleResponseWithoutLDSWatch tests the case where the v2Client
|
|
||||||
// receives an RDS response without a registered LDS watcher.
|
|
||||||
func (s) TestRDSHandleResponseWithoutLDSWatch(t *testing.T) {
|
|
||||||
_, cc, cleanup := startServerAndGetCC(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
|
||||||
f: func(string, map[string]interface{}) {},
|
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
|
||||||
defer v2c.close()
|
|
||||||
|
|
||||||
if v2c.handleRDSResponse(goodRDSResponse1) == nil {
|
|
||||||
t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRDSHandleResponseWithoutRDSWatch tests the case where the v2Client
|
|
||||||
// receives an RDS response without a registered RDS watcher.
|
|
||||||
func (s) TestRDSHandleResponseWithoutRDSWatch(t *testing.T) {
|
|
||||||
fakeServer, cc, cleanup := startServerAndGetCC(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
v2c := newV2Client(&testUpdateReceiver{
|
|
||||||
f: func(string, map[string]interface{}) {},
|
|
||||||
}, cc, goodNodeProto, func(int) time.Duration { return 0 }, nil)
|
|
||||||
defer v2c.close()
|
|
||||||
doLDS(t, v2c, fakeServer)
|
|
||||||
|
|
||||||
if v2c.handleRDSResponse(badResourceTypeInRDSResponse) == nil {
|
|
||||||
t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if v2c.handleRDSResponse(goodRDSResponse1) != nil {
|
|
||||||
t.Fatal("v2c.handleRDSResponse() succeeded, should have failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s) TestMatchTypeForDomain(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
d string
|
|
||||||
want domainMatchType
|
|
||||||
}{
|
|
||||||
{d: "", want: domainMatchTypeInvalid},
|
|
||||||
{d: "*", want: domainMatchTypeUniversal},
|
|
||||||
{d: "bar.*", want: domainMatchTypePrefix},
|
|
||||||
{d: "*.abc.com", want: domainMatchTypeSuffix},
|
|
||||||
{d: "foo.bar.com", want: domainMatchTypeExact},
|
|
||||||
{d: "foo.*.com", want: domainMatchTypeInvalid},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
if got := matchTypeForDomain(tt.d); got != tt.want {
|
|
||||||
t.Errorf("matchTypeForDomain(%q) = %v, want %v", tt.d, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s) TestMatch(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
domain string
|
|
||||||
host string
|
|
||||||
wantTyp domainMatchType
|
|
||||||
wantMatched bool
|
|
||||||
}{
|
|
||||||
{name: "invalid-empty", domain: "", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false},
|
|
||||||
{name: "invalid", domain: "a.*.b", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false},
|
|
||||||
{name: "universal", domain: "*", host: "abc.com", wantTyp: domainMatchTypeUniversal, wantMatched: true},
|
|
||||||
{name: "prefix-match", domain: "abc.*", host: "abc.123", wantTyp: domainMatchTypePrefix, wantMatched: true},
|
|
||||||
{name: "prefix-no-match", domain: "abc.*", host: "abcd.123", wantTyp: domainMatchTypePrefix, wantMatched: false},
|
|
||||||
{name: "suffix-match", domain: "*.123", host: "abc.123", wantTyp: domainMatchTypeSuffix, wantMatched: true},
|
|
||||||
{name: "suffix-no-match", domain: "*.123", host: "abc.1234", wantTyp: domainMatchTypeSuffix, wantMatched: false},
|
|
||||||
{name: "exact-match", domain: "foo.bar", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: true},
|
|
||||||
{name: "exact-no-match", domain: "foo.bar.com", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: false},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if gotTyp, gotMatched := match(tt.domain, tt.host); gotTyp != tt.wantTyp || gotMatched != tt.wantMatched {
|
|
||||||
t.Errorf("match() = %v, %v, want %v, %v", gotTyp, gotMatched, tt.wantTyp, tt.wantMatched)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s) TestFindBestMatchingVirtualHost(t *testing.T) {
|
|
||||||
var (
|
|
||||||
oneExactMatch = &routepb.VirtualHost{
|
|
||||||
Name: "one-exact-match",
|
|
||||||
Domains: []string{"foo.bar.com"},
|
|
||||||
}
|
|
||||||
oneSuffixMatch = &routepb.VirtualHost{
|
|
||||||
Name: "one-suffix-match",
|
|
||||||
Domains: []string{"*.bar.com"},
|
|
||||||
}
|
|
||||||
onePrefixMatch = &routepb.VirtualHost{
|
|
||||||
Name: "one-prefix-match",
|
|
||||||
Domains: []string{"foo.bar.*"},
|
|
||||||
}
|
|
||||||
oneUniversalMatch = &routepb.VirtualHost{
|
|
||||||
Name: "one-universal-match",
|
|
||||||
Domains: []string{"*"},
|
|
||||||
}
|
|
||||||
longExactMatch = &routepb.VirtualHost{
|
|
||||||
Name: "one-exact-match",
|
|
||||||
Domains: []string{"v2.foo.bar.com"},
|
|
||||||
}
|
|
||||||
multipleMatch = &routepb.VirtualHost{
|
|
||||||
Name: "multiple-match",
|
|
||||||
Domains: []string{"pi.foo.bar.com", "314.*", "*.159"},
|
|
||||||
}
|
|
||||||
vhs = []*routepb.VirtualHost{oneExactMatch, oneSuffixMatch, onePrefixMatch, oneUniversalMatch, longExactMatch, multipleMatch}
|
|
||||||
)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
host string
|
|
||||||
vHosts []*routepb.VirtualHost
|
|
||||||
want *routepb.VirtualHost
|
|
||||||
}{
|
|
||||||
{name: "exact-match", host: "foo.bar.com", vHosts: vhs, want: oneExactMatch},
|
|
||||||
{name: "suffix-match", host: "123.bar.com", vHosts: vhs, want: oneSuffixMatch},
|
|
||||||
{name: "prefix-match", host: "foo.bar.org", vHosts: vhs, want: onePrefixMatch},
|
|
||||||
{name: "universal-match", host: "abc.123", vHosts: vhs, want: oneUniversalMatch},
|
|
||||||
{name: "long-exact-match", host: "v2.foo.bar.com", vHosts: vhs, want: longExactMatch},
|
|
||||||
// Matches suffix "*.bar.com" and exact "pi.foo.bar.com". Takes exact.
|
|
||||||
{name: "multiple-match-exact", host: "pi.foo.bar.com", vHosts: vhs, want: multipleMatch},
|
|
||||||
// Matches suffix "*.159" and prefix "foo.bar.*". Takes suffix.
|
|
||||||
{name: "multiple-match-suffix", host: "foo.bar.159", vHosts: vhs, want: multipleMatch},
|
|
||||||
// Matches suffix "*.bar.com" and prefix "314.*". Takes suffix.
|
|
||||||
{name: "multiple-match-prefix", host: "314.bar.com", vHosts: vhs, want: oneSuffixMatch},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := findBestMatchingVirtualHost(tt.host, tt.vHosts); !cmp.Equal(got, tt.want, cmp.Comparer(proto.Equal)) {
|
|
||||||
t.Errorf("findBestMatchingVirtualHost() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s) TestWeightedClustersProtoToMap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
wc *routepb.WeightedCluster
|
|
||||||
want map[string]uint32
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "weight not add up to non default total",
|
|
||||||
wc: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 1}},
|
|
||||||
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 1}},
|
|
||||||
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 1}},
|
|
||||||
},
|
|
||||||
TotalWeight: &wrapperspb.UInt32Value{Value: 10},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "weight not add up to default total",
|
|
||||||
wc: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 2}},
|
|
||||||
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 3}},
|
|
||||||
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 5}},
|
|
||||||
},
|
|
||||||
TotalWeight: nil,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ok non default total weight",
|
|
||||||
wc: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 2}},
|
|
||||||
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 3}},
|
|
||||||
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 5}},
|
|
||||||
},
|
|
||||||
TotalWeight: &wrapperspb.UInt32Value{Value: 10},
|
|
||||||
},
|
|
||||||
want: map[string]uint32{"a": 2, "b": 3, "c": 5},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ok default total weight is 100",
|
|
||||||
wc: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "a", Weight: &wrapperspb.UInt32Value{Value: 20}},
|
|
||||||
{Name: "b", Weight: &wrapperspb.UInt32Value{Value: 30}},
|
|
||||||
{Name: "c", Weight: &wrapperspb.UInt32Value{Value: 50}},
|
|
||||||
},
|
|
||||||
TotalWeight: nil,
|
|
||||||
},
|
|
||||||
want: map[string]uint32{"a": 20, "b": 30, "c": 50},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := weightedClustersProtoToMap(tt.wc)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("weightedClustersProtoToMap() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !cmp.Equal(got, tt.want) {
|
|
||||||
t.Errorf("weightedClustersProtoToMap() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoutesProtoToSlice(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
routes []*routepb.Route
|
|
||||||
wantRoutes []*Route
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no path",
|
|
||||||
routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{},
|
|
||||||
}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "path is regex instead of saferegex",
|
|
||||||
routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{
|
|
||||||
PathSpecifier: &routepb.RouteMatch_Regex{Regex: "*"},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "header contains regex",
|
|
||||||
routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{
|
|
||||||
PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/"},
|
|
||||||
Headers: []*routepb.HeaderMatcher{{
|
|
||||||
Name: "th",
|
|
||||||
HeaderMatchSpecifier: &routepb.HeaderMatcher_RegexMatch{
|
|
||||||
RegexMatch: "*",
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "case_sensitive is false",
|
|
||||||
routes: []*routepb.Route{{
|
|
||||||
Match: &routepb.RouteMatch{
|
|
||||||
PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/"},
|
|
||||||
CaseSensitive: &wrapperspb.BoolValue{Value: false},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "good",
|
|
||||||
routes: []*routepb.Route{
|
|
||||||
{
|
|
||||||
Match: &routepb.RouteMatch{
|
|
||||||
PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/a/"},
|
|
||||||
Headers: []*routepb.HeaderMatcher{
|
|
||||||
{
|
|
||||||
Name: "th",
|
|
||||||
HeaderMatchSpecifier: &routepb.HeaderMatcher_PrefixMatch{
|
|
||||||
PrefixMatch: "tv",
|
|
||||||
},
|
|
||||||
InvertMatch: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RuntimeFraction: &corepb.RuntimeFractionalPercent{
|
|
||||||
DefaultValue: &typepb.FractionalPercent{
|
|
||||||
Numerator: 1,
|
|
||||||
Denominator: typepb.FractionalPercent_HUNDRED,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_WeightedClusters{
|
|
||||||
WeightedClusters: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "B", Weight: &wrapperspb.UInt32Value{Value: 60}},
|
|
||||||
{Name: "A", Weight: &wrapperspb.UInt32Value{Value: 40}},
|
|
||||||
},
|
|
||||||
TotalWeight: &wrapperspb.UInt32Value{Value: 100},
|
|
||||||
}}}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantRoutes: []*Route{{
|
|
||||||
Prefix: newStringP("/a/"),
|
|
||||||
Headers: []*HeaderMatcher{
|
|
||||||
{
|
|
||||||
Name: "th",
|
|
||||||
InvertMatch: newBoolP(true),
|
|
||||||
PrefixMatch: newStringP("tv"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Fraction: newUInt32P(10000),
|
|
||||||
Action: map[string]uint32{"A": 40, "B": 60},
|
|
||||||
}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "query is ignored",
|
|
||||||
routes: []*routepb.Route{
|
|
||||||
{
|
|
||||||
Match: &routepb.RouteMatch{
|
|
||||||
PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/a/"},
|
|
||||||
},
|
|
||||||
Action: &routepb.Route_Route{
|
|
||||||
Route: &routepb.RouteAction{
|
|
||||||
ClusterSpecifier: &routepb.RouteAction_WeightedClusters{
|
|
||||||
WeightedClusters: &routepb.WeightedCluster{
|
|
||||||
Clusters: []*routepb.WeightedCluster_ClusterWeight{
|
|
||||||
{Name: "B", Weight: &wrapperspb.UInt32Value{Value: 60}},
|
|
||||||
{Name: "A", Weight: &wrapperspb.UInt32Value{Value: 40}},
|
|
||||||
},
|
|
||||||
TotalWeight: &wrapperspb.UInt32Value{Value: 100},
|
|
||||||
}}}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "with_query",
|
|
||||||
Match: &routepb.RouteMatch{
|
|
||||||
PathSpecifier: &routepb.RouteMatch_Prefix{Prefix: "/b/"},
|
|
||||||
QueryParameters: []*routepb.QueryParameterMatcher{{Name: "route_will_be_ignored"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Only one route in the result, because the second one with query
|
|
||||||
// parameters is ignored.
|
|
||||||
wantRoutes: []*Route{{
|
|
||||||
Prefix: newStringP("/a/"),
|
|
||||||
Action: map[string]uint32{"A": 40, "B": 60},
|
|
||||||
}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmpOpts := []cmp.Option{
|
|
||||||
cmp.AllowUnexported(Route{}, HeaderMatcher{}, Int64Range{}),
|
|
||||||
cmpopts.EquateEmpty(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := routesProtoToSlice(tt.routes, nil)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("routesProtoToSlice() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !cmp.Equal(got, tt.wantRoutes, cmpOpts...) {
|
|
||||||
t.Errorf("routesProtoToSlice() got = %v, want %v, diff: %v", got, tt.wantRoutes, cmp.Diff(got, tt.wantRoutes, cmpOpts...))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStringP(s string) *string {
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUInt32P(i uint32) *uint32 {
|
|
||||||
return &i
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBoolP(b bool) *bool {
|
|
||||||
return &b
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
_ "google.golang.org/grpc/xds/internal/balancer/weightedtarget"
|
_ "google.golang.org/grpc/xds/internal/balancer/weightedtarget"
|
||||||
_ "google.golang.org/grpc/xds/internal/balancer/xdsrouting"
|
_ "google.golang.org/grpc/xds/internal/balancer/xdsrouting"
|
||||||
"google.golang.org/grpc/xds/internal/client"
|
|
||||||
xdsclient "google.golang.org/grpc/xds/internal/client"
|
xdsclient "google.golang.org/grpc/xds/internal/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -312,13 +311,13 @@ func TestRoutesToJSON(t *testing.T) {
|
||||||
func TestServiceUpdateToJSON(t *testing.T) {
|
func TestServiceUpdateToJSON(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
su client.ServiceUpdate
|
su xdsclient.ServiceUpdate
|
||||||
wantJSON string
|
wantJSON string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "routing",
|
name: "routing",
|
||||||
su: client.ServiceUpdate{
|
su: xdsclient.ServiceUpdate{
|
||||||
Routes: []*xdsclient.Route{{
|
Routes: []*xdsclient.Route{{
|
||||||
Path: newStringP("/service_1/method_1"),
|
Path: newStringP("/service_1/method_1"),
|
||||||
Action: map[string]uint32{"cluster_1": 75, "cluster_2": 25},
|
Action: map[string]uint32{"cluster_1": 75, "cluster_2": 25},
|
||||||
|
|
|
||||||
|
|
@ -319,11 +319,11 @@ func TestXDSResolverGoodServiceUpdate(t *testing.T) {
|
||||||
defer replaceRandNumGenerator(0)()
|
defer replaceRandNumGenerator(0)()
|
||||||
|
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
su client.ServiceUpdate
|
su xdsclient.ServiceUpdate
|
||||||
wantJSON string
|
wantJSON string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
su: client.ServiceUpdate{Routes: []*client.Route{{Prefix: newStringP(""), Action: map[string]uint32{testCluster1: 1}}}},
|
su: xdsclient.ServiceUpdate{Routes: []*client.Route{{Prefix: newStringP(""), Action: map[string]uint32{testCluster1: 1}}}},
|
||||||
wantJSON: testOneClusterOnlyJSON,
|
wantJSON: testOneClusterOnlyJSON,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,25 @@
|
||||||
package testutils
|
package testutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
|
||||||
v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
|
||||||
|
v2endpointpb "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
|
||||||
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
v2typepb "github.com/envoyproxy/go-control-plane/envoy/type"
|
||||||
|
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
|
||||||
"google.golang.org/grpc/xds/internal"
|
"google.golang.org/grpc/xds/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmptyNodeProtoV2 is a node proto with no fields set.
|
// EmptyNodeProtoV2 is a v2 Node proto with no fields set.
|
||||||
var EmptyNodeProtoV2 = &v2corepb.Node{}
|
var EmptyNodeProtoV2 = &v2corepb.Node{}
|
||||||
|
|
||||||
|
// EmptyNodeProtoV3 is a v3 Node proto with no fields set.
|
||||||
|
var EmptyNodeProtoV3 = &v3corepb.Node{}
|
||||||
|
|
||||||
// LocalityIDToProto converts a LocalityID to its proto representation.
|
// LocalityIDToProto converts a LocalityID to its proto representation.
|
||||||
func LocalityIDToProto(l internal.LocalityID) *v2corepb.Locality {
|
func LocalityIDToProto(l internal.LocalityID) *v2corepb.Locality {
|
||||||
return &v2corepb.Locality{
|
return &v2corepb.Locality{
|
||||||
|
|
@ -33,3 +45,101 @@ func LocalityIDToProto(l internal.LocalityID) *v2corepb.Locality {
|
||||||
SubZone: l.SubZone,
|
SubZone: l.SubZone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The helper structs/functions related to EDS protos are used in EDS balancer
|
||||||
|
// tests now, to generate test inputs. Eventually, EDS balancer tests should
|
||||||
|
// generate EndpointsUpdate directly, instead of generating and parsing the
|
||||||
|
// proto message.
|
||||||
|
// TODO: Once EDS balancer tests don't use these, these can be moved to v2 client code.
|
||||||
|
|
||||||
|
// ClusterLoadAssignmentBuilder builds a ClusterLoadAssignment, aka EDS
|
||||||
|
// response.
|
||||||
|
type ClusterLoadAssignmentBuilder struct {
|
||||||
|
v *v2xdspb.ClusterLoadAssignment
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClusterLoadAssignmentBuilder creates a ClusterLoadAssignmentBuilder.
|
||||||
|
func NewClusterLoadAssignmentBuilder(clusterName string, dropPercents []uint32) *ClusterLoadAssignmentBuilder {
|
||||||
|
var drops []*v2xdspb.ClusterLoadAssignment_Policy_DropOverload
|
||||||
|
for i, d := range dropPercents {
|
||||||
|
drops = append(drops, &v2xdspb.ClusterLoadAssignment_Policy_DropOverload{
|
||||||
|
Category: fmt.Sprintf("test-drop-%d", i),
|
||||||
|
DropPercentage: &v2typepb.FractionalPercent{
|
||||||
|
Numerator: d,
|
||||||
|
Denominator: v2typepb.FractionalPercent_HUNDRED,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ClusterLoadAssignmentBuilder{
|
||||||
|
v: &v2xdspb.ClusterLoadAssignment{
|
||||||
|
ClusterName: clusterName,
|
||||||
|
Policy: &v2xdspb.ClusterLoadAssignment_Policy{
|
||||||
|
DropOverloads: drops,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLocalityOptions contains options when adding locality to the builder.
|
||||||
|
type AddLocalityOptions struct {
|
||||||
|
Health []v2corepb.HealthStatus
|
||||||
|
Weight []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLocality adds a locality to the builder.
|
||||||
|
func (clab *ClusterLoadAssignmentBuilder) AddLocality(subzone string, weight uint32, priority uint32, addrsWithPort []string, opts *AddLocalityOptions) {
|
||||||
|
var lbEndPoints []*v2endpointpb.LbEndpoint
|
||||||
|
for i, a := range addrsWithPort {
|
||||||
|
host, portStr, err := net.SplitHostPort(a)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to split " + a)
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to atoi " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
lbe := &v2endpointpb.LbEndpoint{
|
||||||
|
HostIdentifier: &v2endpointpb.LbEndpoint_Endpoint{
|
||||||
|
Endpoint: &v2endpointpb.Endpoint{
|
||||||
|
Address: &v2corepb.Address{
|
||||||
|
Address: &v2corepb.Address_SocketAddress{
|
||||||
|
SocketAddress: &v2corepb.SocketAddress{
|
||||||
|
Protocol: v2corepb.SocketAddress_TCP,
|
||||||
|
Address: host,
|
||||||
|
PortSpecifier: &v2corepb.SocketAddress_PortValue{
|
||||||
|
PortValue: uint32(port)}}}}}},
|
||||||
|
}
|
||||||
|
if opts != nil {
|
||||||
|
if i < len(opts.Health) {
|
||||||
|
lbe.HealthStatus = opts.Health[i]
|
||||||
|
}
|
||||||
|
if i < len(opts.Weight) {
|
||||||
|
lbe.LoadBalancingWeight = &wrapperspb.UInt32Value{Value: opts.Weight[i]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lbEndPoints = append(lbEndPoints, lbe)
|
||||||
|
}
|
||||||
|
|
||||||
|
var localityID *v2corepb.Locality
|
||||||
|
if subzone != "" {
|
||||||
|
localityID = &v2corepb.Locality{
|
||||||
|
Region: "",
|
||||||
|
Zone: "",
|
||||||
|
SubZone: subzone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clab.v.Endpoints = append(clab.v.Endpoints, &v2endpointpb.LocalityLbEndpoints{
|
||||||
|
Locality: localityID,
|
||||||
|
LbEndpoints: lbEndPoints,
|
||||||
|
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: weight},
|
||||||
|
Priority: priority,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build builds ClusterLoadAssignment.
|
||||||
|
func (clab *ClusterLoadAssignmentBuilder) Build() *v2xdspb.ClusterLoadAssignment {
|
||||||
|
return clab.v
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package version defines supported xDS API versions.
|
// Package version defines constants to distinguish between supported xDS API
|
||||||
|
// versions.
|
||||||
package version
|
package version
|
||||||
|
|
||||||
// TransportAPI refers to the API version for xDS transport protocol. This
|
// TransportAPI refers to the API version for xDS transport protocol. This
|
||||||
|
|
@ -30,3 +31,19 @@ const (
|
||||||
// TransportV3 refers to the v3 xDS transport protocol.
|
// TransportV3 refers to the v3 xDS transport protocol.
|
||||||
TransportV3
|
TransportV3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Resource URLs. We need to be able to accept either version of the resource
|
||||||
|
// regardless of the version of the transport protocol in use.
|
||||||
|
const (
|
||||||
|
V2ListenerURL = "type.googleapis.com/envoy.api.v2.Listener"
|
||||||
|
V2RouteConfigURL = "type.googleapis.com/envoy.api.v2.RouteConfiguration"
|
||||||
|
V2ClusterURL = "type.googleapis.com/envoy.api.v2.Cluster"
|
||||||
|
V2EndpointsURL = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
|
||||||
|
V2HTTPConnManagerURL = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"
|
||||||
|
|
||||||
|
V3ListenerURL = "type.googleapis.com/envoy.config.listener.v3.Listener"
|
||||||
|
V3RouteConfigURL = "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"
|
||||||
|
V3ClusterURL = "type.googleapis.com/envoy.config.cluster.v3.Cluster"
|
||||||
|
V3EndpointsURL = "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
|
||||||
|
V3HTTPConnManagerURL = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
package xds
|
package xds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "google.golang.org/grpc/xds/internal/balancer" // Register the balancers.
|
_ "google.golang.org/grpc/xds/internal/balancer" // Register the balancers.
|
||||||
_ "google.golang.org/grpc/xds/internal/resolver" // Register the xds_resolver
|
_ "google.golang.org/grpc/xds/internal/client/v2" // Register the v2 xDS API client.
|
||||||
|
_ "google.golang.org/grpc/xds/internal/resolver" // Register the xds_resolver.
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue