diff --git a/util/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java b/util/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java index a07428a30b..ca2b940e20 100644 --- a/util/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java @@ -20,11 +20,18 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; import io.grpc.ConnectivityState; import io.grpc.ConnectivityStateInfo; import io.grpc.ExperimentalApi; import io.grpc.LoadBalancer; +import io.grpc.LoadBalancerRegistry; +import io.grpc.NameResolver.ConfigOrError; import io.grpc.Status; +import io.grpc.internal.ServiceConfigUtil; +import java.util.List; +import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; @@ -33,9 +40,16 @@ import javax.annotation.concurrent.NotThreadSafe; * 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. * - *

The balancer must {@link #switchTo(LoadBalancer.Factory) switch to} a policy prior to {@link + *

The child balancer and configuration is specified using service config. Config objects are + * generally created by calling {@link #parseLoadBalancingPolicyConfig(List)} from a + * {@link io.grpc.LoadBalancerProvider#parseLoadBalancingPolicyConfig + * provider's parseLoadBalancingPolicyConfig()} implementation. + * + *

Alternatively, the balancer may {@link #switchTo(LoadBalancer.Factory) switch to} a policy + * prior to {@link * LoadBalancer#handleResolvedAddresses(ResolvedAddresses) handling resolved addresses} for the - * first time. + * first time. This causes graceful switch to ignore the service config and pass through the + * resolved addresses directly to the child policy. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/5999") @NotThreadSafe // Must be accessed in SynchronizationContext @@ -90,6 +104,7 @@ public final class GracefulSwitchLoadBalancer extends ForwardingLoadBalancer { private LoadBalancer pendingLb = defaultBalancer; private ConnectivityState pendingState; private SubchannelPicker pendingPicker; + private boolean switchToCalled; private boolean currentLbIsReady; @@ -97,11 +112,43 @@ public final class GracefulSwitchLoadBalancer extends ForwardingLoadBalancer { this.helper = checkNotNull(helper, "helper"); } + @Override + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + if (switchToCalled) { + delegate().handleResolvedAddresses(resolvedAddresses); + return; + } + Config config = (Config) resolvedAddresses.getLoadBalancingPolicyConfig(); + switchToInternal(config.childFactory); + delegate().handleResolvedAddresses( + resolvedAddresses.toBuilder() + .setLoadBalancingPolicyConfig(config.childConfig) + .build()); + } + + @Override + public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { + if (switchToCalled) { + return delegate().acceptResolvedAddresses(resolvedAddresses); + } + Config config = (Config) resolvedAddresses.getLoadBalancingPolicyConfig(); + switchToInternal(config.childFactory); + return delegate().acceptResolvedAddresses( + resolvedAddresses.toBuilder() + .setLoadBalancingPolicyConfig(config.childConfig) + .build()); + } + /** * Gracefully switch to a new policy defined by the given factory, if the given factory isn't * equal to the current one. */ public void switchTo(LoadBalancer.Factory newBalancerFactory) { + switchToCalled = true; + switchToInternal(newBalancerFactory); + } + + private void switchToInternal(LoadBalancer.Factory newBalancerFactory) { checkNotNull(newBalancerFactory, "newBalancerFactory"); if (newBalancerFactory.equals(pendingBalancerFactory)) { @@ -185,4 +232,86 @@ public final class GracefulSwitchLoadBalancer extends ForwardingLoadBalancer { public String delegateType() { return delegate().getClass().getSimpleName(); } + + /** + * Provided a JSON list of LoadBalancingConfigs, parse it into a config to pass to GracefulSwitch. + */ + public static ConfigOrError parseLoadBalancingPolicyConfig( + List> loadBalancingConfigs) { + return parseLoadBalancingPolicyConfig( + loadBalancingConfigs, LoadBalancerRegistry.getDefaultRegistry()); + } + + /** + * Provided a JSON list of LoadBalancingConfigs, parse it into a config to pass to GracefulSwitch. + */ + public static ConfigOrError parseLoadBalancingPolicyConfig( + List> loadBalancingConfigs, LoadBalancerRegistry lbRegistry) { + List childConfigCandidates = + ServiceConfigUtil.unwrapLoadBalancingConfigList(loadBalancingConfigs); + if (childConfigCandidates == null || childConfigCandidates.isEmpty()) { + return ConfigOrError.fromError( + Status.INTERNAL.withDescription("No child LB config specified")); + } + ConfigOrError selectedConfig = + ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates, lbRegistry); + if (selectedConfig.getError() != null) { + Status error = selectedConfig.getError(); + return ConfigOrError.fromError( + Status.INTERNAL + .withCause(error.getCause()) + .withDescription(error.getDescription()) + .augmentDescription("Failed to select child config")); + } + ServiceConfigUtil.PolicySelection selection = + (ServiceConfigUtil.PolicySelection) selectedConfig.getConfig(); + return ConfigOrError.fromConfig( + createLoadBalancingPolicyConfig(selection.getProvider(), selection.getConfig())); + } + + /** + * Directly create a config to pass to GracefulSwitch. The object returned is the same as would be + * found in {@code ConfigOrError.getConfig()}. + */ + public static Object createLoadBalancingPolicyConfig( + LoadBalancer.Factory childFactory, @Nullable Object childConfig) { + return new Config(childFactory, childConfig); + } + + static final class Config { + final LoadBalancer.Factory childFactory; + @Nullable + final Object childConfig; + + public Config(LoadBalancer.Factory childFactory, @Nullable Object childConfig) { + this.childFactory = checkNotNull(childFactory, "childFactory"); + this.childConfig = childConfig; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Config)) { + return false; + } + Config that = (Config) o; + return Objects.equal(childFactory, that.childFactory) + && Objects.equal(childConfig, that.childConfig); + } + + @Override + public int hashCode() { + return Objects.hashCode(childFactory, childConfig); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper("GracefulSwitchLoadBalancer.Config") + .add("childFactory", childFactory) + .add("childConfig", childConfig) + .toString(); + } + } } diff --git a/util/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java b/util/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java index 6e89176e9c..d871999dad 100644 --- a/util/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java +++ b/util/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java @@ -21,15 +21,17 @@ import static io.grpc.ConnectivityState.CONNECTING; import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.util.GracefulSwitchLoadBalancer.BUFFER_PICKER; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -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 static org.mockito.Mockito.when; +import com.google.common.testing.EqualsTester; import io.grpc.ConnectivityStateInfo; import io.grpc.EquivalentAddressGroup; import io.grpc.LoadBalancer; @@ -41,14 +43,15 @@ import io.grpc.LoadBalancer.Subchannel; import io.grpc.LoadBalancer.SubchannelPicker; import io.grpc.LoadBalancerProvider; import io.grpc.LoadBalancerRegistry; +import io.grpc.NameResolver.ConfigOrError; import io.grpc.Status; import java.net.SocketAddress; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -62,62 +65,58 @@ import org.mockito.InOrder; */ @RunWith(JUnit4.class) public class GracefulSwitchLoadBalancerTest { + private static final Object FAKE_CONFIG = new Object(); + @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467 @Rule public final ExpectedException thrown = ExpectedException.none(); - private final LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry(); - // maps policy name to lb provide - private final Map lbProviders = new HashMap<>(); - // maps policy name to lb - private final Map balancers = new HashMap<>(); + private final Map balancers = new HashMap<>(); private final Map 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"}; + private final LoadBalancerProvider[] lbPolicies = { + new FakeLoadBalancerProvider("lb_policy_0"), + new FakeLoadBalancerProvider("lb_policy_1"), + new FakeLoadBalancerProvider("lb_policy_2"), + new FakeLoadBalancerProvider("lb_policy_3"), + }; - @Before - public void setUp() { - for (String lbPolicy : lbPolicies) { - LoadBalancerProvider lbProvider = new FakeLoadBalancerProvider(lbPolicy); - lbProviders.put(lbPolicy, lbProvider); - lbRegistry.register(lbProvider); - } - } + // OLD TESTS @Test - public void canHandleEmptyAddressListFromNameResolutionForwardedToLatestPolicy() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_canHandleEmptyAddressListFromNameResolutionForwardedToLatestPolicy() { + gracefulSwitchLb.switchTo(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(); + when(lb0.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1])); + gracefulSwitchLb.switchTo(lbPolicies[1]); LoadBalancer lb1 = balancers.get(lbPolicies[1]); assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse(); - doReturn(true).when(lb1).canHandleEmptyAddressListFromNameResolution(); + when(lb1.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2])); + gracefulSwitchLb.switchTo(lbPolicies[2]); LoadBalancer lb2 = balancers.get(lbPolicies[2]); assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse(); - doReturn(true).when(lb2).canHandleEmptyAddressListFromNameResolution(); + when(lb2.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue(); } @Test - public void handleResolvedAddressesAndNameResolutionErrorForwardedToLatestPolicy() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_handleResolvedAddressesAndNameResolutionErrorForwardedToLatestPolicy() { + gracefulSwitchLb.switchTo(lbPolicies[0]); LoadBalancer lb0 = balancers.get(lbPolicies[0]); Helper helper0 = helpers.get(lb0); SubchannelPicker picker = mock(SubchannelPicker.class); @@ -129,7 +128,7 @@ public class GracefulSwitchLoadBalancerTest { gracefulSwitchLb.handleNameResolutionError(Status.DATA_LOSS); verify(lb0).handleNameResolutionError(Status.DATA_LOSS); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1])); + gracefulSwitchLb.switchTo(lbPolicies[1]); LoadBalancer lb1 = balancers.get(lbPolicies[1]); addresses = newFakeAddresses(); gracefulSwitchLb.handleResolvedAddresses(addresses); @@ -139,7 +138,7 @@ public class GracefulSwitchLoadBalancerTest { verify(lb0, never()).handleNameResolutionError(Status.ALREADY_EXISTS); verify(lb1).handleNameResolutionError(Status.ALREADY_EXISTS); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2])); + gracefulSwitchLb.switchTo(lbPolicies[2]); verify(lb1).shutdown(); LoadBalancer lb2 = balancers.get(lbPolicies[2]); addresses = newFakeAddresses(); @@ -156,25 +155,65 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void shutdownTriggeredWhenSwitchAndForwardedWhenSwitchLbShutdown() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_acceptResolvedAddressesAndNameResolutionErrorForwardedToLatestPolicy() { + gracefulSwitchLb.switchTo(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])); + ResolvedAddresses addresses = newFakeAddresses(); + gracefulSwitchLb.acceptResolvedAddresses(addresses); + verify(lb0).acceptResolvedAddresses(addresses); + gracefulSwitchLb.handleNameResolutionError(Status.DATA_LOSS); + verify(lb0).handleNameResolutionError(Status.DATA_LOSS); + + gracefulSwitchLb.switchTo(lbPolicies[1]); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + addresses = newFakeAddresses(); + gracefulSwitchLb.acceptResolvedAddresses(addresses); + verify(lb0, never()).acceptResolvedAddresses(addresses); + verify(lb1).acceptResolvedAddresses(addresses); + gracefulSwitchLb.handleNameResolutionError(Status.ALREADY_EXISTS); + verify(lb0, never()).handleNameResolutionError(Status.ALREADY_EXISTS); + verify(lb1).handleNameResolutionError(Status.ALREADY_EXISTS); + + gracefulSwitchLb.switchTo(lbPolicies[2]); + verify(lb1).shutdown(); + LoadBalancer lb2 = balancers.get(lbPolicies[2]); + addresses = newFakeAddresses(); + gracefulSwitchLb.acceptResolvedAddresses(addresses); + verify(lb0, never()).acceptResolvedAddresses(addresses); + verify(lb1, never()).acceptResolvedAddresses(addresses); + verify(lb2).acceptResolvedAddresses(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 switchTo_shutdownTriggeredWhenSwitchAndForwardedWhenSwitchLbShutdown() { + gracefulSwitchLb.switchTo(lbPolicies[0]); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + + gracefulSwitchLb.switchTo(lbPolicies[1]); LoadBalancer lb1 = balancers.get(lbPolicies[1]); verify(lb1, never()).shutdown(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2])); + gracefulSwitchLb.switchTo(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])); + gracefulSwitchLb.switchTo(lbPolicies[3]); LoadBalancer lb3 = balancers.get(lbPolicies[3]); verify(lb2, never()).shutdown(); verify(lb3, never()).shutdown(); @@ -187,8 +226,8 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void requestConnectionForwardedToLatestPolicies() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_requestConnectionForwardedToLatestPolicies() { + gracefulSwitchLb.switchTo(lbPolicies[0]); LoadBalancer lb0 = balancers.get(lbPolicies[0]); Helper helper0 = helpers.get(lb0); SubchannelPicker picker = mock(SubchannelPicker.class); @@ -197,12 +236,12 @@ public class GracefulSwitchLoadBalancerTest { gracefulSwitchLb.requestConnection(); verify(lb0).requestConnection(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1])); + gracefulSwitchLb.switchTo(lbPolicies[1]); LoadBalancer lb1 = balancers.get(lbPolicies[1]); gracefulSwitchLb.requestConnection(); verify(lb1).requestConnection(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2])); + gracefulSwitchLb.switchTo(lbPolicies[2]); verify(lb1).shutdown(); LoadBalancer lb2 = balancers.get(lbPolicies[2]); gracefulSwitchLb.requestConnection(); @@ -215,7 +254,7 @@ public class GracefulSwitchLoadBalancerTest { gracefulSwitchLb.requestConnection(); verify(lb2, times(2)).requestConnection(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[3])); + gracefulSwitchLb.switchTo(lbPolicies[3]); LoadBalancer lb3 = balancers.get(lbPolicies[3]); gracefulSwitchLb.requestConnection(); verify(lb3).requestConnection(); @@ -224,8 +263,8 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void createSubchannelForwarded() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_createSubchannelForwarded() { + gracefulSwitchLb.switchTo(lbPolicies[0]); LoadBalancer lb0 = balancers.get(lbPolicies[0]); Helper helper0 = helpers.get(lb0); SubchannelPicker picker = mock(SubchannelPicker.class); @@ -235,7 +274,7 @@ public class GracefulSwitchLoadBalancerTest { helper0.createSubchannel(createSubchannelArgs); verify(mockHelper).createSubchannel(createSubchannelArgs); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1])); + gracefulSwitchLb.switchTo(lbPolicies[1]); LoadBalancer lb1 = balancers.get(lbPolicies[1]); Helper helper1 = helpers.get(lb1); createSubchannelArgs = newFakeCreateSubchannelArgs(); @@ -250,22 +289,22 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void updateBalancingStateIsGraceful() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_updateBalancingStateIsGraceful() { + gracefulSwitchLb.switchTo(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])); + gracefulSwitchLb.switchTo(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])); + gracefulSwitchLb.switchTo(lbPolicies[2]); verify(lb1).shutdown(); LoadBalancer lb2 = balancers.get(lbPolicies[2]); Helper helper2 = helpers.get(lb2); @@ -279,7 +318,7 @@ public class GracefulSwitchLoadBalancerTest { verify(lb0).shutdown(); verify(mockHelper).updateBalancingState(READY, picker2); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[3])); + gracefulSwitchLb.switchTo(lbPolicies[3]); LoadBalancer lb3 = balancers.get(lbPolicies[3]); Helper helper3 = helpers.get(lb3); SubchannelPicker picker3 = mock(SubchannelPicker.class); @@ -301,8 +340,8 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void switchWhileOldPolicyIsNotReady() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_switchWhileOldPolicyIsNotReady() { + gracefulSwitchLb.switchTo(lbPolicies[0]); LoadBalancer lb0 = balancers.get(lbPolicies[0]); Helper helper0 = helpers.get(lb0); SubchannelPicker picker = mock(SubchannelPicker.class); @@ -311,7 +350,7 @@ public class GracefulSwitchLoadBalancerTest { helper0.updateBalancingState(CONNECTING, picker); verify(lb0, never()).shutdown(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1])); + gracefulSwitchLb.switchTo(lbPolicies[1]); verify(lb0).shutdown(); LoadBalancer lb1 = balancers.get(lbPolicies[1]); @@ -321,7 +360,7 @@ public class GracefulSwitchLoadBalancerTest { verify(mockHelper).updateBalancingState(CONNECTING, picker); verify(lb1, never()).shutdown(); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[2])); + gracefulSwitchLb.switchTo(lbPolicies[2]); verify(lb1).shutdown(); LoadBalancer lb2 = balancers.get(lbPolicies[2]); @@ -329,14 +368,14 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void switchWhileOldPolicyGoesFromReadyToNotReady() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_switchWhileOldPolicyGoesFromReadyToNotReady() { + gracefulSwitchLb.switchTo(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])); + gracefulSwitchLb.switchTo(lbPolicies[1]); verify(lb0, never()).shutdown(); LoadBalancer lb1 = balancers.get(lbPolicies[1]); @@ -359,15 +398,15 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void switchWhileOldPolicyGoesFromReadyToNotReadyWhileNewPolicyStillIdle() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_switchWhileOldPolicyGoesFromReadyToNotReadyWhileNewPolicyStillIdle() { + gracefulSwitchLb.switchTo(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])); + gracefulSwitchLb.switchTo(lbPolicies[1]); verify(lb0, never()).shutdown(); LoadBalancer lb1 = balancers.get(lbPolicies[1]); @@ -389,38 +428,38 @@ public class GracefulSwitchLoadBalancerTest { } @Test - public void newPolicyNameTheSameAsPendingPolicy_shouldHaveNoEffect() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_newPolicyNameTheSameAsPendingPolicy_shouldHaveNoEffect() { + gracefulSwitchLb.switchTo(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])); + gracefulSwitchLb.switchTo(lbPolicies[1]); LoadBalancer lb1 = balancers.get(lbPolicies[1]); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[1])); + gracefulSwitchLb.switchTo(lbPolicies[1]); assertThat(balancers.get(lbPolicies[1])).isSameInstanceAs(lb1); verifyNoMoreInteractions(lb0, lb1); } @Test - public void newPolicyNameTheSameAsCurrentPolicy_shouldShutdownPendingLb() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + public void switchTo_newPolicyNameTheSameAsCurrentPolicy_shouldShutdownPendingLb() { + gracefulSwitchLb.switchTo(lbPolicies[0]); LoadBalancer lb0 = balancers.get(lbPolicies[0]); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + gracefulSwitchLb.switchTo(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])); + gracefulSwitchLb.switchTo(lbPolicies[1]); LoadBalancer lb1 = balancers.get(lbPolicies[1]); - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + gracefulSwitchLb.switchTo(lbPolicies[0]); verify(lb1).shutdown(); assertThat(balancers.get(lbPolicies[0])).isSameInstanceAs(lb0); @@ -429,7 +468,7 @@ public class GracefulSwitchLoadBalancerTest { @Test - public void newLbFactoryEqualToOldOneShouldHaveNoEffect() { + public void switchTo_newLbFactoryEqualToOldOneShouldHaveNoEffect() { final List balancers = new ArrayList<>(); final class LoadBalancerFactoryWithId extends LoadBalancer.Factory { @@ -476,6 +515,8 @@ public class GracefulSwitchLoadBalancerTest { verifyNoMoreInteractions(lb0, lb1); } + // END OF OLD TESTS + @Test public void transientFailureOnInitialResolutionError() { gracefulSwitchLb.handleNameResolutionError(Status.DATA_LOSS); @@ -489,13 +530,570 @@ public class GracefulSwitchLoadBalancerTest { @Deprecated @Test public void handleSubchannelState_shouldThrow() { - gracefulSwitchLb.switchTo(lbProviders.get(lbPolicies[0])); + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); Subchannel subchannel = mock(Subchannel.class); ConnectivityStateInfo connectivityStateInfo = ConnectivityStateInfo.forNonError(READY); thrown.expect(UnsupportedOperationException.class); gracefulSwitchLb.handleSubchannelState(subchannel, connectivityStateInfo); } + @Test + public void canHandleEmptyAddressListFromNameResolutionForwardedToLatestPolicy() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + + assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse(); + when(lb0.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); + assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue(); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + + assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse(); + + when(lb1.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); + assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue(); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[2], new Object())) + .build())); + LoadBalancer lb2 = balancers.get(lbPolicies[2]); + + assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isFalse(); + + when(lb2.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); + assertThat(gracefulSwitchLb.canHandleEmptyAddressListFromNameResolution()).isTrue(); + } + + @Test + public void handleResolvedAddressesAndNameResolutionErrorForwardedToLatestPolicy() { + ResolvedAddresses addresses = newFakeAddresses(); + Object child0Config = new Object(); + gracefulSwitchLb.handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], child0Config)) + .build()); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + verify(lb0).handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child0Config) + .build()); + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + gracefulSwitchLb.handleNameResolutionError(Status.DATA_LOSS); + verify(lb0).handleNameResolutionError(Status.DATA_LOSS); + + Object child1Config = new Object(); + addresses = newFakeAddresses(); + gracefulSwitchLb.handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], child1Config)) + .build()); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + verify(lb0, never()).handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child1Config) + .build()); + verify(lb1).handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child1Config) + .build()); + gracefulSwitchLb.handleNameResolutionError(Status.ALREADY_EXISTS); + verify(lb0, never()).handleNameResolutionError(Status.ALREADY_EXISTS); + verify(lb1).handleNameResolutionError(Status.ALREADY_EXISTS); + + Object child2Config = new Object(); + addresses = newFakeAddresses(); + gracefulSwitchLb.handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[2], child2Config)) + .build()); + verify(lb1).shutdown(); + LoadBalancer lb2 = balancers.get(lbPolicies[2]); + verify(lb0, never()).handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child2Config) + .build()); + verify(lb1, never()).handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child2Config) + .build()); + verify(lb2).handleResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child2Config) + .build()); + 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 acceptResolvedAddressesAndNameResolutionErrorForwardedToLatestPolicy() { + ResolvedAddresses addresses = newFakeAddresses(); + Object child0Config = new Object(); + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], child0Config)) + .build())); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + verify(lb0).acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child0Config) + .build()); + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + gracefulSwitchLb.handleNameResolutionError(Status.DATA_LOSS); + verify(lb0).handleNameResolutionError(Status.DATA_LOSS); + + Object child1Config = new Object(); + addresses = newFakeAddresses(); + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], child1Config)) + .build())); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + verify(lb0, never()).acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child1Config) + .build()); + verify(lb1).acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child1Config) + .build()); + gracefulSwitchLb.handleNameResolutionError(Status.ALREADY_EXISTS); + verify(lb0, never()).handleNameResolutionError(Status.ALREADY_EXISTS); + verify(lb1).handleNameResolutionError(Status.ALREADY_EXISTS); + + Object child2Config = new Object(); + addresses = newFakeAddresses(); + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[2], child2Config)) + .build())); + verify(lb1).shutdown(); + LoadBalancer lb2 = balancers.get(lbPolicies[2]); + verify(lb0, never()).acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child2Config) + .build()); + verify(lb1, never()).acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child2Config) + .build()); + verify(lb2).acceptResolvedAddresses(addresses.toBuilder() + .setLoadBalancingPolicyConfig(child2Config) + .build()); + 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() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + verify(lb1, never()).shutdown(); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[2], new Object())) + .build())); + verify(lb1).shutdown(); + LoadBalancer lb2 = balancers.get(lbPolicies[2]); + verify(lb0, never()).shutdown(); + helpers.get(lb2).updateBalancingState(READY, mock(SubchannelPicker.class)); + verify(lb0).shutdown(); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[3], new Object())) + .build())); + LoadBalancer lb3 = balancers.get(lbPolicies[3]); + verify(lb2, never()).shutdown(); + verify(lb3, never()).shutdown(); + + gracefulSwitchLb.shutdown(); + verify(lb2).shutdown(); + verify(lb3).shutdown(); + } + + @Test + public void requestConnectionForwardedToLatestPolicies() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + 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(); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + gracefulSwitchLb.requestConnection(); + verify(lb1).requestConnection(); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[2], new Object())) + .build())); + 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(); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[3], new Object())) + .build())); + LoadBalancer lb3 = balancers.get(lbPolicies[3]); + gracefulSwitchLb.requestConnection(); + verify(lb3).requestConnection(); + } + + @Test + public void createSubchannelForwarded() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + 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); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + 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); + } + + @Test + public void updateBalancingStateIsGraceful() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + 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); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + 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); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[2], new Object())) + .build())); + 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); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[3], new Object())) + .build())); + 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); + } + + @Test + public void switchWhileOldPolicyIsNotReady() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + 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(); + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + 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(); + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[2], new Object())) + .build())); + verify(lb1).shutdown(); + } + + @Test + public void switchWhileOldPolicyGoesFromReadyToNotReady() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + 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); + } + + @Test + public void switchWhileOldPolicyGoesFromReadyToNotReadyWhileNewPolicyStillIdle() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + 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); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + 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); + } + + @Test + public void newPolicyNameTheSameAsPendingPolicy_shouldHaveNoEffect() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + assertThat(balancers.get(lbPolicies[1])).isSameInstanceAs(lb1); + } + + @Test + public void newPolicyNameTheSameAsCurrentPolicy_shouldShutdownPendingLb() { + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + LoadBalancer lb0 = balancers.get(lbPolicies[0]); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + assertThat(balancers.get(lbPolicies[0])).isSameInstanceAs(lb0); + + Helper helper0 = helpers.get(lb0); + SubchannelPicker picker = mock(SubchannelPicker.class); + helper0.updateBalancingState(READY, picker); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[1], new Object())) + .build())); + LoadBalancer lb1 = balancers.get(lbPolicies[1]); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(lbPolicies[0], new Object())) + .build())); + verify(lb1).shutdown(); + assertThat(balancers.get(lbPolicies[0])).isSameInstanceAs(lb0); + } + + + @Test + public void newLbFactoryEqualToOldOneShouldHaveNoEffect() { + final List balancers = new ArrayList<>(); + + final class LoadBalancerFactoryWithId extends LoadBalancer.Factory { + final int id; + + LoadBalancerFactoryWithId(int id) { + this.id = id; + } + + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + LoadBalancer balancer = mock(LoadBalancer.class); + when(balancer.acceptResolvedAddresses(any())).thenReturn(Status.OK); + balancers.add(balancer); + return balancer; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof LoadBalancerFactoryWithId)) { + return false; + } + LoadBalancerFactoryWithId that = (LoadBalancerFactoryWithId) o; + return id == that.id; + } + + @Override + public int hashCode() { + return id; + } + } + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(new LoadBalancerFactoryWithId(0), new Object())) + .build())); + assertThat(balancers).hasSize(1); + LoadBalancer lb0 = balancers.get(0); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(new LoadBalancerFactoryWithId(0), new Object())) + .build())); + assertThat(balancers).hasSize(1); + + assertIsOk(gracefulSwitchLb.acceptResolvedAddresses(addressesBuilder() + .setLoadBalancingPolicyConfig(createConfig(new LoadBalancerFactoryWithId(1), new Object())) + .build())); + assertThat(balancers).hasSize(2); + verify(lb0).shutdown(); + } + + @Test + public void configEquals() { + Object config = new Object(); + new EqualsTester() + .addEqualityGroup(createConfig(lbPolicies[0], config), createConfig(lbPolicies[0], config)) + .addEqualityGroup(createConfig(lbPolicies[1], config)) + .addEqualityGroup(createConfig(lbPolicies[0], new Object())) + .testEquals(); + } + + @Test + public void parseLoadBalancingPolicyConfig_null_fails() { + ConfigOrError result = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig(null); + assertThat(result.getError()).isNotNull(); + } + + @Test + public void parseLoadBalancingPolicyConfig_empty_fails() { + ConfigOrError result = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig( + Arrays.asList()); + assertThat(result.getError()).isNotNull(); + } + + @Test + public void parseLoadBalancingPolicyConfig_missing_fails() { + LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry(); + ConfigOrError result = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig( + Arrays.asList(Collections.singletonMap("lb_policy_0", Collections.emptyMap())), lbRegistry); + assertThat(result.getError()).isNotNull(); + } + + @Test + public void parseLoadBalancingPolicyConfig_succeeds() { + LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry(); + lbRegistry.register(lbPolicies[0]); + ConfigOrError result = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig( + Arrays.asList(Collections.singletonMap("lb_policy_0", Collections.emptyMap())), lbRegistry); + assertThat(result.getError()).isNull(); + assertThat(result.getConfig()).isInstanceOf(GracefulSwitchLoadBalancer.Config.class); + GracefulSwitchLoadBalancer.Config config = + (GracefulSwitchLoadBalancer.Config) result.getConfig(); + assertThat(config.childFactory).isEqualTo(lbPolicies[0]); + assertThat(config.childConfig).isEqualTo(FAKE_CONFIG); + } + private final class FakeLoadBalancerProvider extends LoadBalancerProvider { final String policyName; @@ -522,10 +1120,31 @@ public class GracefulSwitchLoadBalancerTest { @Override public LoadBalancer newLoadBalancer(Helper helper) { LoadBalancer balancer = mock(LoadBalancer.class); - balancers.put(policyName, balancer); + when(balancer.acceptResolvedAddresses(any())).thenReturn(Status.OK); + balancers.put(this, balancer); helpers.put(balancer, helper); return balancer; } + + @Override + public ConfigOrError parseLoadBalancingPolicyConfig(Map rawConfig) { + return ConfigOrError.fromConfig(FAKE_CONFIG); + } + } + + private static void assertIsOk(Status status) { + assertThat(status.isOk()).isTrue(); + } + + private ResolvedAddresses.Builder addressesBuilder() { + return ResolvedAddresses.newBuilder() + .setAddresses( + Collections.singletonList(new EquivalentAddressGroup(mock(SocketAddress.class)))); + } + + private static Object createConfig( + LoadBalancer.Factory childFactory, Object childConfig) { + return GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(childFactory, childConfig); } private static ResolvedAddresses newFakeAddresses() { diff --git a/util/src/testFixtures/java/io/grpc/util/GracefulSwitchLoadBalancerAccessor.java b/util/src/testFixtures/java/io/grpc/util/GracefulSwitchLoadBalancerAccessor.java new file mode 100644 index 0000000000..8f62e66be4 --- /dev/null +++ b/util/src/testFixtures/java/io/grpc/util/GracefulSwitchLoadBalancerAccessor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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 io.grpc.LoadBalancerProvider; + +/** + * Accessors for white-box testing involving GracefulSwitchLoadBalancer. + */ +public final class GracefulSwitchLoadBalancerAccessor { + private GracefulSwitchLoadBalancerAccessor() { + // Do not instantiate + } + + public static LoadBalancerProvider getChildProvider(Object config) { + return (LoadBalancerProvider) ((GracefulSwitchLoadBalancer.Config) config).childFactory; + } + + public static Object getChildConfig(Object config) { + return ((GracefulSwitchLoadBalancer.Config) config).childConfig; + } +}