@@ -1284,11 +1284,16 @@ private void subtestGrpclbFallbackInitialTimeout(boolean timerExpires) {
12841284 for (Subchannel subchannel : mockSubchannels ) {
12851285 verify (subchannelPool ).returnSubchannel (eq (subchannel ), any (ConnectivityStateInfo .class ));
12861286 }
1287+
1288+ // RPC error status includes message of balancer RPC timeout
12871289 inOrder .verify (helper ).updateBalancingState (eq (TRANSIENT_FAILURE ), pickerCaptor .capture ());
1288- RoundRobinPicker picker = (RoundRobinPicker ) pickerCaptor .getValue ();
1289- assertThat (picker .dropList ).isEmpty ();
1290- assertThat (picker .pickList )
1291- .containsExactly (new ErrorEntry (GrpclbState .NO_FALLBACK_BACKENDS_FOUND_STATUS ));
1290+ PickResult result = pickerCaptor .getValue ().pickSubchannel (mock (PickSubchannelArgs .class ));
1291+ assertThat (result .getStatus ().getCode ())
1292+ .isEqualTo (GrpclbState .BALANCER_TIMEOUT_STATUS .getCode ());
1293+ assertThat (result .getStatus ().getDescription ())
1294+ .startsWith (GrpclbState .BALANCER_TIMEOUT_STATUS .getDescription ());
1295+ assertThat (result .getStatus ().getDescription ())
1296+ .contains (GrpclbState .NO_FALLBACK_BACKENDS_ERROR );
12921297 }
12931298
12941299 ////////////////////////////////////////////////////////////////
@@ -1408,6 +1413,24 @@ public void grpclbFallback_breakLbStreamBeforeFallbackTimerExpires() {
14081413 eq (LoadBalanceRequest .newBuilder ().setInitialRequest (
14091414 InitialLoadBalanceRequest .newBuilder ().setName (SERVICE_AUTHORITY ).build ())
14101415 .build ()));
1416+
1417+ //////////////////////////////////////////////////////////////////////
1418+ // Name resolver sends new resolution results without any backend addr
1419+ //////////////////////////////////////////////////////////////////////
1420+ deliverResolvedAddresses (Collections .<EquivalentAddressGroup >emptyList (), grpclbBalancerList );
1421+
1422+ // Still in fallback logic, except that the backend list is empty
1423+ for (Subchannel subchannel : mockSubchannels ) {
1424+ verify (subchannelPool ).returnSubchannel (eq (subchannel ), any (ConnectivityStateInfo .class ));
1425+ }
1426+
1427+ // RPC error status includes error of balancer stream
1428+ inOrder .verify (helper ).updateBalancingState (eq (TRANSIENT_FAILURE ), pickerCaptor .capture ());
1429+ PickResult result = pickerCaptor .getValue ().pickSubchannel (mock (PickSubchannelArgs .class ));
1430+ assertThat (result .getStatus ().getCode ()).isEqualTo (streamError .getCode ());
1431+ assertThat (result .getStatus ().getDescription ()).startsWith (streamError .getDescription ());
1432+ assertThat (result .getStatus ().getDescription ())
1433+ .contains (GrpclbState .NO_FALLBACK_BACKENDS_ERROR );
14111434 }
14121435
14131436 @ Test
@@ -1434,6 +1457,24 @@ public void grpclbFallback_noBalancerAddress() {
14341457 assertEquals (0 , fakeClock .numPendingTasks (FALLBACK_MODE_TASK_FILTER ));
14351458 verify (helper , never ())
14361459 .createOobChannel (ArgumentMatchers .<EquivalentAddressGroup >anyList (), anyString ());
1460+ logs .clear ();
1461+
1462+ ///////////////////////////////////////////////////////////////////////////////////////
1463+ // Name resolver sends new resolution results without any backend addr or balancer addr
1464+ ///////////////////////////////////////////////////////////////////////////////////////
1465+ deliverResolvedAddresses (Collections .<EquivalentAddressGroup >emptyList (),
1466+ Collections .<EquivalentAddressGroup >emptyList ());
1467+ assertThat (logs ).containsExactly (
1468+ "DEBUG: [grpclb-<api.google.com>] Error: Status{code=UNAVAILABLE, "
1469+ + "description=No backend or balancer addresses found, cause=null}" );
1470+
1471+ // Keep using existing fallback addresses without interruption
1472+ for (Subchannel subchannel : mockSubchannels ) {
1473+ verify (subchannelPool , never ())
1474+ .returnSubchannel (eq (subchannel ), any (ConnectivityStateInfo .class ));
1475+ }
1476+ verify (helper , never ())
1477+ .updateBalancingState (eq (TRANSIENT_FAILURE ), any (SubchannelPicker .class ));
14371478 }
14381479
14391480 @ Test
@@ -1531,6 +1572,7 @@ private void subtestGrpclbFallbackConnectionLost(
15311572 }
15321573 assertEquals (0 , fakeClock .numPendingTasks (FALLBACK_MODE_TASK_FILTER ));
15331574
1575+ // No subchannel to fallback backends should have been created if no fallback happened
15341576 if (!(balancerBroken && allSubchannelsBroken )) {
15351577 verify (subchannelPool , never ()).takeOrCreateSubchannel (
15361578 eq (backendList .get (0 )), any (Attributes .class ));
@@ -1539,6 +1581,74 @@ private void subtestGrpclbFallbackConnectionLost(
15391581 }
15401582 }
15411583
1584+ @ Test
1585+ public void grpclbFallback_allLost_failToFallback () {
1586+ long loadReportIntervalMillis = 1983 ;
1587+ InOrder inOrder = inOrder (helper , mockLbService , subchannelPool );
1588+
1589+ // Create balancer and (empty) backend addresses
1590+ List <EquivalentAddressGroup > grpclbBalancerList = createResolvedBalancerAddresses (1 );
1591+ deliverResolvedAddresses (Collections .<EquivalentAddressGroup >emptyList (), grpclbBalancerList );
1592+
1593+ inOrder .verify (helper ).createOobChannel (eq (xattr (grpclbBalancerList )),
1594+ eq (lbAuthority (0 ) + NO_USE_AUTHORITY_SUFFIX ));
1595+
1596+ // Attempted to connect to balancer
1597+ assertEquals (1 , fakeOobChannels .size ());
1598+ fakeOobChannels .poll ();
1599+ inOrder .verify (mockLbService ).balanceLoad (lbResponseObserverCaptor .capture ());
1600+ StreamObserver <LoadBalanceResponse > lbResponseObserver = lbResponseObserverCaptor .getValue ();
1601+ assertEquals (1 , lbRequestObservers .size ());
1602+ StreamObserver <LoadBalanceRequest > lbRequestObserver = lbRequestObservers .poll ();
1603+
1604+ verify (lbRequestObserver ).onNext (
1605+ eq (LoadBalanceRequest .newBuilder ().setInitialRequest (
1606+ InitialLoadBalanceRequest .newBuilder ().setName (SERVICE_AUTHORITY ).build ())
1607+ .build ()));
1608+ lbResponseObserver .onNext (buildInitialResponse (loadReportIntervalMillis ));
1609+ // We don't care if these methods have been run.
1610+ inOrder .verify (helper , atLeast (0 )).getSynchronizationContext ();
1611+ inOrder .verify (helper , atLeast (0 )).getScheduledExecutorService ();
1612+
1613+ inOrder .verifyNoMoreInteractions ();
1614+
1615+ // Balancer returns a server list
1616+ List <ServerEntry > serverList = Arrays .asList (
1617+ new ServerEntry ("127.0.0.1" , 2000 , "token0001" ),
1618+ new ServerEntry ("127.0.0.1" , 2010 , "token0002" ));
1619+ lbResponseObserver .onNext (buildInitialResponse ());
1620+ lbResponseObserver .onNext (buildLbResponse (serverList ));
1621+
1622+ List <Subchannel > subchannels = fallbackTestVerifyUseOfBalancerBackendLists (inOrder , serverList );
1623+
1624+ // Break connections
1625+ lbResponseObserver .onError (Status .UNAVAILABLE .asException ());
1626+ // A new stream to LB is created
1627+ inOrder .verify (mockLbService ).balanceLoad (lbResponseObserverCaptor .capture ());
1628+ lbResponseObserver = lbResponseObserverCaptor .getValue ();
1629+ assertEquals (1 , lbRequestObservers .size ());
1630+ lbRequestObserver = lbRequestObservers .poll ();
1631+
1632+ // Break all subchannel connections
1633+ Status error = Status .UNAUTHENTICATED .withDescription ("Permission denied" );
1634+ for (Subchannel subchannel : subchannels ) {
1635+ deliverSubchannelState (subchannel , ConnectivityStateInfo .forTransientFailure (error ));
1636+ }
1637+
1638+ // Recycle all subchannels
1639+ for (Subchannel subchannel : subchannels ) {
1640+ verify (subchannelPool ).returnSubchannel (eq (subchannel ), any (ConnectivityStateInfo .class ));
1641+ }
1642+
1643+ // RPC error status includes errors of subchannels to balancer-provided backends
1644+ inOrder .verify (helper ).updateBalancingState (eq (TRANSIENT_FAILURE ), pickerCaptor .capture ());
1645+ PickResult result = pickerCaptor .getValue ().pickSubchannel (mock (PickSubchannelArgs .class ));
1646+ assertThat (result .getStatus ().getCode ()).isEqualTo (error .getCode ());
1647+ assertThat (result .getStatus ().getDescription ()).startsWith (error .getDescription ());
1648+ assertThat (result .getStatus ().getDescription ())
1649+ .contains (GrpclbState .NO_FALLBACK_BACKENDS_ERROR );
1650+ }
1651+
15421652 private List <Subchannel > fallbackTestVerifyUseOfFallbackBackendLists (
15431653 InOrder inOrder , List <EquivalentAddressGroup > addrs ) {
15441654 return fallbackTestVerifyUseOfBackendLists (inOrder , addrs , null );
@@ -1958,6 +2068,7 @@ public void grpclbWorking_pickFirstMode_lbSendsEmptyAddress() throws Exception {
19582068 assertThat (mockSubchannels ).isEmpty ();
19592069 verify (subchannel ).shutdown ();
19602070
2071+ // RPC error status includes message of no backends provided by balancer
19612072 inOrder .verify (helper ).updateBalancingState (eq (TRANSIENT_FAILURE ), pickerCaptor .capture ());
19622073 RoundRobinPicker errorPicker = (RoundRobinPicker ) pickerCaptor .getValue ();
19632074 assertThat (errorPicker .pickList )
@@ -2445,7 +2556,7 @@ public void grpclbWorking_lbSendsFallbackMessage() {
24452556 new BackendEntry (subchannel2 , getLoadRecorder (), "token0002" ))
24462557 .inOrder ();
24472558
2448- // enter fallback mode
2559+ // Balancer forces entering fallback mode
24492560 lbResponseObserver .onNext (buildLbFallbackResponse ());
24502561
24512562 // existing subchannels must be returned immediately to gracefully shutdown.
@@ -2460,6 +2571,26 @@ public void grpclbWorking_lbSendsFallbackMessage() {
24602571 assertFalse (oobChannel .isShutdown ());
24612572 verify (lbRequestObserver , never ()).onCompleted ();
24622573
2574+ //////////////////////////////////////////////////////////////////////
2575+ // Name resolver sends new resolution results without any backend addr
2576+ //////////////////////////////////////////////////////////////////////
2577+ deliverResolvedAddresses (Collections .<EquivalentAddressGroup >emptyList (), grpclbBalancerList );
2578+
2579+ // Still in fallback logic, except that the backend list is empty
2580+ for (Subchannel subchannel : mockSubchannels ) {
2581+ verify (subchannelPool ).returnSubchannel (eq (subchannel ), any (ConnectivityStateInfo .class ));
2582+ }
2583+
2584+ // RPC error status includes message of fallback requested by balancer
2585+ inOrder .verify (helper ).updateBalancingState (eq (TRANSIENT_FAILURE ), pickerCaptor .capture ());
2586+ PickResult result = pickerCaptor .getValue ().pickSubchannel (mock (PickSubchannelArgs .class ));
2587+ assertThat (result .getStatus ().getCode ())
2588+ .isEqualTo (GrpclbState .BALANCER_REQUESTED_FALLBACK_STATUS .getCode ());
2589+ assertThat (result .getStatus ().getDescription ())
2590+ .startsWith (GrpclbState .BALANCER_REQUESTED_FALLBACK_STATUS .getDescription ());
2591+ assertThat (result .getStatus ().getDescription ())
2592+ .contains (GrpclbState .NO_FALLBACK_BACKENDS_ERROR );
2593+
24632594 // exit fall back by providing two new backends
24642595 ServerEntry backend2a = new ServerEntry ("127.0.0.1" , 8000 , "token1001" );
24652596 ServerEntry backend2b = new ServerEntry ("127.0.0.1" , 8010 , "token1002" );
0 commit comments