util: Status desc for outlier detection ejection (#11036)

Including a Status description makes it easier to debug subchannel
closure issues if it's clear that a subchannel became unavailable because
of an outlier detection ejection.
This commit is contained in:
Terry Wilson 2024-03-22 14:40:06 -07:00 committed by GitHub
parent bdb623031f
commit 10cb4a3bed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 35 additions and 9 deletions

View File

@ -377,8 +377,9 @@ public final class OutlierDetectionLoadBalancer extends LoadBalancer {
void eject() { void eject() {
ejected = true; ejected = true;
subchannelStateListener.onSubchannelState( subchannelStateListener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(
ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)); Status.UNAVAILABLE.withDescription(
"The subchannel has been ejected by outlier detection")));
logger.log(ChannelLogLevel.INFO, "Subchannel ejected: {0}", this); logger.log(ChannelLogLevel.INFO, "Subchannel ejected: {0}", this);
} }

View File

@ -19,8 +19,8 @@ package io.grpc.util;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@ -50,6 +50,7 @@ import io.grpc.LoadBalancer.SubchannelStateListener;
import io.grpc.LoadBalancerProvider; import io.grpc.LoadBalancerProvider;
import io.grpc.Metadata; import io.grpc.Metadata;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.Status.Code;
import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext;
import io.grpc.internal.FakeClock; import io.grpc.internal.FakeClock;
import io.grpc.internal.FakeClock.ScheduledTask; import io.grpc.internal.FakeClock.ScheduledTask;
@ -1203,9 +1204,21 @@ public class OutlierDetectionLoadBalancerTest {
// The one subchannel that was returning errors should be ejected. // The one subchannel that was returning errors should be ejected.
assertEjectedSubchannels(ImmutableSet.of(ImmutableSet.copyOf(servers.get(0).getAddresses()))); assertEjectedSubchannels(ImmutableSet.of(ImmutableSet.copyOf(servers.get(0).getAddresses())));
if (hasHealthConsumer) { if (hasHealthConsumer) {
verify(healthListeners.get(servers.get(0))).onSubchannelState(eq( ArgumentCaptor<ConnectivityStateInfo> csiCaptor = ArgumentCaptor.forClass(
ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE) ConnectivityStateInfo.class);
)); verify(healthListeners.get(servers.get(0)), times(2)).onSubchannelState(csiCaptor.capture());
List<ConnectivityStateInfo> connectivityStateInfos = csiCaptor.getAllValues();
// The subchannel went through two state transitions...
assertThat(connectivityStateInfos).hasSize(2);
// ...it first went to the READY state...
assertThat(connectivityStateInfos.get(0).getState()).isEqualTo(READY);
// ...and then to TRANSIENT_FAILURE as outlier detection ejected it.
assertThat(connectivityStateInfos.get(1).getState()).isEqualTo(TRANSIENT_FAILURE);
assertThat(connectivityStateInfos.get(1).getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
assertThat(connectivityStateInfos.get(1).getStatus().getDescription()).isEqualTo(
"The subchannel has been ejected by outlier detection");
} }
} }
@ -1264,9 +1277,21 @@ public class OutlierDetectionLoadBalancerTest {
// The one subchannel that was returning errors should be ejected. // The one subchannel that was returning errors should be ejected.
assertEjectedSubchannels(ImmutableSet.of(ImmutableSet.copyOf(servers.get(0).getAddresses()))); assertEjectedSubchannels(ImmutableSet.of(ImmutableSet.copyOf(servers.get(0).getAddresses())));
if (hasHealthConsumer) { if (hasHealthConsumer) {
verify(healthListeners.get(servers.get(0))).onSubchannelState(eq( ArgumentCaptor<ConnectivityStateInfo> csiCaptor = ArgumentCaptor.forClass(
ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE) ConnectivityStateInfo.class);
)); verify(healthListeners.get(servers.get(0)), times(2)).onSubchannelState(csiCaptor.capture());
List<ConnectivityStateInfo> connectivityStateInfos = csiCaptor.getAllValues();
// The subchannel went through two state transitions...
assertThat(connectivityStateInfos).hasSize(2);
// ...it first went to the READY state...
assertThat(connectivityStateInfos.get(0).getState()).isEqualTo(READY);
// ...and then to TRANSIENT_FAILURE as outlier detection ejected it.
assertThat(connectivityStateInfos.get(1).getState()).isEqualTo(TRANSIENT_FAILURE);
assertThat(connectivityStateInfos.get(1).getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
assertThat(connectivityStateInfos.get(1).getStatus().getDescription()).isEqualTo(
"The subchannel has been ejected by outlier detection");
} }
} }