mirror of https://github.com/grpc/grpc-java.git
util: a util to gracefully switch load balancer when lb policy changes
Following the section "Gracefully Switching LB Policies" in the spec go/grpc-client-channel-spec
This commit is contained in:
parent
1fbc61b280
commit
d7b9438d39
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright 2019 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.grpc.ConnectivityState;
|
||||
import io.grpc.ConnectivityStateInfo;
|
||||
import io.grpc.ExperimentalApi;
|
||||
import io.grpc.LoadBalancer;
|
||||
import io.grpc.LoadBalancerProvider;
|
||||
import io.grpc.Status;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A load balancer that gracefully swaps to a new lb policy. If the channel is currently in a state
|
||||
* other than READY, the new policy will be swapped into place immediately. Otherwise, the channel
|
||||
* will keep using the old policy until the new policy reports READY or the old policy exits READY.
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5999")
|
||||
@NotThreadSafe // Must be accessed in SynchronizationContext
|
||||
public final class GracefulSwitchLoadBalancer extends ForwardingLoadBalancer {
|
||||
private static final LoadBalancer NOOP_BALANCER = new LoadBalancer() {
|
||||
@Override
|
||||
public void handleNameResolutionError(Status error) {}
|
||||
|
||||
@Override
|
||||
public void shutdown() {}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
static final SubchannelPicker BUFFER_PICKER = new SubchannelPicker() {
|
||||
@Override
|
||||
public PickResult pickSubchannel(PickSubchannelArgs args) {
|
||||
return PickResult.withNoResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BUFFER_PICKER";
|
||||
}
|
||||
};
|
||||
|
||||
private final Helper helper;
|
||||
|
||||
// While the new policy is not fully switched on, the pendingLb is handling new updates from name
|
||||
// resolver, and the currentLb is updating channel state and picker for the given helper.
|
||||
// The current fields are guaranteed to be set after the initial swapTo().
|
||||
// The pending fields are cleared when it becomes current.
|
||||
@Nullable private String currentPolicyName;
|
||||
private LoadBalancer currentLb = NOOP_BALANCER;
|
||||
@Nullable private String pendingPolicyName;
|
||||
private LoadBalancer pendingLb = NOOP_BALANCER;
|
||||
private ConnectivityState pendingState;
|
||||
private SubchannelPicker pendingPicker;
|
||||
|
||||
private boolean currentLbIsReady;
|
||||
|
||||
public GracefulSwitchLoadBalancer(Helper helper) {
|
||||
this.helper = checkNotNull(helper, "helper");
|
||||
}
|
||||
|
||||
/** Gracefully switch to a new load balancing policy. */
|
||||
public void switchTo(LoadBalancerProvider newLbProvider) {
|
||||
checkNotNull(newLbProvider, "newLbProvider");
|
||||
|
||||
String newPolicyName = newLbProvider.getPolicyName();
|
||||
if (newPolicyName.equals(pendingPolicyName)) {
|
||||
return;
|
||||
}
|
||||
pendingLb.shutdown();
|
||||
pendingLb = NOOP_BALANCER;
|
||||
pendingPolicyName = null;
|
||||
pendingState = ConnectivityState.CONNECTING;
|
||||
pendingPicker = BUFFER_PICKER;
|
||||
|
||||
if (newPolicyName.equals(currentPolicyName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class PendingHelper extends ForwardingLoadBalancerHelper {
|
||||
LoadBalancer lb;
|
||||
|
||||
@Override
|
||||
protected Helper delegate() {
|
||||
return helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
|
||||
if (lb == pendingLb) {
|
||||
checkState(currentLbIsReady, "there's pending lb while current lb has been out of READY");
|
||||
pendingState = newState;
|
||||
pendingPicker = newPicker;
|
||||
if (newState == ConnectivityState.READY) {
|
||||
swap();
|
||||
}
|
||||
} else if (lb == currentLb) {
|
||||
currentLbIsReady = newState == ConnectivityState.READY;
|
||||
if (!currentLbIsReady && pendingLb != NOOP_BALANCER) {
|
||||
swap(); // current policy exits READY, so swap
|
||||
} else {
|
||||
helper.updateBalancingState(newState, newPicker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PendingHelper pendingHelper = new PendingHelper();
|
||||
pendingHelper.lb = newLbProvider.newLoadBalancer(pendingHelper);
|
||||
pendingLb = pendingHelper.lb;
|
||||
pendingPolicyName = newPolicyName;
|
||||
if (!currentLbIsReady) {
|
||||
swap(); // the old policy is not READY at the moment, so swap to the new one right now
|
||||
}
|
||||
}
|
||||
|
||||
private void swap() {
|
||||
helper.updateBalancingState(pendingState, pendingPicker);
|
||||
currentLb.shutdown();
|
||||
currentLb = pendingLb;
|
||||
currentPolicyName = pendingPolicyName;
|
||||
pendingLb = NOOP_BALANCER;
|
||||
pendingPolicyName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LoadBalancer delegate() {
|
||||
return pendingLb == NOOP_BALANCER ? currentLb : pendingLb;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void handleSubchannelState(
|
||||
Subchannel subchannel, ConnectivityStateInfo stateInfo) {
|
||||
throw new UnsupportedOperationException(
|
||||
"handleSubchannelState() is not supported by " + this.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
pendingLb.shutdown();
|
||||
currentLb.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
* Copyright 2019 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.util;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static io.grpc.ConnectivityState.CONNECTING;
|
||||
import static io.grpc.ConnectivityState.READY;
|
||||
import static io.grpc.util.GracefulSwitchLoadBalancer.BUFFER_PICKER;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import io.grpc.ConnectivityStateInfo;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.LoadBalancer;
|
||||
import io.grpc.LoadBalancer.CreateSubchannelArgs;
|
||||
import io.grpc.LoadBalancer.Helper;
|
||||
import io.grpc.LoadBalancer.ResolvedAddresses;
|
||||
import io.grpc.LoadBalancer.Subchannel;
|
||||
import io.grpc.LoadBalancer.SubchannelPicker;
|
||||
import io.grpc.LoadBalancerProvider;
|
||||
import io.grpc.LoadBalancerRegistry;
|
||||
import io.grpc.Status;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
/**
|
||||
* Tests for {@link GracefulSwitchLoadBalancer}.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class GracefulSwitchLoadBalancerTest {
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private final LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry();
|
||||
// maps policy name to lb provide
|
||||
private final Map<String, LoadBalancerProvider> lbProviders = new HashMap<>();
|
||||
// maps policy name to lb
|
||||
private final Map<String, LoadBalancer> balancers = new HashMap<>();
|
||||
private final Map<LoadBalancer, Helper> helpers = new HashMap<>();
|
||||
private final Helper mockHelper = mock(Helper.class);
|
||||
private final GracefulSwitchLoadBalancer gracefulSwitchLb =
|
||||
new GracefulSwitchLoadBalancer(mockHelper);
|
||||
private final String[] lbPolicies = {"lb_policy_0", "lb_policy_1", "lb_policy_2", "lb_policy_3"};
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
for (int i = 0; i < lbPolicies.length; i++) {
|
||||
String lbPolicy = lbPolicies[i];
|
||||
LoadBalancerProvider lbProvider = new FakeLoadBalancerProvider(lbPolicy);
|
||||
lbProviders.put(lbPolicy, lbProvider);
|
||||
lbRegistry.register(lbProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canHandleEmptyAddressListFromNameResolutionForwardedToLatestPolicy() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse();
|
||||
doReturn(true).when(lb0).canHandleEmptyAddressListFromNameResolution();
|
||||
assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue();
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
|
||||
assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse();
|
||||
|
||||
doReturn(true).when(lb1).canHandleEmptyAddressListFromNameResolution();
|
||||
assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue();
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2]));
|
||||
LoadBalancer lb2 = balancers.get(lbPolicies[2]);
|
||||
|
||||
assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse();
|
||||
|
||||
doReturn(true).when(lb2).canHandleEmptyAddressListFromNameResolution();
|
||||
assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleResolvedAddressesAndNameResolutionErrorForwardedToLatestPolicy() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
ResolvedAddresses addresses = newFakeAddresses();
|
||||
gracefulSwitchLb.handleResolvedAddresses(addresses);
|
||||
verify(lb0).handleResolvedAddresses(addresses);
|
||||
gracefulSwitchLb.handleNameResolutionError(Status.DATA_LOSS);
|
||||
verify(lb0).handleNameResolutionError(Status.DATA_LOSS);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
addresses = newFakeAddresses();
|
||||
gracefulSwitchLb.handleResolvedAddresses(addresses);
|
||||
verify(lb0, never()).handleResolvedAddresses(addresses);
|
||||
verify(lb1).handleResolvedAddresses(addresses);
|
||||
gracefulSwitchLb.handleNameResolutionError(Status.ALREADY_EXISTS);
|
||||
verify(lb0, never()).handleNameResolutionError(Status.ALREADY_EXISTS);
|
||||
verify(lb1).handleNameResolutionError(Status.ALREADY_EXISTS);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2]));
|
||||
verify(lb1).shutdown();
|
||||
LoadBalancer lb2 = balancers.get(lbPolicies[2]);
|
||||
addresses = newFakeAddresses();
|
||||
gracefulSwitchLb.handleResolvedAddresses(addresses);
|
||||
verify(lb0, never()).handleResolvedAddresses(addresses);
|
||||
verify(lb1, never()).handleResolvedAddresses(addresses);
|
||||
verify(lb2).handleResolvedAddresses(addresses);
|
||||
gracefulSwitchLb.handleNameResolutionError(Status.CANCELLED);
|
||||
verify(lb0, never()).handleNameResolutionError(Status.CANCELLED);
|
||||
verify(lb1, never()).handleNameResolutionError(Status.CANCELLED);
|
||||
verify(lb2).handleNameResolutionError(Status.CANCELLED);
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1, lb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shutdownTriggeredWhenSwitchAndForwardedWhenSwitchLbShutdown() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
verify(lb1, never()).shutdown();
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2]));
|
||||
verify(lb1).shutdown();
|
||||
LoadBalancer lb2 = balancers.get(lbPolicies[2]);
|
||||
verify(lb0, never()).shutdown();
|
||||
helpers.get(lb2).updateBalancingState(READY, mock(SubchannelPicker.class));
|
||||
verify(lb0).shutdown();
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[3]));
|
||||
LoadBalancer lb3 = balancers.get(lbPolicies[3]);
|
||||
verify(lb2, never()).shutdown();
|
||||
verify(lb3, never()).shutdown();
|
||||
|
||||
gracefulSwitchLb.shutdown();
|
||||
verify(lb2).shutdown();
|
||||
verify(lb3).shutdown();
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1, lb2, lb3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestConnectionForwardedToLatestPolicies() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
gracefulSwitchLb.requestConnection();
|
||||
verify(lb0).requestConnection();
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
gracefulSwitchLb.requestConnection();
|
||||
verify(lb1).requestConnection();
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2]));
|
||||
verify(lb1).shutdown();
|
||||
LoadBalancer lb2 = balancers.get(lbPolicies[2]);
|
||||
gracefulSwitchLb.requestConnection();
|
||||
verify(lb2).requestConnection();
|
||||
|
||||
// lb2 reports READY
|
||||
helpers.get(lb2).updateBalancingState(READY, mock(SubchannelPicker.class));
|
||||
verify(lb0).shutdown();
|
||||
|
||||
gracefulSwitchLb.requestConnection();
|
||||
verify(lb2, times(2)).requestConnection();
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[3]));
|
||||
LoadBalancer lb3 = balancers.get(lbPolicies[3]);
|
||||
gracefulSwitchLb.requestConnection();
|
||||
verify(lb3).requestConnection();
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1, lb2, lb3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSubchannelForwarded() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
CreateSubchannelArgs createSubchannelArgs = newFakeCreateSubchannelArgs();
|
||||
helper0.createSubchannel(createSubchannelArgs);
|
||||
verify(mockHelper).createSubchannel(createSubchannelArgs);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
Helper helper1 = helpers.get(lb1);
|
||||
createSubchannelArgs = newFakeCreateSubchannelArgs();
|
||||
helper1.createSubchannel(createSubchannelArgs);
|
||||
verify(mockHelper).createSubchannel(createSubchannelArgs);
|
||||
|
||||
createSubchannelArgs = newFakeCreateSubchannelArgs();
|
||||
helper0.createSubchannel(createSubchannelArgs);
|
||||
verify(mockHelper).createSubchannel(createSubchannelArgs);
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateBalancingStateIsGraceful() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
verify(mockHelper).updateBalancingState(READY, picker);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
Helper helper1 = helpers.get(lb1);
|
||||
picker = mock(SubchannelPicker.class);
|
||||
helper1.updateBalancingState(CONNECTING, picker);
|
||||
verify(mockHelper, never()).updateBalancingState(CONNECTING, picker);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2]));
|
||||
verify(lb1).shutdown();
|
||||
LoadBalancer lb2 = balancers.get(lbPolicies[2]);
|
||||
Helper helper2 = helpers.get(lb2);
|
||||
picker = mock(SubchannelPicker.class);
|
||||
helper2.updateBalancingState(CONNECTING, picker);
|
||||
verify(mockHelper, never()).updateBalancingState(CONNECTING, picker);
|
||||
|
||||
// lb2 reports READY
|
||||
SubchannelPicker picker2 = mock(SubchannelPicker.class);
|
||||
helper2.updateBalancingState(READY, picker2);
|
||||
verify(lb0).shutdown();
|
||||
verify(mockHelper).updateBalancingState(READY, picker2);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[3]));
|
||||
LoadBalancer lb3 = balancers.get(lbPolicies[3]);
|
||||
Helper helper3 = helpers.get(lb3);
|
||||
SubchannelPicker picker3 = mock(SubchannelPicker.class);
|
||||
helper3.updateBalancingState(CONNECTING, picker3);
|
||||
verify(mockHelper, never()).updateBalancingState(CONNECTING, picker3);
|
||||
|
||||
// lb2 out of READY
|
||||
picker2 = mock(SubchannelPicker.class);
|
||||
helper2.updateBalancingState(CONNECTING, picker2);
|
||||
verify(mockHelper, never()).updateBalancingState(CONNECTING, picker2);
|
||||
verify(mockHelper).updateBalancingState(CONNECTING, picker3);
|
||||
verify(lb2).shutdown();
|
||||
|
||||
picker3 = mock(SubchannelPicker.class);
|
||||
helper3.updateBalancingState(CONNECTING, picker3);
|
||||
verify(mockHelper).updateBalancingState(CONNECTING, picker3);
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1, lb2, lb3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchWhileOldPolicyIsNotReady() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(CONNECTING, picker);
|
||||
|
||||
verify(lb0, never()).shutdown();
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
verify(lb0).shutdown();
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
|
||||
Helper helper1 = helpers.get(lb1);
|
||||
picker = mock(SubchannelPicker.class);
|
||||
helper1.updateBalancingState(CONNECTING, picker);
|
||||
verify(mockHelper).updateBalancingState(CONNECTING, picker);
|
||||
|
||||
verify(lb1, never()).shutdown();
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2]));
|
||||
verify(lb1).shutdown();
|
||||
LoadBalancer lb2 = balancers.get(lbPolicies[2]);
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1, lb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchWhileOldPolicyGoesFromReadyToNotReady() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
verify(lb0, never()).shutdown();
|
||||
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
Helper helper1 = helpers.get(lb1);
|
||||
SubchannelPicker picker1 = mock(SubchannelPicker.class);
|
||||
helper1.updateBalancingState(CONNECTING, picker1);
|
||||
verify(mockHelper, never()).updateBalancingState(CONNECTING, picker1);
|
||||
|
||||
picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(CONNECTING, picker);
|
||||
verify(lb0).shutdown();
|
||||
verify(mockHelper, never()).updateBalancingState(CONNECTING, picker);
|
||||
verify(mockHelper).updateBalancingState(CONNECTING, picker1);
|
||||
|
||||
picker1 = mock(SubchannelPicker.class);
|
||||
helper1.updateBalancingState(READY, picker1);
|
||||
verify(mockHelper).updateBalancingState(READY, picker1);
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchWhileOldPolicyGoesFromReadyToNotReadyWhileNewPolicyStillIdle() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
InOrder inOrder = inOrder(lb0, mockHelper);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
verify(lb0, never()).shutdown();
|
||||
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
Helper helper1 = helpers.get(lb1);
|
||||
|
||||
picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(CONNECTING, picker);
|
||||
|
||||
verify(mockHelper, never()).updateBalancingState(CONNECTING, picker);
|
||||
inOrder.verify(mockHelper).updateBalancingState(CONNECTING, BUFFER_PICKER);
|
||||
inOrder.verify(lb0).shutdown(); // shutdown after update
|
||||
|
||||
picker = mock(SubchannelPicker.class);
|
||||
helper1.updateBalancingState(CONNECTING, picker);
|
||||
inOrder.verify(mockHelper).updateBalancingState(CONNECTING, picker);
|
||||
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
verifyNoMoreInteractions(lb1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newPolicyNameTheSameAsPendingPolicy_shouldHaveNoEffect() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
assertThat(balancers.get(lbPolicies[1])).isSameInstanceAs(lb1);
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newPolicyNameTheSameAsCurrentPolicy_shouldShutdownPendingLb() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
LoadBalancer lb0 = balancers.get(lbPolicies[0]);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
assertThat(balancers.get(lbPolicies[0])).isSameInstanceAs(lb0);
|
||||
|
||||
Helper helper0 = helpers.get(lb0);
|
||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||
helper0.updateBalancingState(READY, picker);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1]));
|
||||
LoadBalancer lb1 = balancers.get(lbPolicies[1]);
|
||||
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
verify(lb1).shutdown();
|
||||
assertThat(balancers.get(lbPolicies[0])).isSameInstanceAs(lb0);
|
||||
|
||||
verifyNoMoreInteractions(lb0, lb1);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Test
|
||||
public void handleSubchannelState_shouldThrow() {
|
||||
gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0]));
|
||||
Subchannel subchannel = mock(Subchannel.class);
|
||||
ConnectivityStateInfo connectivityStateInfo = ConnectivityStateInfo.forNonError(READY);
|
||||
thrown.expect(UnsupportedOperationException.class);
|
||||
gracefulSwitchLb.handleSubchannelState(subchannel, connectivityStateInfo);
|
||||
}
|
||||
|
||||
private final class FakeLoadBalancerProvider extends LoadBalancerProvider {
|
||||
|
||||
final String policyName;
|
||||
|
||||
FakeLoadBalancerProvider(String policyName) {
|
||||
this.policyName = policyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPolicyName() {
|
||||
return policyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
||||
LoadBalancer balancer = mock(LoadBalancer.class);
|
||||
balancers.put(policyName, balancer);
|
||||
helpers.put(balancer, helper);
|
||||
return balancer;
|
||||
}
|
||||
}
|
||||
|
||||
private static ResolvedAddresses newFakeAddresses() {
|
||||
return ResolvedAddresses
|
||||
.newBuilder()
|
||||
.setAddresses(
|
||||
Collections.singletonList(new EquivalentAddressGroup(mock(SocketAddress.class))))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static CreateSubchannelArgs newFakeCreateSubchannelArgs() {
|
||||
return CreateSubchannelArgs
|
||||
.newBuilder()
|
||||
.setAddresses(new EquivalentAddressGroup(mock(SocketAddress.class)))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue