5959import io .grpc .MetricRecorder .BatchRecorder ;
6060import io .grpc .MetricRecorder .Registration ;
6161import io .grpc .NameResolver .ConfigOrError ;
62+ import io .grpc .Server ;
6263import io .grpc .Status ;
6364import io .grpc .Status .Code ;
6465import io .grpc .SynchronizationContext ;
6566import io .grpc .inprocess .InProcessChannelBuilder ;
6667import io .grpc .inprocess .InProcessServerBuilder ;
6768import io .grpc .internal .BackoffPolicy ;
6869import io .grpc .internal .FakeClock ;
70+ import io .grpc .internal .GrpcUtil ;
71+ import io .grpc .internal .ObjectPool ;
6972import io .grpc .internal .PickSubchannelArgsImpl ;
73+ import io .grpc .internal .SharedResourcePool ;
7074import io .grpc .lookup .v1 .RouteLookupServiceGrpc ;
7175import io .grpc .rls .CachingRlsLbClient .CacheEntry ;
7276import io .grpc .rls .CachingRlsLbClient .CachedRouteLookupResponse ;
96100import java .util .Map ;
97101import java .util .Set ;
98102import java .util .concurrent .ExecutionException ;
103+ import java .util .concurrent .Executor ;
104+ import java .util .concurrent .ExecutorService ;
99105import java .util .concurrent .ScheduledExecutorService ;
100106import java .util .concurrent .ScheduledFuture ;
101107import java .util .concurrent .TimeUnit ;
102108import java .util .concurrent .TimeoutException ;
109+ import java .util .concurrent .atomic .AtomicBoolean ;
103110import javax .annotation .Nonnull ;
104111import org .junit .After ;
105112import org .junit .Before ;
@@ -160,8 +167,9 @@ public void uncaughtException(Thread t, Throwable e) {
160167 fakeClock .getScheduledExecutorService ());
161168 private final ChildLoadBalancingPolicy childLbPolicy =
162169 new ChildLoadBalancingPolicy ("target" , Collections .<String , Object >emptyMap (), lbProvider );
170+ private final FakeHelper fakeHelper = new FakeHelper ();
163171 private final Helper helper =
164- mock (Helper .class , delegatesTo (new FakeHelper () ));
172+ mock (Helper .class , delegatesTo (fakeHelper ));
165173 private final FakeThrottler fakeThrottler = new FakeThrottler ();
166174 private final LbPolicyConfiguration lbPolicyConfiguration =
167175 new LbPolicyConfiguration (ROUTE_LOOKUP_CONFIG , null , childLbPolicy );
@@ -325,29 +333,94 @@ public void get_throttledAndRecover() throws Exception {
325333
326334 assertThat (resp .hasError ()).isTrue ();
327335
336+ // let it pass throttler
337+ fakeThrottler .nextResult = false ;
328338 fakeClock .forwardTime (10 , TimeUnit .MILLISECONDS );
329- // initially backed off entry is backed off again
339+ // Backoff entry evicted from cache.
330340 verify (evictionListener )
331341 .onEviction (eq (routeLookupRequest ), any (CacheEntry .class ), eq (EvictionType .EXPLICIT ));
342+ // Assert that Rls LB policy picker was updated.
343+ assertThat (fakeHelper .lastPicker .toString ()).isEqualTo ("RlsPicker{target=service1}" );
344+ }
332345
333- resp = getInSyncContext (routeLookupRequest );
334-
335- assertThat (resp .hasError ()).isTrue ();
336-
337- // let it pass throttler
338- fakeThrottler .nextResult = false ;
339- fakeClock .forwardTime (10 , TimeUnit .MILLISECONDS );
346+ @ Test
347+ public void controlPlaneTransientToReady_backOffEntriesRemovedAndPickerUpdated ()
348+ throws Exception {
349+ setUpRlsLbClient ();
350+ final ConnectivityState [] rlsChannelState = new ConnectivityState [1 ];
351+ Runnable channelStateListener = new Runnable () {
352+ @ Override
353+ public void run () {
354+ rlsChannelState [0 ] = fakeHelper .oobChannel .getState (false );
355+ fakeHelper .oobChannel .notifyWhenStateChanged (rlsChannelState [0 ], this );
356+ synchronized (this ) {
357+ notify ();
358+ }
359+ }
360+ };
361+ fakeHelper .oobChannel .notifyWhenStateChanged (fakeHelper .oobChannel .getState (false ),
362+ channelStateListener );
363+ RouteLookupRequest routeLookupRequest = RouteLookupRequest .create (ImmutableMap .of (
364+ "server" , "bigtable.googleapis.com" , "service-key" , "foo" , "method-key" , "bar" ));
365+ rlsServerImpl .setLookupTable (
366+ ImmutableMap .of (
367+ routeLookupRequest ,
368+ RouteLookupResponse .create (ImmutableList .of ("target" ), "header" )));
340369
370+ CachedRouteLookupResponse resp = getInSyncContext (routeLookupRequest );
371+ assertThat (resp .isPending ()).isTrue ();
372+ // server response
373+ fakeClock .forwardTime (SERVER_LATENCY_MILLIS , TimeUnit .MILLISECONDS );
341374 resp = getInSyncContext (routeLookupRequest );
375+ assertThat (resp .hasData ()).isTrue ();
342376
377+ fakeHelper .server .shutdown ();
378+ // Channel goes to IDLE state from the shutdown listener handling.
379+ try {
380+ if (!fakeHelper .server .awaitTermination (10 , TimeUnit .SECONDS )) {
381+ fakeHelper .server .shutdownNow (); // Forceful shutdown if graceful timeout expires
382+ }
383+ } catch (InterruptedException e ) {
384+ fakeHelper .server .shutdownNow ();
385+ }
386+ // Use a different key to cause a cache miss and trigger a RPC.
387+ RouteLookupRequest routeLookupRequest2 = RouteLookupRequest .create (ImmutableMap .of (
388+ "server" , "bigtable.googleapis.com" , "service-key" , "foo2" , "method-key" , "bar" ));
389+ // Rls channel will go to TRANSIENT_FAILURE (back-off) because the picker notices the
390+ // subchannel state IDLE and the server transport listener is null.
391+ resp = getInSyncContext (routeLookupRequest2 );
343392 assertThat (resp .isPending ()).isTrue ();
393+ assertThat (rlsChannelState [0 ]).isEqualTo (ConnectivityState .TRANSIENT_FAILURE );
394+ // Throttle the next rpc call.
395+ fakeThrottler .nextResult = true ;
396+ fakeBackoffProvider .nextPolicy = createBackoffPolicy (10 , TimeUnit .MILLISECONDS );
344397
345- // server responses
346- fakeClock .forwardTime (SERVER_LATENCY_MILLIS , TimeUnit .MILLISECONDS );
398+ // Cause another cache miss by using a new request key. This will create a back-off Rls
399+ // cache entry.
400+ RouteLookupRequest routeLookupRequest3 = RouteLookupRequest .create (ImmutableMap .of (
401+ "server" , "bigtable.googleapis.com" , "service-key" , "foo3" , "method-key" , "bar" ));
402+ resp = getInSyncContext (routeLookupRequest3 );
347403
348- resp = getInSyncContext ( routeLookupRequest );
404+ assertThat ( resp . hasError ()). isTrue ( );
349405
350- assertThat (resp .hasData ()).isTrue ();
406+ fakeHelper .createServerAndRegister ("service1" );
407+ // Wait for Rls subchannel back-off expiry and its moving to READY
408+ synchronized (channelStateListener ) {
409+ channelStateListener .wait (5000 );
410+ }
411+ assertThat (rlsChannelState [0 ]).isEqualTo (ConnectivityState .READY );
412+ final ObjectPool <? extends Executor > defaultExecutorPool =
413+ SharedResourcePool .forResource (GrpcUtil .SHARED_CHANNEL_EXECUTOR );
414+ AtomicBoolean isSuccess = new AtomicBoolean (false );
415+ ((ExecutorService ) defaultExecutorPool .getObject ()).submit (() -> {
416+ // Backoff entry evicted from cache.
417+ verify (evictionListener )
418+ .onEviction (eq (routeLookupRequest3 ), any (CacheEntry .class ), eq (EvictionType .EXPLICIT ));
419+ // Assert that Rls LB policy picker was updated.
420+ assertThat (fakeHelper .lastPicker .toString ()).isEqualTo ("RlsPicker{target=service1}" );
421+ isSuccess .set (true );
422+ }).get ();
423+ assertThat (isSuccess .get ()).isTrue ();
351424 }
352425
353426 @ Test
@@ -894,16 +967,24 @@ public void run() {
894967
895968 private final class FakeHelper extends Helper {
896969
970+ SubchannelPicker lastPicker ;
971+ Server server ;
972+ ManagedChannel oobChannel ;
973+
974+ void createServerAndRegister (String target ) throws IOException {
975+ server = InProcessServerBuilder .forName (target )
976+ .addService (rlsServerImpl )
977+ .directExecutor ()
978+ .build ()
979+ .start ();
980+ grpcCleanupRule .register (server );
981+ }
982+
897983 @ Override
898984 public ManagedChannelBuilder <?> createResolvingOobChannelBuilder (
899985 String target , ChannelCredentials creds ) {
900986 try {
901- grpcCleanupRule .register (
902- InProcessServerBuilder .forName (target )
903- .addService (rlsServerImpl )
904- .directExecutor ()
905- .build ()
906- .start ());
987+ createServerAndRegister (target );
907988 } catch (IOException e ) {
908989 throw new RuntimeException ("cannot create server: " + target , e );
909990 }
@@ -919,7 +1000,8 @@ protected ManagedChannelBuilder<?> delegate() {
9191000
9201001 @ Override
9211002 public ManagedChannel build () {
922- return grpcCleanupRule .register (super .build ());
1003+ oobChannel = super .build ();
1004+ return grpcCleanupRule .register (oobChannel );
9231005 }
9241006
9251007 @ Override
@@ -948,7 +1030,7 @@ public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String author
9481030 @ Override
9491031 public void updateBalancingState (
9501032 @ Nonnull ConnectivityState newState , @ Nonnull SubchannelPicker newPicker ) {
951- // no-op
1033+ lastPicker = newPicker ;
9521034 }
9531035
9541036 @ Override
0 commit comments