@ -236,6 +236,7 @@ class XdsClientTest : public ::testing::Test {
all_resources_required_in_sotw > ,
ResourceStruct > : : WatcherInterface {
public :
// Returns true if no event is received during the timeout period.
bool ExpectNoEvent ( absl : : Duration timeout ) {
MutexLock lock ( & mu_ ) ;
return ! WaitForEventLocked ( timeout ) ;
@ -323,6 +324,8 @@ class XdsClientTest : public ::testing::Test {
cv_ . Signal ( ) ;
}
// Returns true if an event was received, or false if the timeout
// expires before any event is received.
bool WaitForEventLocked ( absl : : Duration timeout )
ABSL_EXCLUSIVE_LOCKS_REQUIRED ( & mu_ ) {
while ( queue_ . empty ( ) ) {
@ -562,13 +565,12 @@ class XdsClientTest : public ::testing::Test {
// specified bootstrap config.
void InitXdsClient (
FakeXdsBootstrap : : Builder bootstrap_builder = FakeXdsBootstrap : : Builder ( ) ,
Duration resource_request_timeout = Duration : : Seconds ( 15 ) *
grpc_test_slowdown_factor ( ) ) {
Duration resource_request_timeout = Duration : : Seconds ( 15 ) ) {
auto transport_factory = MakeOrphanable < FakeXdsTransportFactory > ( ) ;
transport_factory_ = transport_factory - > Ref ( ) ;
xds_client_ = MakeRefCounted < XdsClient > ( bootstrap_builder . Build ( ) ,
std : : move ( transport_factory ) ,
resource_request_timeout ) ;
xds_client_ = MakeRefCounted < XdsClient > (
bootstrap_builder . Build ( ) , std : : move ( transport_factory ) ,
resource_request_timeout * grpc_test_slowdown_factor ( ) ) ;
}
// Starts and cancels a watch for a Foo resource.
@ -624,11 +626,23 @@ class XdsClientTest : public ::testing::Test {
timeout * grpc_test_slowdown_factor ( ) ) ;
}
void TriggerConnectionFailure ( const XdsBootstrap : : XdsServer & server ,
void ReportConnecting ( const XdsBootstrap : : XdsServer & server ) {
const auto * xds_server = xds_client_ - > bootstrap ( ) . FindXdsServer ( server ) ;
GPR_ASSERT ( xds_server ! = nullptr ) ;
transport_factory_ - > ReportConnecting ( * xds_server ) ;
}
void ReportReady ( const XdsBootstrap : : XdsServer & server ) {
const auto * xds_server = xds_client_ - > bootstrap ( ) . FindXdsServer ( server ) ;
GPR_ASSERT ( xds_server ! = nullptr ) ;
transport_factory_ - > ReportReady ( * xds_server ) ;
}
void ReportTransientFailure ( const XdsBootstrap : : XdsServer & server ,
absl : : Status status ) {
const auto * xds_server = xds_client_ - > bootstrap ( ) . FindXdsServer ( server ) ;
GPR_ASSERT ( xds_server ! = nullptr ) ;
transport_factory_ - > TriggerConnectionFailure ( * xds_server , status ) ;
transport_factory_ - > Report Trans ientFailure( * xds_server , std : : move ( st atus ) ) ;
}
RefCountedPtr < FakeXdsTransportFactory : : FakeStreamingCall > WaitForAdsStream (
@ -1830,14 +1844,8 @@ TEST_F(XdsClientTest, StreamClosedByServer) {
/*resource_names=*/ { " foo1 " } ) ;
// Now server closes the stream.
stream - > MaybeSendStatusToClient ( absl : : OkStatus ( ) ) ;
// XdsClient should report error to watcher.
auto error = watcher - > WaitForNextError ( ) ;
ASSERT_TRUE ( error . has_value ( ) ) ;
EXPECT_EQ ( error - > code ( ) , absl : : StatusCode : : kUnavailable ) ;
EXPECT_EQ ( error - > message ( ) ,
" xDS channel for server default_xds_server: xDS call failed; "
" status: OK (node ID:xds_client_test) " )
< < * error ;
// XdsClient should NOT report error to watcher, because we saw a
// response on the stream before it failed.
// XdsClient should create a new stream.
stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
@ -1853,69 +1861,106 @@ TEST_F(XdsClientTest, StreamClosedByServer) {
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Before the server resends the resource, start a new watcher for the
// same resource. This watcher should immediately receive the cached
// resource and then the error notification -- in that order .
// resource.
auto watcher2 = StartFooWatch ( " foo1 " ) ;
resource = watcher2 - > WaitForNextResource ( ) ;
ASSERT_TRUE ( resource . has_value ( ) ) ;
EXPECT_EQ ( resource - > name , " foo1 " ) ;
EXPECT_EQ ( resource - > value , 6 ) ;
error = watcher2 - > WaitForNextError ( ) ;
ASSERT_TRUE ( error . has_value ( ) ) ;
EXPECT_EQ ( error - > code ( ) , absl : : StatusCode : : kUnavailable ) ;
EXPECT_EQ ( error - > message ( ) ,
" xDS channel for server default_xds_server: xDS call failed; "
" status: OK (node ID:xds_client_test) " )
< < * error ;
// Create a watcher for a new resource. This should immediately
// receive the cached channel error.
auto watcher3 = StartFooWatch ( " foo2 " ) ;
error = watcher3 - > WaitForNextError ( ) ;
// Server now sends the requested resource.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
. set_version_info ( " 1 " )
. set_nonce ( " B " )
. AddFooResource ( XdsFooResource ( " foo1 " , 6 ) )
. Serialize ( ) ) ;
// Watcher does NOT get an update, since the resource has not changed.
EXPECT_FALSE ( watcher - > WaitForNextResource ( ) ) ;
// XdsClient sends an ACK.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " B " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
// Cancel watcher.
CancelFooWatch ( watcher . get ( ) , " foo1 " ) ;
// The XdsClient may or may not send an unsubscription message
// before it closes the transport, depending on callback timing.
request = WaitForRequest ( stream . get ( ) ) ;
if ( request . has_value ( ) ) {
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " B " ,
/*error_detail=*/ absl : : OkStatus ( ) , /*resource_names=*/ { } ) ;
}
}
TEST_F ( XdsClientTest , StreamClosedByServerWithoutSeeingResponse ) {
InitXdsClient ( ) ;
// Start a watch for "foo1".
auto watcher = StartFooWatch ( " foo1 " ) ;
// Watcher should initially not see any resource reported.
EXPECT_FALSE ( watcher - > HasEvent ( ) ) ;
// XdsClient should have created an ADS stream.
auto stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient should have sent a subscription request on the ADS stream.
auto request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Server closes the stream without sending a response.
stream - > MaybeSendStatusToClient ( absl : : UnavailableError ( " ugh " ) ) ;
// XdsClient should report an error to the watcher.
auto error = watcher - > WaitForNextError ( ) ;
ASSERT_TRUE ( error . has_value ( ) ) ;
EXPECT_EQ ( error - > code ( ) , absl : : StatusCode : : kUnavailable ) ;
EXPECT_EQ ( error - > message ( ) ,
" xDS channel for server default_xds_server: xDS call failed; "
" status: OK (node ID:xds_client_test) " )
" xDS channel for server default_xds_server: xDS call failed "
" with no responses received; status: UNAVAILABLE: ugh "
" (node ID:xds_client_test) " )
< < * error ;
// And the client will send a new request subscribing to the new resource.
// XdsClient should create a new stream.
stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient sends a subscription request.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " " ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " , " foo2 " } ) ;
// Server now sends the requested resources.
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Server now sends the requested resource.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
. set_version_info ( " 1 " )
. set_nonce ( " B " )
. set_nonce ( " A " )
. AddFooResource ( XdsFooResource ( " foo1 " , 6 ) )
. AddFooResource ( XdsFooResource ( " foo2 " , 7 ) )
. Serialize ( ) ) ;
// Watchers for foo1 do NOT get an update, since the resource has not changed.
EXPECT_FALSE ( watcher - > WaitForNextResource ( ) ) ;
EXPECT_FALSE ( watcher2 - > WaitForNextResource ( ) ) ;
// The watcher for foo2 gets the newly delivered resource.
resource = watcher3 - > WaitForNextResource ( ) ;
// Watcher gets the resource.
auto resource = watcher - > WaitForNextResource ( ) ;
ASSERT_TRUE ( resource . has_value ( ) ) ;
EXPECT_EQ ( resource - > name , " foo2 " ) ;
EXPECT_EQ ( resource - > value , 7 ) ;
EXPECT_EQ ( resource - > name , " foo1 " ) ;
EXPECT_EQ ( resource - > value , 6 ) ;
// XdsClient sends an ACK.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " B " ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " , " foo2 " } ) ;
// Cancel watchers.
CancelFooWatch ( watcher . get ( ) , " foo1 " , /*delay_unsubscription=*/ true ) ;
CancelFooWatch ( watcher2 . get ( ) , " foo1 " , /*delay_unsubscription=*/ true ) ;
CancelFooWatch ( watcher3 . get ( ) , " foo1 " ) ;
/*resource_names=*/ { " foo1 " } ) ;
// Cancel watcher.
CancelFooWatch ( watcher . get ( ) , " foo1 " ) ;
// The XdsClient may or may not send an unsubscription message
// before it closes the transport, depending on callback timing.
request = WaitForRequest ( stream . get ( ) ) ;
if ( request . has_value ( ) ) {
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " B " ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) , /*resource_names=*/ { } ) ;
}
}
@ -1960,14 +2005,8 @@ TEST_F(XdsClientTest, StreamClosedByServerAndResourcesNotResentOnNewStream) {
/*resource_names=*/ { " foo1 " } ) ;
// Now server closes the stream.
stream - > MaybeSendStatusToClient ( absl : : OkStatus ( ) ) ;
// XdsClient should report error to watcher.
auto error = watcher - > WaitForNextError ( ) ;
ASSERT_TRUE ( error . has_value ( ) ) ;
EXPECT_EQ ( error - > code ( ) , absl : : StatusCode : : kUnavailable ) ;
EXPECT_EQ ( error - > message ( ) ,
" xDS channel for server default_xds_server: xDS call failed; "
" status: OK (node ID:xds_client_test) " )
< < * error ;
// XdsClient should NOT report error to watcher, because we saw a
// response on the stream before it failed.
// XdsClient should create a new stream.
stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
@ -1998,8 +2037,91 @@ TEST_F(XdsClientTest, StreamClosedByServerAndResourcesNotResentOnNewStream) {
}
}
// This test is similar to the previous test, except that the resource
// is not received on the original stream before the stream fails. That
// difference means that we are not expecting the optimization where the
// server may not re-send the resource, which means that we instead
// trigger the does-not-exist timeout.
TEST_F ( XdsClientTest , ResourceDoesNotExistAfterStreamRestart ) {
// Lower resources-does-not-exist timeout so test finishes faster.
InitXdsClient ( FakeXdsBootstrap : : Builder ( ) , Duration : : Seconds ( 3 ) ) ;
// Start a watch for "foo1".
auto watcher = StartFooWatch ( " foo1 " ) ;
// Watcher should initially not see any resource reported.
EXPECT_FALSE ( watcher - > HasEvent ( ) ) ;
// XdsClient should have created an ADS stream.
auto stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient should have sent a subscription request on the ADS stream.
auto request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Stream fails.
stream - > MaybeSendStatusToClient ( absl : : UnavailableError ( " ugh " ) ) ;
// XdsClient should report error to watcher.
auto error = watcher - > WaitForNextError ( ) ;
ASSERT_TRUE ( error . has_value ( ) ) ;
EXPECT_EQ ( error - > code ( ) , absl : : StatusCode : : kUnavailable ) ;
EXPECT_EQ ( error - > message ( ) ,
" xDS channel for server default_xds_server: xDS call failed "
" with no responses received; status: UNAVAILABLE: ugh "
" (node ID:xds_client_test) " )
< < * error ;
// XdsClient should create a new stream.
stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient sends a subscription request.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Server does NOT send a response immediately.
// Client should receive a resource does-not-exist.
ASSERT_TRUE ( watcher - > WaitForDoesNotExist ( absl : : Seconds ( 4 ) ) ) ;
// Server now sends the requested resource.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
. set_version_info ( " 1 " )
. set_nonce ( " A " )
. AddFooResource ( XdsFooResource ( " foo1 " , 6 ) )
. Serialize ( ) ) ;
// The resource is delivered to the watcher.
auto resource = watcher - > WaitForNextResource ( ) ;
ASSERT_TRUE ( resource . has_value ( ) ) ;
EXPECT_EQ ( resource - > name , " foo1 " ) ;
EXPECT_EQ ( resource - > value , 6 ) ;
// XdsClient sends an ACK.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
// Cancel watcher.
CancelFooWatch ( watcher . get ( ) , " foo1 " ) ;
// The XdsClient may or may not send an unsubscription message
// before it closes the transport, depending on callback timing.
request = WaitForRequest ( stream . get ( ) ) ;
if ( request . has_value ( ) ) {
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) , /*resource_names=*/ { } ) ;
}
}
TEST_F ( XdsClientTest , ConnectionFails ) {
InitXdsClient ( ) ;
// Lower resources-does-not-exist timeout, to make sure that we're not
// triggering that here.
InitXdsClient ( FakeXdsBootstrap : : Builder ( ) , Duration : : Seconds ( 3 ) ) ;
// Tell transport not to immediately report that it is connected.
transport_factory_ - > SetAutoReportTransportReady ( false ) ;
// Start a watch for "foo1".
auto watcher = StartFooWatch ( " foo1 " ) ;
// Watcher should initially not see any resource reported.
@ -2016,7 +2138,7 @@ TEST_F(XdsClientTest, ConnectionFails) {
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Transport reports connection failure.
Trigg erCon nec tion Failure ( xds_client_ - > bootstrap ( ) . server ( ) ,
Report Trans ientFailure( xds_client_ - > bootstrap ( ) . server ( ) ,
absl : : UnavailableError ( " connection failed " ) ) ;
// XdsClient should report an error to the watcher.
auto error = watcher - > WaitForNextError ( ) ;
@ -2026,6 +2148,9 @@ TEST_F(XdsClientTest, ConnectionFails) {
" xDS channel for server default_xds_server: "
" connection failed (node ID:xds_client_test) " )
< < * error ;
// We should not see a resource-does-not-exist event, because the
// timer should not be running while the channel is disconnected.
EXPECT_TRUE ( watcher - > ExpectNoEvent ( absl : : Seconds ( 4 ) ) ) ;
// Start a new watch. This watcher should be given the same error,
// since we have not yet recovered.
auto watcher2 = StartFooWatch ( " foo1 " ) ;
@ -2036,11 +2161,12 @@ TEST_F(XdsClientTest, ConnectionFails) {
" xDS channel for server default_xds_server: "
" connection failed (node ID:xds_client_test) " )
< < * error ;
// Inside the XdsTransport interface, the channel will eventually
// reconnect, and the call will proceed. None of that will be visible
// to the XdsClient, because the call uses wait_for_ready. So here,
// to simulate the connection being established, all we need to do is
// allow the stream to proceed.
// Second watcher should not see resource-does-not-exist either.
EXPECT_FALSE ( watcher2 - > HasEvent ( ) ) ;
// Report channel as having become connected.
ReportReady ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// The ADS stream uses wait_for_ready inside the XdsTransport interface,
// so when the channel reconnects, the already-started stream will proceed.
// Server sends a response.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
@ -2060,6 +2186,252 @@ TEST_F(XdsClientTest, ConnectionFails) {
// XdsClient should have sent an ACK message to the xDS server.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
// Cancel watches.
CancelFooWatch ( watcher . get ( ) , " foo1 " ) ;
CancelFooWatch ( watcher2 . get ( ) , " foo1 " ) ;
// The XdsClient may or may not send an unsubscription message
// before it closes the transport, depending on callback timing.
request = WaitForRequest ( stream . get ( ) ) ;
if ( request . has_value ( ) ) {
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) , /*resource_names=*/ { } ) ;
}
}
TEST_F ( XdsClientTest , LongConnectionAttempt ) {
// Lower resources-does-not-exist timeout, to make sure that we're not
// triggering that here.
InitXdsClient ( FakeXdsBootstrap : : Builder ( ) , Duration : : Seconds ( 3 ) ) ;
// Tell transport not to immediately report that it is connected.
transport_factory_ - > SetAutoReportTransportReady ( false ) ;
// Start a watch for "foo1".
auto watcher = StartFooWatch ( " foo1 " ) ;
// Watcher should initially not see any resource reported.
EXPECT_FALSE ( watcher - > HasEvent ( ) ) ;
// XdsClient should have created an ADS stream.
auto stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient should have sent a subscription request on the ADS stream.
auto request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// We should not see a resource-does-not-exist event, because the
// timer should not be running while the channel is disconnected.
EXPECT_TRUE ( watcher - > ExpectNoEvent ( absl : : Seconds ( 4 ) ) ) ;
// Report channel as having become connected.
ReportReady ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// The ADS stream uses wait_for_ready inside the XdsTransport interface,
// so when the channel connects, the already-started stream will proceed.
// Server sends a response.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
. set_version_info ( " 1 " )
. set_nonce ( " A " )
. AddFooResource ( XdsFooResource ( " foo1 " , 6 ) )
. Serialize ( ) ) ;
// XdsClient should have delivered the response to the watcher.
auto resource = watcher - > WaitForNextResource ( ) ;
ASSERT_TRUE ( resource . has_value ( ) ) ;
EXPECT_EQ ( resource - > name , " foo1 " ) ;
EXPECT_EQ ( resource - > value , 6 ) ;
// XdsClient should have sent an ACK message to the xDS server.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
// Cancel watch.
CancelFooWatch ( watcher . get ( ) , " foo1 " ) ;
// The XdsClient may or may not send an unsubscription message
// before it closes the transport, depending on callback timing.
request = WaitForRequest ( stream . get ( ) ) ;
if ( request . has_value ( ) ) {
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) , /*resource_names=*/ { } ) ;
}
}
TEST_F ( XdsClientTest , LongReconnectionAttempt ) {
// Lower resources-does-not-exist timeout, to make sure that we're not
// triggering that here.
InitXdsClient ( FakeXdsBootstrap : : Builder ( ) , Duration : : Seconds ( 3 ) ) ;
// Tell transport not to immediately report that it is connected.
transport_factory_ - > SetAutoReportTransportReady ( false ) ;
// Start a watch for "foo1".
auto watcher = StartFooWatch ( " foo1 " ) ;
// Watcher should initially not see any resource reported.
EXPECT_FALSE ( watcher - > HasEvent ( ) ) ;
// XdsClient should have created an ADS stream.
auto stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient should have sent a subscription request on the ADS stream.
auto request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Transport reports connection.
ReportReady ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// Server sends a response.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
. set_version_info ( " 1 " )
. set_nonce ( " A " )
. AddFooResource ( XdsFooResource ( " foo1 " , 6 ) )
. Serialize ( ) ) ;
// XdsClient should have delivered the response to the watcher.
auto resource = watcher - > WaitForNextResource ( ) ;
ASSERT_TRUE ( resource . has_value ( ) ) ;
EXPECT_EQ ( resource - > name , " foo1 " ) ;
EXPECT_EQ ( resource - > value , 6 ) ;
// XdsClient should have sent an ACK message to the xDS server.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
// Transport reports disconnection.
ReportConnecting ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// Stream fails because of transport disconnection.
stream - > MaybeSendStatusToClient ( absl : : UnavailableError ( " connection failed " ) ) ;
// XdsClient should NOT report error to watcher, because we saw a
// response on the stream before it failed.
// XdsClient creates a new stream.
stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient should have sent a subscription request on the ADS stream.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Report channel as having become connected.
ReportReady ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// We should not see a resource-does-not-exist event, because the
// resource was already cached, so the server can optimize by not
// resending it.
EXPECT_TRUE ( watcher - > ExpectNoEvent ( absl : : Seconds ( 4 ) ) ) ;
// The ADS stream uses wait_for_ready inside the XdsTransport interface,
// so when the channel connects, the already-started stream will proceed.
// Server sends a response.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
. set_version_info ( " 1 " )
. set_nonce ( " A " )
. AddFooResource ( XdsFooResource ( " foo1 " , 6 ) )
. Serialize ( ) ) ;
// Watcher will not see any update, since the resource is unchanged.
EXPECT_TRUE ( watcher - > ExpectNoEvent ( absl : : Seconds ( 1 ) ) ) ;
// XdsClient should have sent an ACK message to the xDS server.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
// Cancel watch.
CancelFooWatch ( watcher . get ( ) , " foo1 " ) ;
// The XdsClient may or may not send an unsubscription message
// before it closes the transport, depending on callback timing.
request = WaitForRequest ( stream . get ( ) ) ;
if ( request . has_value ( ) ) {
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) , /*resource_names=*/ { } ) ;
}
}
// This test is similar to the previous test, except that the resource
// is not received on the original stream before the stream fails. That
// difference means that we are not expecting the optimization where the
// server may not re-send the resource, which means that we instead
// trigger the does-not-exist timeout.
TEST_F ( XdsClientTest , LongReconnectionAttemptBeforeResourceReceived ) {
// Lower resources-does-not-exist timeout, to make sure that we're not
// triggering that here.
InitXdsClient ( FakeXdsBootstrap : : Builder ( ) , Duration : : Seconds ( 3 ) ) ;
// Tell transport not to immediately report that it is connected.
transport_factory_ - > SetAutoReportTransportReady ( false ) ;
// Start a watch for "foo1".
auto watcher = StartFooWatch ( " foo1 " ) ;
// Watcher should initially not see any resource reported.
EXPECT_FALSE ( watcher - > HasEvent ( ) ) ;
// XdsClient should have created an ADS stream.
auto stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient should have sent a subscription request on the ADS stream.
auto request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// Transport reports connected.
ReportReady ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// Server does NOT send a response.
// Now report channel as having become disconnected.
ReportConnecting ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// Stream fails because of transport disconnection.
stream - > MaybeSendStatusToClient ( absl : : UnavailableError ( " connection failed " ) ) ;
// XdsClient should report an error to the watcher.
auto error = watcher - > WaitForNextError ( ) ;
ASSERT_TRUE ( error . has_value ( ) ) ;
EXPECT_EQ ( error - > code ( ) , absl : : StatusCode : : kUnavailable ) ;
EXPECT_EQ ( error - > message ( ) ,
" xDS channel for server default_xds_server: xDS call failed "
" with no responses received; status: "
" UNAVAILABLE: connection failed (node ID:xds_client_test) " )
< < * error ;
// XdsClient creates a new stream.
stream = WaitForAdsStream ( ) ;
ASSERT_TRUE ( stream ! = nullptr ) ;
// XdsClient should have sent a subscription request on the ADS stream.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " " , /*response_nonce=*/ " " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { " foo1 " } ) ;
CheckRequestNode ( * request ) ; // Should be present on the first request.
// We should not see a resource-does-not-exist event, because the
// timer should not be running while the channel is disconnected.
EXPECT_TRUE ( watcher - > ExpectNoEvent ( absl : : Seconds ( 4 ) ) ) ;
// Now report channel as having become connected.
ReportReady ( xds_client_ - > bootstrap ( ) . server ( ) ) ;
// The ADS stream uses wait_for_ready inside the XdsTransport interface,
// so when the channel connects, the already-started stream will proceed.
// Server sends a response.
stream - > SendMessageToClient (
ResponseBuilder ( XdsFooResourceType : : Get ( ) - > type_url ( ) )
. set_version_info ( " 1 " )
. set_nonce ( " A " )
. AddFooResource ( XdsFooResource ( " foo1 " , 6 ) )
. Serialize ( ) ) ;
// XdsClient sends the resource to the watcher.
auto resource = watcher - > WaitForNextResource ( ) ;
ASSERT_TRUE ( resource . has_value ( ) ) ;
EXPECT_EQ ( resource - > name , " foo1 " ) ;
EXPECT_EQ ( resource - > value , 6 ) ;
// XdsClient should have sent an ACK message to the xDS server.
request = WaitForRequest ( stream . get ( ) ) ;
ASSERT_TRUE ( request . has_value ( ) ) ;
CheckRequest ( * request , XdsFooResourceType : : Get ( ) - > type_url ( ) ,
/*version_info=*/ " 1 " , /*response_nonce=*/ " A " ,
/*error_detail=*/ absl : : OkStatus ( ) ,
@ -2578,7 +2950,7 @@ TEST_F(XdsClientTest, FederationChannelFailureReportedToWatchers) {
/*error_detail=*/ absl : : OkStatus ( ) ,
/*resource_names=*/ { kXdstpResourceName } ) ;
// Now cause a channel failure on the stream to the authority's xDS server.
Trigg erCon nec tion Failure ( authority_server ,
Report Trans ientFailure( authority_server ,
absl : : UnavailableError ( " connection failed " ) ) ;
// The watcher for the xdstp resource name should see the error.
auto error = watcher2 - > WaitForNextError ( ) ;