@@ -1453,7 +1453,7 @@ public void ldsResourceDeleted_ignoreResourceDeletion() {
14531453 * and fail RPCs when LDS resource is deleted.
14541454 */
14551455 @ Test
1456- public void ldsResourceDeleted_failOnDataErrors () {
1456+ public void ldsResourceDeleted_failOnDataErrors_true () {
14571457 BootstrapperImpl .xdsDataErrorHandlingEnabled = true ;
14581458 xdsServerInfo = ServerInfo .create (SERVER_URI , CHANNEL_CREDENTIALS , false ,
14591459 true , false , true );
@@ -1508,6 +1508,68 @@ public void ldsResourceDeleted_failOnDataErrors() {
15081508 BootstrapperImpl .xdsDataErrorHandlingEnabled = false ;
15091509 }
15101510
1511+ /**
1512+ * When the fail_on_data_errors server feature is not present, the default behavior
1513+ * is to treat a resource deletion as an ambient error and preserve the cached resource.
1514+ */
1515+ @ Test
1516+ public void ldsResourceDeleted_failOnDataErrors_false () {
1517+ BootstrapperImpl .xdsDataErrorHandlingEnabled = true ;
1518+ xdsServerInfo = ServerInfo .create (SERVER_URI , CHANNEL_CREDENTIALS , false ,
1519+ true , false , false );
1520+ BootstrapInfo bootstrapInfo =
1521+ Bootstrapper .BootstrapInfo .builder ()
1522+ .servers (Collections .singletonList (xdsServerInfo ))
1523+ .node (NODE )
1524+ .authorities (ImmutableMap .of (
1525+ "" ,
1526+ AuthorityInfo .create (
1527+ "xdstp:///envoy.config.listener.v3.Listener/%s" ,
1528+ ImmutableList .of (Bootstrapper .ServerInfo .create (
1529+ SERVER_URI_EMPTY_AUTHORITY , CHANNEL_CREDENTIALS )))))
1530+ .certProviders (ImmutableMap .of ())
1531+ .build ();
1532+ xdsClient = new XdsClientImpl (
1533+ xdsTransportFactory ,
1534+ bootstrapInfo ,
1535+ fakeClock .getScheduledExecutorService (),
1536+ backoffPolicyProvider ,
1537+ fakeClock .getStopwatchSupplier (),
1538+ timeProvider ,
1539+ MessagePrinter .INSTANCE ,
1540+ new TlsContextManagerImpl (bootstrapInfo ),
1541+ xdsClientMetricReporter );
1542+
1543+ InOrder inOrder = inOrder (ldsResourceWatcher );
1544+ DiscoveryRpcCall call = startResourceWatcher (XdsListenerResource .getInstance (), LDS_RESOURCE ,
1545+ ldsResourceWatcher );
1546+ verifyResourceMetadataRequested (LDS , LDS_RESOURCE );
1547+
1548+ // Initial LDS response.
1549+ call .sendResponse (LDS , testListenerVhosts , VERSION_1 , "0000" );
1550+ call .verifyRequest (LDS , LDS_RESOURCE , VERSION_1 , "0000" , NODE );
1551+ inOrder .verify (ldsResourceWatcher ).onResourceChanged (ldsUpdateCaptor .capture ());
1552+ StatusOr <LdsUpdate > statusOrUpdate = ldsUpdateCaptor .getValue ();
1553+ assertThat (statusOrUpdate .hasValue ()).isTrue ();
1554+ verifyGoldenListenerVhosts (statusOrUpdate .getValue ());
1555+ verifyResourceMetadataAcked (LDS , LDS_RESOURCE , testListenerVhosts , VERSION_1 , TIME_INCREMENT );
1556+ verifySubscribedResourcesMetadataSizes (1 , 0 , 0 , 0 );
1557+
1558+ // Empty LDS response deletes the listener and fails RPCs.
1559+ call .sendResponse (LDS , Collections .<Any >emptyList (), VERSION_2 , "0001" );
1560+ call .verifyRequest (LDS , LDS_RESOURCE , VERSION_2 , "0001" , NODE );
1561+ ArgumentCaptor <Status > statusCaptor = ArgumentCaptor .forClass (Status .class );
1562+ inOrder .verify (ldsResourceWatcher ).onAmbientError (statusCaptor .capture ());
1563+ Status receivedStatus = statusCaptor .getValue ();
1564+ assertThat (receivedStatus .getCode ()).isEqualTo (Status .Code .NOT_FOUND );
1565+ assertThat (receivedStatus .getDescription ()).contains (
1566+ "Resource " + LDS_RESOURCE + " deleted from server" );
1567+ inOrder .verify (ldsResourceWatcher , never ()).onResourceChanged (any ());
1568+ verifySubscribedResourcesMetadataSizes (1 , 0 , 0 , 0 );
1569+
1570+ BootstrapperImpl .xdsDataErrorHandlingEnabled = false ;
1571+ }
1572+
15111573 @ Test
15121574 @ SuppressWarnings ("unchecked" )
15131575 public void multipleLdsWatchers () {
@@ -3037,7 +3099,7 @@ public void cdsResourceDeleted_ignoreResourceDeletion() {
30373099 * and fail RPCs when CDS resource is deleted.
30383100 */
30393101 @ Test
3040- public void cdsResourceDeleted_failOnDataErrors () {
3102+ public void cdsResourceDeleted_failOnDataErrors_true () {
30413103 BootstrapperImpl .xdsDataErrorHandlingEnabled = true ;
30423104 xdsServerInfo = ServerInfo .create (SERVER_URI , CHANNEL_CREDENTIALS , false ,
30433105 true , false , true );
@@ -3090,6 +3152,74 @@ public void cdsResourceDeleted_failOnDataErrors() {
30903152 BootstrapperImpl .xdsDataErrorHandlingEnabled = false ;
30913153 }
30923154
3155+ /**
3156+ * When fail_on_data_errors server feature is on, xDS client should delete the cached cluster
3157+ * and fail RPCs when CDS resource is deleted.
3158+ */
3159+ @ Test
3160+ public void cdsResourceDeleted_failOnDataErrors_false () {
3161+ BootstrapperImpl .xdsDataErrorHandlingEnabled = true ;
3162+ // Set failOnDataErrors to false for this test case.
3163+ xdsServerInfo = ServerInfo .create (SERVER_URI , CHANNEL_CREDENTIALS , false ,
3164+ true , false , false );
3165+ BootstrapInfo bootstrapInfo =
3166+ Bootstrapper .BootstrapInfo .builder ()
3167+ .servers (Collections .singletonList (xdsServerInfo ))
3168+ .node (NODE )
3169+ .authorities (ImmutableMap .of (
3170+ "" ,
3171+ AuthorityInfo .create (
3172+ "xdstp:///envoy.config.listener.v3.Listener/%s" ,
3173+ ImmutableList .of (Bootstrapper .ServerInfo .create (
3174+ SERVER_URI_EMPTY_AUTHORITY , CHANNEL_CREDENTIALS )))))
3175+ .certProviders (ImmutableMap .of ())
3176+ .build ();
3177+ xdsClient = new XdsClientImpl (
3178+ xdsTransportFactory ,
3179+ bootstrapInfo ,
3180+ fakeClock .getScheduledExecutorService (),
3181+ backoffPolicyProvider ,
3182+ fakeClock .getStopwatchSupplier (),
3183+ timeProvider ,
3184+ MessagePrinter .INSTANCE ,
3185+ new TlsContextManagerImpl (bootstrapInfo ),
3186+ xdsClientMetricReporter );
3187+
3188+ InOrder inOrder = inOrder (cdsResourceWatcher );
3189+ DiscoveryRpcCall call = startResourceWatcher (XdsClusterResource .getInstance (), CDS_RESOURCE ,
3190+ cdsResourceWatcher );
3191+ verifyResourceMetadataRequested (CDS , CDS_RESOURCE );
3192+
3193+ // Initial CDS response.
3194+ call .sendResponse (CDS , testClusterRoundRobin , VERSION_1 , "0000" );
3195+ call .verifyRequest (CDS , CDS_RESOURCE , VERSION_1 , "0000" , NODE );
3196+ inOrder .verify (cdsResourceWatcher ).onResourceChanged (cdsUpdateCaptor .capture ());
3197+ StatusOr <CdsUpdate > statusOrUpdate = cdsUpdateCaptor .getValue ();
3198+ assertThat (statusOrUpdate .hasValue ()).isTrue ();
3199+ verifyGoldenClusterRoundRobin (statusOrUpdate .getValue ());
3200+ verifyResourceMetadataAcked (CDS , CDS_RESOURCE , testClusterRoundRobin , VERSION_1 ,
3201+ TIME_INCREMENT );
3202+ verifySubscribedResourcesMetadataSizes (0 , 1 , 0 , 0 );
3203+
3204+ // Empty CDS response should trigger an ambient error.
3205+ call .sendResponse (CDS , Collections .<Any >emptyList (), VERSION_2 , "0001" );
3206+ call .verifyRequest (CDS , CDS_RESOURCE , VERSION_2 , "0001" , NODE );
3207+
3208+ // Verify that onAmbientError() is called.
3209+ ArgumentCaptor <Status > statusCaptor = ArgumentCaptor .forClass (Status .class );
3210+ inOrder .verify (cdsResourceWatcher ).onAmbientError (statusCaptor .capture ());
3211+ Status receivedStatus = statusCaptor .getValue ();
3212+ assertThat (receivedStatus .getCode ()).isEqualTo (Status .Code .NOT_FOUND );
3213+ assertThat (receivedStatus .getDescription ()).contains (
3214+ "Resource " + CDS_RESOURCE + " deleted from server" );
3215+
3216+ // Verify that onResourceChanged() is NOT called again.
3217+ inOrder .verify (cdsResourceWatcher , never ()).onResourceChanged (any ());
3218+ verifySubscribedResourcesMetadataSizes (0 , 1 , 0 , 0 );
3219+
3220+ BootstrapperImpl .xdsDataErrorHandlingEnabled = false ;
3221+ }
3222+
30933223 @ Test
30943224 @ SuppressWarnings ("unchecked" )
30953225 public void multipleCdsWatchers () {
0 commit comments