use an attribute from resolved addresses IS_PETIOLE_POLICY to control whether or not health checking is supported (#11513)

* use an attribute from resolved addresses IS_PETIOLE_POLICY to control whether or not health checking is supported so that top level versions can't do any health checking, while those under petiole policies can.

Fixes #11413
This commit is contained in:
Larry Safran 2024-09-06 11:43:07 -07:00 committed by GitHub
parent f6d2f20fcd
commit 5de65a6d50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 5 deletions

View File

@ -69,6 +69,7 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
private ConnectivityState concludedState = IDLE;
private final boolean enableHappyEyeballs =
PickFirstLoadBalancerProvider.isEnabledHappyEyeballs();
private boolean notAPetiolePolicy = true; // means not under a petiole policy
PickFirstLeafLoadBalancer(Helper helper) {
this.helper = checkNotNull(helper, "helper");
@ -80,6 +81,10 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
return Status.FAILED_PRECONDITION.withDescription("Already shut down");
}
// Cache whether or not this is a petiole policy, which is based off of an address attribute
Boolean isPetiolePolicy = resolvedAddresses.getAttributes().get(IS_PETIOLE_POLICY);
this.notAPetiolePolicy = isPetiolePolicy == null || !isPetiolePolicy;
List<EquivalentAddressGroup> servers = resolvedAddresses.getAddresses();
// Validate the address list
@ -303,7 +308,8 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
if (subchannelData.state != READY) {
return;
}
if (subchannelData.getHealthState() == READY) {
if (notAPetiolePolicy || subchannelData.getHealthState() == READY) {
updateBalancingState(READY,
new FixedResultPicker(PickResult.withSubchannel(subchannelData.subchannel)));
} else if (subchannelData.getHealthState() == TRANSIENT_FAILURE) {
@ -444,7 +450,7 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
hcListener.subchannelData = subchannelData;
subchannels.put(addr, subchannelData);
Attributes scAttrs = subchannel.getAttributes();
if (scAttrs.get(LoadBalancer.HAS_HEALTH_PRODUCER_LISTENER_KEY) == null) {
if (notAPetiolePolicy || scAttrs.get(LoadBalancer.HAS_HEALTH_PRODUCER_LISTENER_KEY) == null) {
subchannelData.healthStateInfo = ConnectivityStateInfo.forNonError(READY);
}
subchannel.start(stateInfo -> processSubchannelState(subchannelData, stateInfo));
@ -468,6 +474,13 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
@Override
public void onSubchannelState(ConnectivityStateInfo newState) {
if (notAPetiolePolicy) {
log.log(Level.WARNING,
"Ignoring health status {0} for subchannel {1} as this is not under a petiole policy",
new Object[]{newState, subchannelData.subchannel});
return;
}
log.log(Level.FINE, "Received health status {0} for subchannel {1}",
new Object[]{newState, subchannelData.subchannel});
subchannelData.healthStateInfo = newState;

View File

@ -25,6 +25,7 @@ import static io.grpc.ConnectivityState.SHUTDOWN;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
import static io.grpc.LoadBalancer.HAS_HEALTH_PRODUCER_LISTENER_KEY;
import static io.grpc.LoadBalancer.HEALTH_CONSUMER_LISTENER_ARG_KEY;
import static io.grpc.LoadBalancer.IS_PETIOLE_POLICY;
import static io.grpc.internal.PickFirstLeafLoadBalancer.CONNECTION_DELAY_INTERVAL_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -389,15 +390,44 @@ public class PickFirstLeafLoadBalancerTest {
verify(mockSubchannel2).requestConnection();
}
@Test
public void healthCheck_nonPetiolePolicy() {
when(mockSubchannel1.getAttributes()).thenReturn(
Attributes.newBuilder().set(HAS_HEALTH_PRODUCER_LISTENER_KEY, true).build());
// Initialize with one server loadbalancer and both health and state listeners
List<EquivalentAddressGroup> oneServer = Lists.newArrayList(servers.get(0));
loadBalancer.acceptResolvedAddresses(ResolvedAddresses.newBuilder().setAddresses(oneServer)
.setAttributes(Attributes.EMPTY).build());
InOrder inOrder = inOrder(mockHelper, mockSubchannel1);
inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), any(SubchannelPicker.class));
inOrder.verify(mockHelper).createSubchannel(createArgsCaptor.capture());
SubchannelStateListener healthListener = createArgsCaptor.getValue()
.getOption(HEALTH_CONSUMER_LISTENER_ARG_KEY);
inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture());
SubchannelStateListener stateListener = stateListenerCaptor.getValue();
stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING));
healthListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING));
inOrder.verify(mockHelper, never()).updateBalancingState(any(), any());
stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(READY));
inOrder.verify(mockHelper).updateBalancingState(eq(READY), any()); // health listener ignored
healthListener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(Status.INTERNAL));
inOrder.verify(mockHelper, never()).updateBalancingState(any(), any(SubchannelPicker.class));
}
@Test
public void healthCheckFlow() {
when(mockSubchannel1.getAttributes()).thenReturn(
Attributes.newBuilder().set(HAS_HEALTH_PRODUCER_LISTENER_KEY, true).build());
when(mockSubchannel2.getAttributes()).thenReturn(
Attributes.newBuilder().set(HAS_HEALTH_PRODUCER_LISTENER_KEY, true).build());
List<EquivalentAddressGroup> oneServer = Lists.newArrayList(servers.get(0), servers.get(1));
loadBalancer.acceptResolvedAddresses(ResolvedAddresses.newBuilder().setAddresses(oneServer)
.setAttributes(Attributes.EMPTY).build());
.setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build()).build());
InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2);
inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), any(SubchannelPicker.class));
@ -413,13 +443,13 @@ public class PickFirstLeafLoadBalancerTest {
// subchannel2 | IDLE | IDLE
stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING));
healthListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING));
inOrder.verify(mockHelper, times(0)).updateBalancingState(any(), any());
inOrder.verify(mockHelper, never()).updateBalancingState(any(), any());
// subchannel | state | health
// subchannel1 | READY | CONNECTING
// subchannel2 | IDLE | IDLE
stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(READY));
inOrder.verify(mockHelper, times(0)).updateBalancingState(any(), any());
inOrder.verify(mockHelper, never()).updateBalancingState(any(), any());
// subchannel | state | health
// subchannel1 | READY | READY