From 751faa6faa3fbccf46af74c6597218438f39c2ca Mon Sep 17 00:00:00 2001 From: Jihun Cho Date: Thu, 23 Jan 2020 17:59:27 -0800 Subject: [PATCH] core: promote ServiceConfigErrorHandling (#6633) --- .../AbstractManagedChannelImplBuilder.java | 30 - .../AutoConfiguredLoadBalancerFactory.java | 215 +- .../AutoConfiguredLoadBalancerFactory2.java | 447 -- .../io/grpc/internal/ManagedChannelImpl.java | 225 +- .../io/grpc/internal/ManagedChannelImpl2.java | 2007 -------- .../internal/ManagedChannelServiceConfig.java | 47 +- .../ManagedChannelServiceConfig2.java | 322 -- .../internal/ServiceConfigInterceptor.java | 26 +- .../internal/ServiceConfigInterceptor2.java | 199 - ...AbstractManagedChannelImplBuilderTest.java | 14 - ...AutoConfiguredLoadBalancerFactoryTest.java | 467 +- ...utoConfiguredLoadBalancerFactoryTest2.java | 981 ---- .../io/grpc/internal/HedgingPolicyTest.java | 30 +- .../ManagedChannelImplIdlenessTest.java | 11 +- .../ManagedChannelImplIdlenessTest2.java | 560 --- .../grpc/internal/ManagedChannelImplTest.java | 257 +- .../internal/ManagedChannelImplTest2.java | 4263 ----------------- .../io/grpc/internal/RetryPolicyTest.java | 30 +- .../ServiceConfigErrorHandlingTest.java | 8 +- .../ServiceConfigInterceptorTest.java | 105 +- .../ServiceConfigInterceptorTest2.java | 493 -- 21 files changed, 899 insertions(+), 9838 deletions(-) delete mode 100644 core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory2.java delete mode 100644 core/src/main/java/io/grpc/internal/ManagedChannelImpl2.java delete mode 100644 core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig2.java delete mode 100644 core/src/main/java/io/grpc/internal/ServiceConfigInterceptor2.java delete mode 100644 core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest2.java delete mode 100644 core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest2.java delete mode 100644 core/src/test/java/io/grpc/internal/ManagedChannelImplTest2.java delete mode 100644 core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest2.java diff --git a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java index 507b5d185f..6bc54d62d0 100644 --- a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java @@ -99,13 +99,6 @@ public abstract class AbstractManagedChannelImplBuilder private static final long DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES = 1L << 24; // 16M private static final long DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES = 1L << 20; // 1M - @VisibleForTesting - static final String ENABLE_SERVICE_CONFIG_ERROR_HANDLING_PROPERTY = - "io.grpc.internal.ManagedChannelImpl.enableServiceConfigErrorHandling"; - private static final boolean DEFAULT_ENABLE_SERVICE_CONFIG_ERROR_HANDLING = - Boolean.parseBoolean( - System.getProperty(ENABLE_SERVICE_CONFIG_ERROR_HANDLING_PROPERTY, "false")); - ObjectPool executorPool = DEFAULT_EXECUTOR_POOL; ObjectPool offloadExecutorPool = DEFAULT_EXECUTOR_POOL; @@ -165,8 +158,6 @@ public abstract class AbstractManagedChannelImplBuilder @Nullable ProxyDetector proxyDetector; - boolean enableServiceConfigErrorHandling = DEFAULT_ENABLE_SERVICE_CONFIG_ERROR_HANDLING; - /** * Sets the maximum message size allowed for a single gRPC frame. If an inbound messages * larger than this limit is received it will not be processed and the RPC will fail with @@ -457,16 +448,6 @@ public abstract class AbstractManagedChannelImplBuilder return thisT(); } - /** - * Enables service config error handling implemented in {@link ManagedChannelImpl2}. By default, - * it is disabled unless system property {@link #ENABLE_SERVICE_CONFIG_ERROR_HANDLING_PROPERTY} is - * set to {@code "true"}. - */ - protected T enableServiceConfigErrorHandling() { - this.enableServiceConfigErrorHandling = true; - return thisT(); - } - /** * Disable or enable stats features. Enabled by default. * @@ -527,17 +508,6 @@ public abstract class AbstractManagedChannelImplBuilder @Override public ManagedChannel build() { - if (this.enableServiceConfigErrorHandling) { - return new ManagedChannelOrphanWrapper(new ManagedChannelImpl2( - this, - buildTransportFactory(), - // TODO(carl-mastrangelo): Allow clients to pass this in - new ExponentialBackoffPolicy.Provider(), - SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR), - GrpcUtil.STOPWATCH_SUPPLIER, - getEffectiveInterceptors(), - TimeProvider.SYSTEM_TIME_PROVIDER)); - } return new ManagedChannelOrphanWrapper(new ManagedChannelImpl( this, buildTransportFactory(), diff --git a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java index ac926693f8..263a20d4a8 100644 --- a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java +++ b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java @@ -20,7 +20,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.LoadBalancer.ATTR_LOAD_BALANCING_CONFIG; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; import io.grpc.Attributes; +import io.grpc.ChannelLogger; import io.grpc.ChannelLogger.ChannelLogLevel; import io.grpc.ConnectivityState; import io.grpc.ConnectivityStateInfo; @@ -39,13 +42,13 @@ import io.grpc.Status; import io.grpc.internal.ServiceConfigUtil.LbConfig; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.annotation.Nullable; -@SuppressWarnings("deprecation") // migrate to AutoConfiguredLoadBalancerFactory2 is required +// TODO(creamsoup) fully deprecate LoadBalancer.ATTR_LOAD_BALANCING_CONFIG +@SuppressWarnings("deprecation") public final class AutoConfiguredLoadBalancerFactory { private static final Logger logger = Logger.getLogger(AutoConfiguredLoadBalancerFactory.class.getName()); @@ -107,8 +110,10 @@ public final class AutoConfiguredLoadBalancerFactory { } /** - * Returns non-OK status if resolvedAddresses is rejected and should be considered as a - * name-resolution error. + * Returns non-OK status if resolvedAddresses is empty and delegate lb requires address ({@link + * LoadBalancer#canHandleEmptyAddressListFromNameResolution()} returns {@code false}). {@code + * AutoConfiguredLoadBalancer} doesn't expose {@code + * canHandleEmptyAddressListFromNameResolution} because it depends on the delegated LB. */ Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) { List servers = resolvedAddresses.getAddresses(); @@ -116,12 +121,14 @@ public final class AutoConfiguredLoadBalancerFactory { if (attributes.get(ATTR_LOAD_BALANCING_CONFIG) != null) { throw new IllegalArgumentException( "Unexpected ATTR_LOAD_BALANCING_CONFIG from upstream: " - + attributes.get(ATTR_LOAD_BALANCING_CONFIG)); + + attributes.get(ATTR_LOAD_BALANCING_CONFIG)); } - Map configMap = attributes.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG); - PolicySelection selection; + PolicySelection policySelection = + (PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig(); + ResolvedPolicySelection resolvedSelection; + try { - selection = decideLoadBalancerProvider(servers, configMap); + resolvedSelection = resolveLoadBalancerProvider(servers, policySelection); } catch (PolicyException e) { Status s = Status.INTERNAL.withDescription(e.getMessage()); helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s)); @@ -130,6 +137,7 @@ public final class AutoConfiguredLoadBalancerFactory { delegate = new NoopLoadBalancer(); return Status.OK; } + PolicySelection selection = resolvedSelection.policySelection; if (delegateProvider == null || !selection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) { @@ -142,24 +150,25 @@ public final class AutoConfiguredLoadBalancerFactory { ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}", old.getClass().getSimpleName(), delegate.getClass().getSimpleName()); } - - if (selection.config != null) { + Object lbConfig = selection.config; + if (lbConfig != null) { helper.getChannelLogger().log( ChannelLogLevel.DEBUG, "Load-balancing config: {0}", selection.config); attributes = - attributes.toBuilder().set(ATTR_LOAD_BALANCING_CONFIG, selection.config).build(); + attributes.toBuilder().set(ATTR_LOAD_BALANCING_CONFIG, selection.rawConfig).build(); } LoadBalancer delegate = getDelegate(); - if (selection.serverList.isEmpty() + if (resolvedSelection.serverList.isEmpty() && !delegate.canHandleEmptyAddressListFromNameResolution()) { return Status.UNAVAILABLE.withDescription( "NameResolver returned no usable address. addrs=" + servers + ", attrs=" + attributes); } else { delegate.handleResolvedAddresses( ResolvedAddresses.newBuilder() - .setAddresses(selection.serverList) + .setAddresses(resolvedSelection.serverList) .setAttributes(attributes) + .setLoadBalancingPolicyConfig(lbConfig) .build()); return Status.OK; } @@ -199,24 +208,17 @@ public final class AutoConfiguredLoadBalancerFactory { } /** - * Picks a load balancer based on given criteria. In order of preference: - * - *
    - *
  1. User provided lb on the channel. This is a degenerate case and not handled here. - * This options is deprecated.
  2. - *
  3. Policy from "loadBalancingConfig" if present. This is not covered here.
  4. - *
  5. "grpclb" if any gRPC LB balancer addresses are present
  6. - *
  7. The policy from "loadBalancingPolicy" if present
  8. - *
  9. "pick_first" if the service config choice does not specify
  10. - *
+ * Resolves a load balancer based on given criteria. If policySelection is {@code null} and + * given servers contains any gRPC LB addresses, it will fall back to "grpclb". If no gRPC LB + * addresses are not present, it will fall back to {@link #defaultPolicy}. * * @param servers The list of servers reported - * @param config the service config object - * @return the new load balancer factory, never null + * @param policySelection the selected policy from raw service config + * @return the resolved policy selection */ @VisibleForTesting - PolicySelection decideLoadBalancerProvider( - List servers, @Nullable Map config) + ResolvedPolicySelection resolveLoadBalancerProvider( + List servers, @Nullable PolicySelection policySelection) throws PolicyException { // Check for balancer addresses boolean haveBalancerAddress = false; @@ -229,36 +231,10 @@ public final class AutoConfiguredLoadBalancerFactory { } } - List lbConfigs = null; - if (config != null) { - List> rawLbConfigs = - ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(config); - lbConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(rawLbConfigs); - } - if (lbConfigs != null && !lbConfigs.isEmpty()) { - LinkedHashSet policiesTried = new LinkedHashSet<>(); - for (LbConfig lbConfig : lbConfigs) { - String policy = lbConfig.getPolicyName(); - LoadBalancerProvider provider = registry.getProvider(policy); - if (provider == null) { - policiesTried.add(policy); - } else { - if (!policiesTried.isEmpty()) { - // Before returning, log all previously tried policies - helper.getChannelLogger().log( - ChannelLogLevel.DEBUG, - "{0} specified by Service Config are not available", policiesTried); - } - return new PolicySelection( - provider, - policy.equals(GRPCLB_POLICY_NAME) ? servers : backendAddrs, - lbConfig.getRawConfigValue()); - } - } - if (!haveBalancerAddress) { - throw new PolicyException( - "None of " + policiesTried + " specified by Service Config are available."); - } + if (policySelection != null) { + String policyName = policySelection.provider.getPolicyName(); + return new ResolvedPolicySelection( + policySelection, policyName.equals(GRPCLB_POLICY_NAME) ? servers : backendAddrs); } if (haveBalancerAddress) { @@ -278,20 +254,29 @@ public final class AutoConfiguredLoadBalancerFactory { helper.getChannelLogger().log(ChannelLogLevel.ERROR, errorMsg); logger.warning(errorMsg); } - return new PolicySelection( - getProviderOrThrow( - "round_robin", "received balancer addresses but grpclb runtime is missing"), - backendAddrs, null); + return new ResolvedPolicySelection( + new PolicySelection( + getProviderOrThrow( + "round_robin", "received balancer addresses but grpclb runtime is missing"), + /* rawConfig = */ null, + /* config= */ null), + backendAddrs); } - return new PolicySelection(grpclbProvider, servers, null); + return new ResolvedPolicySelection( + new PolicySelection( + grpclbProvider, /* rawConfig= */ null, /* config= */ null), servers); } // No balancer address this time. If balancer address shows up later, we want to make sure // the warning is logged one more time. roundRobinDueToGrpclbDepMissing = false; // No config nor balancer address. Use default. - return new PolicySelection( - getProviderOrThrow(defaultPolicy, "using default policy"), servers, null); + return new ResolvedPolicySelection( + new PolicySelection( + getProviderOrThrow(defaultPolicy, "using default policy"), + /* rawConfig= */ null, + /* config= */ null), + servers); } } @@ -306,13 +291,27 @@ public final class AutoConfiguredLoadBalancerFactory { } /** - * Unlike a normal {@link LoadBalancer.Factory}, this accepts a full service config rather than + * Parses first available LoadBalancer policy from service config. Available LoadBalancer should + * be registered to {@link LoadBalancerRegistry}. If the first available LoadBalancer policy is + * invalid, it doesn't fall-back to next available policy, instead it returns error. This also + * means, it ignores LoadBalancer policies after the first available one even if any of them are + * invalid. + * + *

Order of policy preference: + * + *

    + *
  1. Policy from "loadBalancingConfig" if present
  2. + *
  3. The policy from deprecated "loadBalancingPolicy" if present
  4. + *
+ *

+ * + *

Unlike a normal {@link LoadBalancer.Factory}, this accepts a full service config rather than * the LoadBalancingConfig. * - * @return null if no selection could be made. + * @return the parsed {@link PolicySelection}, or {@code null} if no selection could be made. */ @Nullable - ConfigOrError selectLoadBalancerPolicy(Map serviceConfig) { + ConfigOrError parseLoadBalancerPolicy(Map serviceConfig, ChannelLogger channelLogger) { try { List loadBalancerConfigs = null; if (serviceConfig != null) { @@ -328,10 +327,18 @@ public final class AutoConfiguredLoadBalancerFactory { if (provider == null) { policiesTried.add(policy); } else { - return ConfigOrError.fromConfig(new PolicySelection( - provider, - /* serverList= */ null, - lbConfig.getRawConfigValue())); + if (!policiesTried.isEmpty()) { + channelLogger.log( + ChannelLogLevel.DEBUG, + "{0} specified by Service Config are not available", policiesTried); + } + ConfigOrError parsedLbPolicyConfig = + provider.parseLoadBalancingPolicyConfig(lbConfig.getRawConfigValue()); + if (parsedLbPolicyConfig.getError() != null) { + return parsedLbPolicyConfig; + } + return ConfigOrError.fromConfig( + new PolicySelection(provider, serviceConfig, parsedLbPolicyConfig.getConfig())); } } return ConfigOrError.fromError( @@ -357,25 +364,65 @@ public final class AutoConfiguredLoadBalancerFactory { @VisibleForTesting static final class PolicySelection { final LoadBalancerProvider provider; - @Nullable final List serverList; - // TODO(carl-mastrangelo): make this the non-raw service config object. - @Nullable final Map config; - - PolicySelection( - LoadBalancerProvider provider, List serverList, - @Nullable Map config) { - this.provider = checkNotNull(provider, "provider"); - this.serverList = Collections.unmodifiableList(checkNotNull(serverList, "serverList")); - this.config = config; - } + @Nullable final Map rawConfig; + @Nullable final Object config; PolicySelection( LoadBalancerProvider provider, - @Nullable Map config) { + @Nullable Map rawConfig, + @Nullable Object config) { this.provider = checkNotNull(provider, "provider"); - this.serverList = null; + this.rawConfig = rawConfig; this.config = config; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PolicySelection that = (PolicySelection) o; + return Objects.equal(provider, that.provider) + && Objects.equal(rawConfig, that.rawConfig) + && Objects.equal(config, that.config); + } + + @Override + public int hashCode() { + return Objects.hashCode(provider, rawConfig, config); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("provider", provider) + .add("rawConfig", rawConfig) + .add("config", config) + .toString(); + } + } + + @VisibleForTesting + static final class ResolvedPolicySelection { + final PolicySelection policySelection; + final List serverList; + + ResolvedPolicySelection( + PolicySelection policySelection, List serverList) { + this.policySelection = checkNotNull(policySelection, "policySelection"); + this.serverList = Collections.unmodifiableList(checkNotNull(serverList, "serverList")); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("policySelection", policySelection) + .add("serverList", serverList) + .toString(); + } } private static final class EmptyPicker extends SubchannelPicker { diff --git a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory2.java b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory2.java deleted file mode 100644 index d463293659..0000000000 --- a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory2.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright 2018 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.internal; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.grpc.LoadBalancer.ATTR_LOAD_BALANCING_CONFIG; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import io.grpc.Attributes; -import io.grpc.ChannelLogger; -import io.grpc.ChannelLogger.ChannelLogLevel; -import io.grpc.ConnectivityState; -import io.grpc.ConnectivityStateInfo; -import io.grpc.EquivalentAddressGroup; -import io.grpc.LoadBalancer; -import io.grpc.LoadBalancer.Helper; -import io.grpc.LoadBalancer.PickResult; -import io.grpc.LoadBalancer.PickSubchannelArgs; -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.NameResolver.ConfigOrError; -import io.grpc.Status; -import io.grpc.internal.ServiceConfigUtil.LbConfig; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; -import javax.annotation.Nullable; - -@SuppressWarnings("deprecation") // after migrated to 2, we can deprecate it -public final class AutoConfiguredLoadBalancerFactory2 { - private static final Logger logger = - Logger.getLogger(AutoConfiguredLoadBalancerFactory2.class.getName()); - private static final String GRPCLB_POLICY_NAME = "grpclb"; - - private final LoadBalancerRegistry registry; - private final String defaultPolicy; - - public AutoConfiguredLoadBalancerFactory2(String defaultPolicy) { - this(LoadBalancerRegistry.getDefaultRegistry(), defaultPolicy); - } - - @VisibleForTesting - AutoConfiguredLoadBalancerFactory2(LoadBalancerRegistry registry, String defaultPolicy) { - this.registry = checkNotNull(registry, "registry"); - this.defaultPolicy = checkNotNull(defaultPolicy, "defaultPolicy"); - } - - public AutoConfiguredLoadBalancer newLoadBalancer(Helper helper) { - return new AutoConfiguredLoadBalancer(helper); - } - - private static final class NoopLoadBalancer extends LoadBalancer { - - @Override - @Deprecated - public void handleResolvedAddressGroups(List s, Attributes a) {} - - @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {} - - @Override - public void handleNameResolutionError(Status error) {} - - @Override - public void shutdown() {} - } - - @VisibleForTesting - public final class AutoConfiguredLoadBalancer { - private final Helper helper; - private LoadBalancer delegate; - private LoadBalancerProvider delegateProvider; - private boolean roundRobinDueToGrpclbDepMissing; - - AutoConfiguredLoadBalancer(Helper helper) { - this.helper = helper; - delegateProvider = registry.getProvider(defaultPolicy); - if (delegateProvider == null) { - throw new IllegalStateException("Could not find policy '" + defaultPolicy - + "'. Make sure its implementation is either registered to LoadBalancerRegistry or" - + " included in META-INF/services/io.grpc.LoadBalancerProvider from your jar files."); - } - delegate = delegateProvider.newLoadBalancer(helper); - } - - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - tryHandleResolvedAddresses(resolvedAddresses); - } - - /** - * Returns non-OK status if resolvedAddresses is empty and delegate lb requires address ({@link - * LoadBalancer#canHandleEmptyAddressListFromNameResolution()} returns {@code false}). {@code - * AutoConfiguredLoadBalancer} doesn't expose {@code - * canHandleEmptyAddressListFromNameResolution} because it depends on the delegated LB. - */ - Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - List servers = resolvedAddresses.getAddresses(); - Attributes attributes = resolvedAddresses.getAttributes(); - if (attributes.get(ATTR_LOAD_BALANCING_CONFIG) != null) { - throw new IllegalArgumentException( - "Unexpected ATTR_LOAD_BALANCING_CONFIG from upstream: " - + attributes.get(ATTR_LOAD_BALANCING_CONFIG)); - } - PolicySelection policySelection = - (PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig(); - ResolvedPolicySelection resolvedSelection; - - try { - resolvedSelection = resolveLoadBalancerProvider(servers, policySelection); - } catch (PolicyException e) { - Status s = Status.INTERNAL.withDescription(e.getMessage()); - helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s)); - delegate.shutdown(); - delegateProvider = null; - delegate = new NoopLoadBalancer(); - return Status.OK; - } - PolicySelection selection = resolvedSelection.policySelection; - - if (delegateProvider == null - || !selection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) { - helper.updateBalancingState(ConnectivityState.CONNECTING, new EmptyPicker()); - delegate.shutdown(); - delegateProvider = selection.provider; - LoadBalancer old = delegate; - delegate = delegateProvider.newLoadBalancer(helper); - helper.getChannelLogger().log( - ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}", - old.getClass().getSimpleName(), delegate.getClass().getSimpleName()); - } - Object lbConfig = selection.config; - if (lbConfig != null) { - helper.getChannelLogger().log( - ChannelLogLevel.DEBUG, "Load-balancing config: {0}", selection.config); - attributes = - attributes.toBuilder().set(ATTR_LOAD_BALANCING_CONFIG, selection.rawConfig).build(); - } - - LoadBalancer delegate = getDelegate(); - if (resolvedSelection.serverList.isEmpty() - && !delegate.canHandleEmptyAddressListFromNameResolution()) { - return Status.UNAVAILABLE.withDescription( - "NameResolver returned no usable address. addrs=" + servers + ", attrs=" + attributes); - } else { - delegate.handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(resolvedSelection.serverList) - .setAttributes(attributes) - .setLoadBalancingPolicyConfig(lbConfig) - .build()); - return Status.OK; - } - } - - void handleNameResolutionError(Status error) { - getDelegate().handleNameResolutionError(error); - } - - @Deprecated - void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) { - getDelegate().handleSubchannelState(subchannel, stateInfo); - } - - void requestConnection() { - getDelegate().requestConnection(); - } - - void shutdown() { - delegate.shutdown(); - delegate = null; - } - - @VisibleForTesting - public LoadBalancer getDelegate() { - return delegate; - } - - @VisibleForTesting - void setDelegate(LoadBalancer lb) { - delegate = lb; - } - - @VisibleForTesting - LoadBalancerProvider getDelegateProvider() { - return delegateProvider; - } - - /** - * Resolves a load balancer based on given criteria. If policySelection is {@code null} and - * given servers contains any gRPC LB addresses, it will fall back to "grpclb". If no gRPC LB - * addresses are not present, it will fall back to {@link #defaultPolicy}. - * - * @param servers The list of servers reported - * @param policySelection the selected policy from raw service config - * @return the resolved policy selection - */ - @VisibleForTesting - ResolvedPolicySelection resolveLoadBalancerProvider( - List servers, @Nullable PolicySelection policySelection) - throws PolicyException { - // Check for balancer addresses - boolean haveBalancerAddress = false; - List backendAddrs = new ArrayList<>(); - for (EquivalentAddressGroup s : servers) { - if (s.getAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY) != null) { - haveBalancerAddress = true; - } else { - backendAddrs.add(s); - } - } - - if (policySelection != null) { - String policyName = policySelection.provider.getPolicyName(); - return new ResolvedPolicySelection( - policySelection, policyName.equals(GRPCLB_POLICY_NAME) ? servers : backendAddrs); - } - - if (haveBalancerAddress) { - // This is a special case where the existence of balancer address in the resolved address - // selects "grpclb" policy if the service config couldn't select a policy - LoadBalancerProvider grpclbProvider = registry.getProvider(GRPCLB_POLICY_NAME); - if (grpclbProvider == null) { - if (backendAddrs.isEmpty()) { - throw new PolicyException( - "Received ONLY balancer addresses but grpclb runtime is missing"); - } - if (!roundRobinDueToGrpclbDepMissing) { - // We don't log the warning every time we have an update. - roundRobinDueToGrpclbDepMissing = true; - String errorMsg = "Found balancer addresses but grpclb runtime is missing." - + " Will use round_robin. Please include grpc-grpclb in your runtime dependencies."; - helper.getChannelLogger().log(ChannelLogLevel.ERROR, errorMsg); - logger.warning(errorMsg); - } - return new ResolvedPolicySelection( - new PolicySelection( - getProviderOrThrow( - "round_robin", "received balancer addresses but grpclb runtime is missing"), - /* rawConfig = */ null, - /* config= */ null), - backendAddrs); - } - return new ResolvedPolicySelection( - new PolicySelection( - grpclbProvider, /* rawConfig= */ null, /* config= */ null), servers); - } - // No balancer address this time. If balancer address shows up later, we want to make sure - // the warning is logged one more time. - roundRobinDueToGrpclbDepMissing = false; - - // No config nor balancer address. Use default. - return new ResolvedPolicySelection( - new PolicySelection( - getProviderOrThrow(defaultPolicy, "using default policy"), - /* rawConfig= */ null, - /* config= */ null), - servers); - } - } - - private LoadBalancerProvider getProviderOrThrow(String policy, String choiceReason) - throws PolicyException { - LoadBalancerProvider provider = registry.getProvider(policy); - if (provider == null) { - throw new PolicyException( - "Trying to load '" + policy + "' because " + choiceReason + ", but it's unavailable"); - } - return provider; - } - - /** - * Parses first available LoadBalancer policy from service config. Available LoadBalancer should - * be registered to {@link LoadBalancerRegistry}. If the first available LoadBalancer policy is - * invalid, it doesn't fall-back to next available policy, instead it returns error. This also - * means, it ignores LoadBalancer policies after the first available one even if any of them are - * invalid. - * - *

Order of policy preference: - * - *

    - *
  1. Policy from "loadBalancingConfig" if present
  2. - *
  3. The policy from deprecated "loadBalancingPolicy" if present
  4. - *
- *

- * - *

Unlike a normal {@link LoadBalancer.Factory}, this accepts a full service config rather than - * the LoadBalancingConfig. - * - * @return the parsed {@link PolicySelection}, or {@code null} if no selection could be made. - */ - @Nullable - ConfigOrError parseLoadBalancerPolicy(Map serviceConfig, ChannelLogger channelLogger) { - try { - List loadBalancerConfigs = null; - if (serviceConfig != null) { - List> rawLbConfigs = - ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(serviceConfig); - loadBalancerConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(rawLbConfigs); - } - if (loadBalancerConfigs != null && !loadBalancerConfigs.isEmpty()) { - List policiesTried = new ArrayList<>(); - for (LbConfig lbConfig : loadBalancerConfigs) { - String policy = lbConfig.getPolicyName(); - LoadBalancerProvider provider = registry.getProvider(policy); - if (provider == null) { - policiesTried.add(policy); - } else { - if (!policiesTried.isEmpty()) { - channelLogger.log( - ChannelLogLevel.DEBUG, - "{0} specified by Service Config are not available", policiesTried); - } - ConfigOrError parsedLbPolicyConfig = - provider.parseLoadBalancingPolicyConfig(lbConfig.getRawConfigValue()); - if (parsedLbPolicyConfig.getError() != null) { - return parsedLbPolicyConfig; - } - return ConfigOrError.fromConfig( - new PolicySelection(provider, serviceConfig, parsedLbPolicyConfig.getConfig())); - } - } - return ConfigOrError.fromError( - Status.UNKNOWN.withDescription( - "None of " + policiesTried + " specified by Service Config are available.")); - } - return null; - } catch (RuntimeException e) { - return ConfigOrError.fromError( - Status.UNKNOWN.withDescription("can't parse load balancer configuration").withCause(e)); - } - } - - @VisibleForTesting - static final class PolicyException extends Exception { - private static final long serialVersionUID = 1L; - - private PolicyException(String msg) { - super(msg); - } - } - - @VisibleForTesting - static final class PolicySelection { - final LoadBalancerProvider provider; - @Nullable final Map rawConfig; - @Nullable final Object config; - - PolicySelection( - LoadBalancerProvider provider, - @Nullable Map rawConfig, - @Nullable Object config) { - this.provider = checkNotNull(provider, "provider"); - this.rawConfig = rawConfig; - this.config = config; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PolicySelection that = (PolicySelection) o; - return Objects.equal(provider, that.provider) - && Objects.equal(rawConfig, that.rawConfig) - && Objects.equal(config, that.config); - } - - @Override - public int hashCode() { - return Objects.hashCode(provider, rawConfig, config); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("provider", provider) - .add("rawConfig", rawConfig) - .add("config", config) - .toString(); - } - } - - @VisibleForTesting - static final class ResolvedPolicySelection { - final PolicySelection policySelection; - final List serverList; - - ResolvedPolicySelection( - PolicySelection policySelection, List serverList) { - this.policySelection = checkNotNull(policySelection, "policySelection"); - this.serverList = Collections.unmodifiableList(checkNotNull(serverList, "serverList")); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("policySelection", policySelection) - .add("serverList", serverList) - .toString(); - } - } - - private static final class EmptyPicker extends SubchannelPicker { - - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withNoResult(); - } - } - - private static final class FailingPicker extends SubchannelPicker { - private final Status failure; - - FailingPicker(Status failure) { - this.failure = failure; - } - - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withError(failure); - } - } -} diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 86def9eb0b..062acc3a59 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -27,6 +27,7 @@ import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.common.util.concurrent.ListenableFuture; @@ -96,7 +97,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; -import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; @@ -130,6 +130,11 @@ final class ManagedChannelImpl extends ManagedChannel implements static final Status SUBCHANNEL_SHUTDOWN_STATUS = Status.UNAVAILABLE.withDescription("Subchannel shutdown invoked"); + private static final ServiceConfigHolder EMPTY_SERVICE_CONFIG = + new ServiceConfigHolder( + Collections.emptyMap(), + ManagedChannelServiceConfig.empty()); + private final InternalLogId logId; private final String target; private final NameResolverRegistry nameResolverRegistry; @@ -243,28 +248,22 @@ final class ManagedChannelImpl extends ManagedChannel implements private final ChannelTracer channelTracer; private final ChannelLogger channelLogger; private final InternalChannelz channelz; + // Must be mutated and read from syncContext - @CheckForNull - private Boolean haveBackends; // a flag for doing channel tracing when flipped + // a flag for doing channel tracing when flipped + private ResolutionState lastResolutionState = ResolutionState.NO_RESOLUTION; // Must be mutated and read from constructor or syncContext - // TODO(notcarl): check this value when error in service config resolution + // used for channel tracing when value changed + private ServiceConfigHolder lastServiceConfig = EMPTY_SERVICE_CONFIG; @Nullable - private Map lastServiceConfig; // used for channel tracing when value changed - @Nullable - private final Map defaultServiceConfig; + private final ServiceConfigHolder defaultServiceConfig; // Must be mutated and read from constructor or syncContext - // See service config error handling spec for reference. - // TODO(notcarl): check this value when error in service config resolution - @SuppressWarnings("UnusedVariable") - private boolean waitingForServiceConfig = true; + private boolean serviceConfigUpdated = false; private final boolean lookUpServiceConfig; // One instance per channel. private final ChannelBufferMeter channelBufferUsed = new ChannelBufferMeter(); - @Nullable - private Throttle throttle; - private final long perRpcBufferLimit; private final long channelBufferLimit; @@ -504,6 +503,7 @@ final class ManagedChannelImpl extends ManagedChannel implements final Metadata headers, final Context context) { checkState(retryEnabled, "retry should be enabled"); + final Throttle throttle = lastServiceConfig.managedChannelServiceConfig.getRetryThrottling(); final class RetryStream extends RetriableStream { RetryStream() { super( @@ -582,18 +582,20 @@ final class ManagedChannelImpl extends ManagedChannel implements new ExecutorHolder( checkNotNull(builder.offloadExecutorPool, "offloadExecutorPool")); this.nameResolverRegistry = builder.nameResolverRegistry; + ScParser serviceConfigParser = + new ScParser( + retryEnabled, + builder.maxRetryAttempts, + builder.maxHedgedAttempts, + loadBalancerFactory, + channelLogger); this.nameResolverArgs = NameResolver.Args.newBuilder() .setDefaultPort(builder.getDefaultPort()) .setProxyDetector(proxyDetector) .setSynchronizationContext(syncContext) .setScheduledExecutorService(scheduledExecutor) - .setServiceConfigParser( - new ScParser( - retryEnabled, - builder.maxRetryAttempts, - builder.maxHedgedAttempts, - loadBalancerFactory)) + .setServiceConfigParser(serviceConfigParser) .setChannelLogger(channelLogger) .setOffloadExecutor( // Avoid creating the offloadExecutor until it is first used @@ -610,10 +612,23 @@ final class ManagedChannelImpl extends ManagedChannel implements this.delayedTransport = new DelayedClientTransport(this.executor, this.syncContext); this.delayedTransport.start(delayedTransportListener); this.backoffPolicyProvider = backoffPolicyProvider; - serviceConfigInterceptor = new ServiceConfigInterceptor( - retryEnabled, builder.maxRetryAttempts, builder.maxHedgedAttempts); - this.defaultServiceConfig = builder.defaultServiceConfig; - this.lastServiceConfig = defaultServiceConfig; + + serviceConfigInterceptor = new ServiceConfigInterceptor(retryEnabled); + if (builder.defaultServiceConfig != null) { + ConfigOrError parsedDefaultServiceConfig = + serviceConfigParser.parseServiceConfig(builder.defaultServiceConfig); + checkState( + parsedDefaultServiceConfig.getError() == null, + "Default config is invalid: %s", + parsedDefaultServiceConfig.getError()); + this.defaultServiceConfig = + new ServiceConfigHolder( + builder.defaultServiceConfig, + (ManagedChannelServiceConfig) parsedDefaultServiceConfig.getConfig()); + this.lastServiceConfig = this.defaultServiceConfig; + } else { + this.defaultServiceConfig = null; + } this.lookUpServiceConfig = builder.lookUpServiceConfig; Channel channel = new RealChannel(nameResolver.getServiceAuthority()); channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor); @@ -667,11 +682,8 @@ final class ManagedChannelImpl extends ManagedChannel implements // May only be called in constructor or syncContext private void handleServiceConfigUpdate() { - waitingForServiceConfig = false; - serviceConfigInterceptor.handleUpdate(lastServiceConfig); - if (retryEnabled) { - throttle = ServiceConfigUtil.getThrottlePolicy(lastServiceConfig); - } + serviceConfigUpdated = true; + serviceConfigInterceptor.handleUpdate(lastServiceConfig.managedChannelServiceConfig); } @VisibleForTesting @@ -1309,48 +1321,73 @@ final class ManagedChannelImpl extends ManagedChannel implements Attributes attrs = resolutionResult.getAttributes(); channelLogger.log( ChannelLogLevel.DEBUG, "Resolved address: {0}, config={1}", servers, attrs); + ResolutionState lastResolutionStateCopy = lastResolutionState; - if (haveBackends == null || !haveBackends) { + if (lastResolutionState != ResolutionState.SUCCESS) { channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); - haveBackends = true; + lastResolutionState = ResolutionState.SUCCESS; } nameResolverBackoffPolicy = null; + ConfigOrError configOrError = resolutionResult.getServiceConfig(); + ServiceConfigHolder validServiceConfig = null; + Status serviceConfigError = null; + if (configOrError != null) { + Map rawServiceConfig = + resolutionResult.getAttributes().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG); + validServiceConfig = configOrError.getConfig() == null + ? null + : new ServiceConfigHolder( + rawServiceConfig, (ManagedChannelServiceConfig) configOrError.getConfig()); + serviceConfigError = configOrError.getError(); + } - // Assuming no error in config resolution for now. - final Map serviceConfig = - attrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG); - Map effectiveServiceConfig; + ServiceConfigHolder effectiveServiceConfig; if (!lookUpServiceConfig) { - if (serviceConfig != null) { + if (validServiceConfig != null) { channelLogger.log( ChannelLogLevel.INFO, "Service config from name resolver discarded by channel settings"); } - effectiveServiceConfig = defaultServiceConfig; + effectiveServiceConfig = + defaultServiceConfig == null ? EMPTY_SERVICE_CONFIG : defaultServiceConfig; + attrs = attrs.toBuilder().discard(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG).build(); } else { // Try to use config if returned from name resolver // Otherwise, try to use the default config if available - if (serviceConfig != null) { - effectiveServiceConfig = serviceConfig; - } else { + if (validServiceConfig != null) { + effectiveServiceConfig = validServiceConfig; + } else if (defaultServiceConfig != null) { effectiveServiceConfig = defaultServiceConfig; - if (defaultServiceConfig != null) { + channelLogger.log( + ChannelLogLevel.INFO, + "Received no service config, using default service config"); + } else if (serviceConfigError != null) { + if (!serviceConfigUpdated) { + // First DNS lookup has invalid service config, and cannot fall back to default channelLogger.log( ChannelLogLevel.INFO, - "Received no service config, using default service config"); + "Fallback to error due to invalid first service config without default config"); + onError(configOrError.getError()); + return; + } else { + effectiveServiceConfig = lastServiceConfig; } + } else { + effectiveServiceConfig = EMPTY_SERVICE_CONFIG; } - - // FIXME(notcarl): reference equality is not right (although not harmful) right now. - // Name resolver should return the same config if txt record is the same - if (effectiveServiceConfig != lastServiceConfig) { - channelLogger.log(ChannelLogLevel.INFO, - "Service config changed{0}", effectiveServiceConfig == null ? " to null" : ""); + if (!effectiveServiceConfig.equals(lastServiceConfig)) { + channelLogger.log( + ChannelLogLevel.INFO, + "Service config changed{0}", + effectiveServiceConfig == EMPTY_SERVICE_CONFIG ? " to empty" : ""); lastServiceConfig = effectiveServiceConfig; } try { + // TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS + // and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But, + // lbNeedAddress is not deterministic handleServiceConfigUpdate(); } catch (RuntimeException re) { logger.log( @@ -1363,18 +1400,31 @@ final class ManagedChannelImpl extends ManagedChannel implements // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) { Attributes effectiveAttrs = attrs; - if (effectiveServiceConfig != serviceConfig) { + if (effectiveServiceConfig != validServiceConfig) { effectiveAttrs = attrs.toBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, effectiveServiceConfig) + .set( + GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, + effectiveServiceConfig.rawServiceConfig) .build(); } + Status handleResult = helper.lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(effectiveAttrs) - .build()); + .setAddresses(servers) + .setAttributes(effectiveAttrs) + .setLoadBalancingPolicyConfig( + effectiveServiceConfig.managedChannelServiceConfig.getLoadBalancingConfig()) + .build()); + if (!handleResult.isOk()) { - handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used")); + if (servers.isEmpty() && lastResolutionStateCopy == ResolutionState.SUCCESS) { + // lb doesn't expose that it needs address or not, because for some LB it is not + // deterministic. Assuming lb needs address if LB returns error when the address is + // empty and it is not the first resolution. + scheduleExponentialBackOffInSyncContext(); + } else { + handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used")); + } } } } @@ -1399,15 +1449,21 @@ final class ManagedChannelImpl extends ManagedChannel implements private void handleErrorInSyncContext(Status error) { logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}", new Object[] {getLogId(), error}); - if (haveBackends == null || haveBackends) { + if (lastResolutionState != ResolutionState.ERROR) { channelLogger.log(ChannelLogLevel.WARNING, "Failed to resolve name: {0}", error); - haveBackends = false; + lastResolutionState = ResolutionState.ERROR; } // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. if (NameResolverListener.this.helper != ManagedChannelImpl.this.lbHelper) { return; } + helper.lb.handleNameResolutionError(error); + + scheduleExponentialBackOffInSyncContext(); + } + + private void scheduleExponentialBackOffInSyncContext() { if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh.isPending()) { // The name resolver may invoke onError multiple times, but we only want to // schedule one backoff attempt @@ -1845,17 +1901,20 @@ final class ManagedChannelImpl extends ManagedChannel implements private final int maxRetryAttemptsLimit; private final int maxHedgedAttemptsLimit; private final AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory; + private final ChannelLogger channelLogger; ScParser( boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit, - AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory) { + AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory, + ChannelLogger channelLogger) { this.retryEnabled = retryEnabled; this.maxRetryAttemptsLimit = maxRetryAttemptsLimit; this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit; this.autoLoadBalancerFactory = checkNotNull(autoLoadBalancerFactory, "autoLoadBalancerFactory"); + this.channelLogger = checkNotNull(channelLogger, "channelLogger"); } @Override @@ -1863,7 +1922,7 @@ final class ManagedChannelImpl extends ManagedChannel implements try { Object loadBalancingPolicySelection; ConfigOrError choiceFromLoadBalancer = - autoLoadBalancerFactory.selectLoadBalancerPolicy(rawServiceConfig); + autoLoadBalancerFactory.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); if (choiceFromLoadBalancer == null) { loadBalancingPolicySelection = null; } else if (choiceFromLoadBalancer.getError() != null) { @@ -1895,4 +1954,54 @@ final class ManagedChannelImpl extends ManagedChannel implements + "See https://github.com/grpc/grpc-java/issues/5015 for more details", e); } } + + /** + * A ResolutionState indicates the status of last name resolution. + */ + enum ResolutionState { + NO_RESOLUTION, + SUCCESS, + ERROR + } + + // TODO(creamsoup) remove this class when AutoConfiguredLoadBalancerFactory doesn't require raw + // service config. + private static final class ServiceConfigHolder { + Map rawServiceConfig; + ManagedChannelServiceConfig managedChannelServiceConfig; + + ServiceConfigHolder( + Map rawServiceConfig, ManagedChannelServiceConfig managedChannelServiceConfig) { + this.rawServiceConfig = checkNotNull(rawServiceConfig, "rawServiceConfig"); + this.managedChannelServiceConfig = + checkNotNull(managedChannelServiceConfig, "managedChannelServiceConfig"); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServiceConfigHolder that = (ServiceConfigHolder) o; + return Objects.equal(rawServiceConfig, that.rawServiceConfig) + && Objects + .equal(managedChannelServiceConfig, that.managedChannelServiceConfig); + } + + @Override + public int hashCode() { + return Objects.hashCode(rawServiceConfig, managedChannelServiceConfig); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("rawServiceConfig", rawServiceConfig) + .add("managedChannelServiceConfig", managedChannelServiceConfig) + .toString(); + } + } } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl2.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl2.java deleted file mode 100644 index d2444f1713..0000000000 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl2.java +++ /dev/null @@ -1,2007 +0,0 @@ -/* - * Copyright 2016 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.internal; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static io.grpc.ConnectivityState.IDLE; -import static io.grpc.ConnectivityState.SHUTDOWN; -import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; -import static io.grpc.internal.ServiceConfigInterceptor2.HEDGING_POLICY_KEY; -import static io.grpc.internal.ServiceConfigInterceptor2.RETRY_POLICY_KEY; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import com.google.common.base.Stopwatch; -import com.google.common.base.Supplier; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; -import io.grpc.Attributes; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ChannelLogger; -import io.grpc.ChannelLogger.ChannelLogLevel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.ClientInterceptors; -import io.grpc.ClientStreamTracer; -import io.grpc.CompressorRegistry; -import io.grpc.ConnectivityState; -import io.grpc.ConnectivityStateInfo; -import io.grpc.Context; -import io.grpc.DecompressorRegistry; -import io.grpc.EquivalentAddressGroup; -import io.grpc.InternalChannelz; -import io.grpc.InternalChannelz.ChannelStats; -import io.grpc.InternalChannelz.ChannelTrace; -import io.grpc.InternalInstrumented; -import io.grpc.InternalLogId; -import io.grpc.InternalWithLogId; -import io.grpc.LoadBalancer; -import io.grpc.LoadBalancer.CreateSubchannelArgs; -import io.grpc.LoadBalancer.PickResult; -import io.grpc.LoadBalancer.PickSubchannelArgs; -import io.grpc.LoadBalancer.ResolvedAddresses; -import io.grpc.LoadBalancer.SubchannelPicker; -import io.grpc.LoadBalancer.SubchannelStateListener; -import io.grpc.ManagedChannel; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.NameResolver; -import io.grpc.NameResolver.ConfigOrError; -import io.grpc.NameResolver.ResolutionResult; -import io.grpc.NameResolverRegistry; -import io.grpc.ProxyDetector; -import io.grpc.Status; -import io.grpc.SynchronizationContext; -import io.grpc.SynchronizationContext.ScheduledHandle; -import io.grpc.internal.AutoConfiguredLoadBalancerFactory2.AutoConfiguredLoadBalancer; -import io.grpc.internal.ClientCallImpl.ClientTransportProvider; -import io.grpc.internal.RetriableStream.ChannelBufferMeter; -import io.grpc.internal.RetriableStream.Throttle; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; -import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; -import javax.annotation.concurrent.ThreadSafe; - -/** A communication channel for making outgoing RPCs. */ -@ThreadSafe -final class ManagedChannelImpl2 extends ManagedChannel implements - InternalInstrumented { - static final Logger logger = Logger.getLogger(ManagedChannelImpl2.class.getName()); - - // Matching this pattern means the target string is a URI target or at least intended to be one. - // A URI target must be an absolute hierarchical URI. - // From RFC 2396: scheme = alpha *( alpha | digit | "+" | "-" | "." ) - @VisibleForTesting - static final Pattern URI_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+.-]*:/.*"); - - static final long IDLE_TIMEOUT_MILLIS_DISABLE = -1; - - @VisibleForTesting - static final long SUBCHANNEL_SHUTDOWN_DELAY_SECONDS = 5; - - @VisibleForTesting - static final Status SHUTDOWN_NOW_STATUS = - Status.UNAVAILABLE.withDescription("Channel shutdownNow invoked"); - - @VisibleForTesting - static final Status SHUTDOWN_STATUS = - Status.UNAVAILABLE.withDescription("Channel shutdown invoked"); - - @VisibleForTesting - static final Status SUBCHANNEL_SHUTDOWN_STATUS = - Status.UNAVAILABLE.withDescription("Subchannel shutdown invoked"); - - private static final ServiceConfigHolder EMPTY_SERVICE_CONFIG = - new ServiceConfigHolder( - Collections.emptyMap(), - ManagedChannelServiceConfig2.empty()); - - private final InternalLogId logId; - private final String target; - private final NameResolverRegistry nameResolverRegistry; - private final NameResolver.Factory nameResolverFactory; - private final NameResolver.Args nameResolverArgs; - private final AutoConfiguredLoadBalancerFactory2 loadBalancerFactory; - private final ClientTransportFactory transportFactory; - private final RestrictedScheduledExecutor scheduledExecutor; - private final Executor executor; - private final ObjectPool executorPool; - private final ObjectPool balancerRpcExecutorPool; - private final ExecutorHolder balancerRpcExecutorHolder; - private final ExecutorHolder offloadExecutorHolder; - private final TimeProvider timeProvider; - private final int maxTraceEvents; - - @VisibleForTesting - final SynchronizationContext syncContext = new SynchronizationContext( - new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - logger.log( - Level.SEVERE, - "[" + getLogId() + "] Uncaught exception in the SynchronizationContext. Panic!", - e); - panic(e); - } - }); - - private boolean fullStreamDecompression; - - private final DecompressorRegistry decompressorRegistry; - private final CompressorRegistry compressorRegistry; - - private final Supplier stopwatchSupplier; - /** The timout before entering idle mode. */ - private final long idleTimeoutMillis; - - private final ConnectivityStateManager channelStateManager = new ConnectivityStateManager(); - - private final ServiceConfigInterceptor2 serviceConfigInterceptor; - - private final BackoffPolicy.Provider backoffPolicyProvider; - - /** - * We delegate to this channel, so that we can have interceptors as necessary. If there aren't - * any interceptors and the {@link io.grpc.BinaryLog} is {@code null} then this will just be a - * {@link RealChannel}. - */ - private final Channel interceptorChannel; - @Nullable private final String userAgent; - - // Only null after channel is terminated. Must be assigned from the syncContext. - private NameResolver nameResolver; - - // Must be accessed from the syncContext. - private boolean nameResolverStarted; - - // null when channel is in idle mode. Must be assigned from syncContext. - @Nullable - private LbHelperImpl lbHelper; - - // Must ONLY be assigned from updateSubchannelPicker(), which is called from syncContext. - // null if channel is in idle mode. - @Nullable - private volatile SubchannelPicker subchannelPicker; - - // Must be accessed from the syncContext - private boolean panicMode; - - // Must be mutated from syncContext - // If any monitoring hook to be added later needs to get a snapshot of this Set, we could - // switch to a ConcurrentHashMap. - private final Set subchannels = new HashSet<>(16, .75f); - - // Must be mutated from syncContext - private final Set oobChannels = new HashSet<>(1, .75f); - - // reprocess() must be run from syncContext - private final DelayedClientTransport delayedTransport; - private final UncommittedRetriableStreamsRegistry uncommittedRetriableStreamsRegistry - = new UncommittedRetriableStreamsRegistry(); - - // Shutdown states. - // - // Channel's shutdown process: - // 1. shutdown(): stop accepting new calls from applications - // 1a shutdown <- true - // 1b subchannelPicker <- null - // 1c delayedTransport.shutdown() - // 2. delayedTransport terminated: stop stream-creation functionality - // 2a terminating <- true - // 2b loadBalancer.shutdown() - // * LoadBalancer will shutdown subchannels and OOB channels - // 2c loadBalancer <- null - // 2d nameResolver.shutdown() - // 2e nameResolver <- null - // 3. All subchannels and OOB channels terminated: Channel considered terminated - - private final AtomicBoolean shutdown = new AtomicBoolean(false); - // Must only be mutated and read from syncContext - private boolean shutdownNowed; - // Must only be mutated from syncContext - private volatile boolean terminating; - // Must be mutated from syncContext - private volatile boolean terminated; - private final CountDownLatch terminatedLatch = new CountDownLatch(1); - - private final CallTracer.Factory callTracerFactory; - private final CallTracer channelCallTracer; - private final ChannelTracer channelTracer; - private final ChannelLogger channelLogger; - private final InternalChannelz channelz; - - // Must be mutated and read from syncContext - // a flag for doing channel tracing when flipped - private ResolutionState lastResolutionState = ResolutionState.NO_RESOLUTION; - // Must be mutated and read from constructor or syncContext - // used for channel tracing when value changed - private ServiceConfigHolder lastServiceConfig = EMPTY_SERVICE_CONFIG; - @Nullable - private final ServiceConfigHolder defaultServiceConfig; - // Must be mutated and read from constructor or syncContext - private boolean serviceConfigUpdated = false; - private final boolean lookUpServiceConfig; - - // One instance per channel. - private final ChannelBufferMeter channelBufferUsed = new ChannelBufferMeter(); - - private final long perRpcBufferLimit; - private final long channelBufferLimit; - - // Temporary false flag that can skip the retry code path. - private final boolean retryEnabled; - - // Called from syncContext - private final ManagedClientTransport.Listener delayedTransportListener = - new DelayedTransportListener(); - - // Must be called from syncContext - private void maybeShutdownNowSubchannels() { - if (shutdownNowed) { - for (InternalSubchannel subchannel : subchannels) { - subchannel.shutdownNow(SHUTDOWN_NOW_STATUS); - } - for (OobChannel oobChannel : oobChannels) { - oobChannel.getInternalSubchannel().shutdownNow(SHUTDOWN_NOW_STATUS); - } - } - } - - // Must be accessed from syncContext - @VisibleForTesting - final InUseStateAggregator inUseStateAggregator = new IdleModeStateAggregator(); - - @Override - public ListenableFuture getStats() { - final SettableFuture ret = SettableFuture.create(); - final class StatsFetcher implements Runnable { - @Override - public void run() { - ChannelStats.Builder builder = new InternalChannelz.ChannelStats.Builder(); - channelCallTracer.updateBuilder(builder); - channelTracer.updateBuilder(builder); - builder.setTarget(target).setState(channelStateManager.getState()); - List children = new ArrayList<>(); - children.addAll(subchannels); - children.addAll(oobChannels); - builder.setSubchannels(children); - ret.set(builder.build()); - } - } - - // subchannels and oobchannels can only be accessed from syncContext - syncContext.execute(new StatsFetcher()); - return ret; - } - - @Override - public InternalLogId getLogId() { - return logId; - } - - // Run from syncContext - private class IdleModeTimer implements Runnable { - - @Override - public void run() { - enterIdleMode(); - } - } - - // Must be called from syncContext - private void shutdownNameResolverAndLoadBalancer(boolean channelIsActive) { - syncContext.throwIfNotInThisSynchronizationContext(); - if (channelIsActive) { - checkState(nameResolverStarted, "nameResolver is not started"); - checkState(lbHelper != null, "lbHelper is null"); - } - if (nameResolver != null) { - cancelNameResolverBackoff(); - nameResolver.shutdown(); - nameResolverStarted = false; - if (channelIsActive) { - nameResolver = getNameResolver(target, nameResolverFactory, nameResolverArgs); - } else { - nameResolver = null; - } - } - if (lbHelper != null) { - lbHelper.lb.shutdown(); - lbHelper = null; - } - subchannelPicker = null; - } - - /** - * Make the channel exit idle mode, if it's in it. - * - *

Must be called from syncContext - */ - @VisibleForTesting - void exitIdleMode() { - syncContext.throwIfNotInThisSynchronizationContext(); - if (shutdown.get() || panicMode) { - return; - } - if (inUseStateAggregator.isInUse()) { - // Cancel the timer now, so that a racing due timer will not put Channel on idleness - // when the caller of exitIdleMode() is about to use the returned loadBalancer. - cancelIdleTimer(false); - } else { - // exitIdleMode() may be called outside of inUseStateAggregator.handleNotInUse() while - // isInUse() == false, in which case we still need to schedule the timer. - rescheduleIdleTimer(); - } - if (lbHelper != null) { - return; - } - channelLogger.log(ChannelLogLevel.INFO, "Exiting idle mode"); - LbHelperImpl lbHelper = new LbHelperImpl(); - lbHelper.lb = loadBalancerFactory.newLoadBalancer(lbHelper); - // Delay setting lbHelper until fully initialized, since loadBalancerFactory is user code and - // may throw. We don't want to confuse our state, even if we will enter panic mode. - this.lbHelper = lbHelper; - - NameResolverListener listener = new NameResolverListener(lbHelper, nameResolver); - nameResolver.start(listener); - nameResolverStarted = true; - } - - // Must be run from syncContext - private void enterIdleMode() { - // nameResolver and loadBalancer are guaranteed to be non-null. If any of them were null, - // either the idleModeTimer ran twice without exiting the idle mode, or the task in shutdown() - // did not cancel idleModeTimer, or enterIdle() ran while shutdown or in idle, all of - // which are bugs. - shutdownNameResolverAndLoadBalancer(true); - delayedTransport.reprocess(null); - channelLogger.log(ChannelLogLevel.INFO, "Entering IDLE state"); - channelStateManager.gotoState(IDLE); - if (inUseStateAggregator.isInUse()) { - exitIdleMode(); - } - } - - // Must be run from syncContext - private void cancelIdleTimer(boolean permanent) { - idleTimer.cancel(permanent); - } - - // Always run from syncContext - private void rescheduleIdleTimer() { - if (idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) { - return; - } - idleTimer.reschedule(idleTimeoutMillis, TimeUnit.MILLISECONDS); - } - - // Run from syncContext - @VisibleForTesting - class DelayedNameResolverRefresh implements Runnable { - @Override - public void run() { - scheduledNameResolverRefresh = null; - refreshNameResolution(); - } - } - - // Must be used from syncContext - @Nullable private ScheduledHandle scheduledNameResolverRefresh; - // The policy to control backoff between name resolution attempts. Non-null when an attempt is - // scheduled. Must be used from syncContext - @Nullable private BackoffPolicy nameResolverBackoffPolicy; - - // Must be run from syncContext - private void cancelNameResolverBackoff() { - syncContext.throwIfNotInThisSynchronizationContext(); - if (scheduledNameResolverRefresh != null) { - scheduledNameResolverRefresh.cancel(); - scheduledNameResolverRefresh = null; - nameResolverBackoffPolicy = null; - } - } - - /** - * Force name resolution refresh to happen immediately and reset refresh back-off. Must be run - * from syncContext. - */ - private void refreshAndResetNameResolution() { - syncContext.throwIfNotInThisSynchronizationContext(); - cancelNameResolverBackoff(); - refreshNameResolution(); - } - - private void refreshNameResolution() { - syncContext.throwIfNotInThisSynchronizationContext(); - if (nameResolverStarted) { - nameResolver.refresh(); - } - } - - private final class ChannelTransportProvider implements ClientTransportProvider { - @Override - public ClientTransport get(PickSubchannelArgs args) { - SubchannelPicker pickerCopy = subchannelPicker; - if (shutdown.get()) { - // If channel is shut down, delayedTransport is also shut down which will fail the stream - // properly. - return delayedTransport; - } - if (pickerCopy == null) { - final class ExitIdleModeForTransport implements Runnable { - @Override - public void run() { - exitIdleMode(); - } - } - - syncContext.execute(new ExitIdleModeForTransport()); - return delayedTransport; - } - // There is no need to reschedule the idle timer here. - // - // pickerCopy != null, which means idle timer has not expired when this method starts. - // Even if idle timer expires right after we grab pickerCopy, and it shuts down LoadBalancer - // which calls Subchannel.shutdown(), the InternalSubchannel will be actually shutdown after - // SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, which gives the caller time to start RPC on it. - // - // In most cases the idle timer is scheduled to fire after the transport has created the - // stream, which would have reported in-use state to the channel that would have cancelled - // the idle timer. - PickResult pickResult = pickerCopy.pickSubchannel(args); - ClientTransport transport = GrpcUtil.getTransportFromPickResult( - pickResult, args.getCallOptions().isWaitForReady()); - if (transport != null) { - return transport; - } - return delayedTransport; - } - - @Override - public ClientStream newRetriableStream( - final MethodDescriptor method, - final CallOptions callOptions, - final Metadata headers, - final Context context) { - checkState(retryEnabled, "retry should be enabled"); - final Throttle throttle = lastServiceConfig.managedChannelServiceConfig.getRetryThrottling(); - final class RetryStream extends RetriableStream { - RetryStream() { - super( - method, - headers, - channelBufferUsed, - perRpcBufferLimit, - channelBufferLimit, - getCallExecutor(callOptions), - transportFactory.getScheduledExecutorService(), - callOptions.getOption(RETRY_POLICY_KEY), - callOptions.getOption(HEDGING_POLICY_KEY), - throttle); - } - - @Override - Status prestart() { - return uncommittedRetriableStreamsRegistry.add(this); - } - - @Override - void postCommit() { - uncommittedRetriableStreamsRegistry.remove(this); - } - - @Override - ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata newHeaders) { - CallOptions newOptions = callOptions.withStreamTracerFactory(tracerFactory); - ClientTransport transport = - get(new PickSubchannelArgsImpl(method, newHeaders, newOptions)); - Context origContext = context.attach(); - try { - return transport.newStream(method, newHeaders, newOptions); - } finally { - context.detach(origContext); - } - } - } - - return new RetryStream(); - } - } - - private final ClientTransportProvider transportProvider = new ChannelTransportProvider(); - - private final Rescheduler idleTimer; - - ManagedChannelImpl2( - AbstractManagedChannelImplBuilder builder, - ClientTransportFactory clientTransportFactory, - BackoffPolicy.Provider backoffPolicyProvider, - ObjectPool balancerRpcExecutorPool, - Supplier stopwatchSupplier, - List interceptors, - final TimeProvider timeProvider) { - this.target = checkNotNull(builder.target, "target"); - this.logId = InternalLogId.allocate("Channel", target); - this.timeProvider = checkNotNull(timeProvider, "timeProvider"); - this.executorPool = checkNotNull(builder.executorPool, "executorPool"); - this.executor = checkNotNull(executorPool.getObject(), "executor"); - this.transportFactory = - new CallCredentialsApplyingTransportFactory(clientTransportFactory, this.executor); - this.scheduledExecutor = - new RestrictedScheduledExecutor(transportFactory.getScheduledExecutorService()); - maxTraceEvents = builder.maxTraceEvents; - channelTracer = new ChannelTracer( - logId, builder.maxTraceEvents, timeProvider.currentTimeNanos(), - "Channel for '" + target + "'"); - channelLogger = new ChannelLoggerImpl(channelTracer, timeProvider); - this.nameResolverFactory = builder.getNameResolverFactory(); - ProxyDetector proxyDetector = - builder.proxyDetector != null ? builder.proxyDetector : GrpcUtil.DEFAULT_PROXY_DETECTOR; - this.retryEnabled = builder.retryEnabled && !builder.temporarilyDisableRetry; - this.loadBalancerFactory = new AutoConfiguredLoadBalancerFactory2(builder.defaultLbPolicy); - this.offloadExecutorHolder = - new ExecutorHolder( - checkNotNull(builder.offloadExecutorPool, "offloadExecutorPool")); - this.nameResolverRegistry = builder.nameResolverRegistry; - ScParser serviceConfigParser = - new ScParser( - retryEnabled, - builder.maxRetryAttempts, - builder.maxHedgedAttempts, - loadBalancerFactory, - channelLogger); - this.nameResolverArgs = - NameResolver.Args.newBuilder() - .setDefaultPort(builder.getDefaultPort()) - .setProxyDetector(proxyDetector) - .setSynchronizationContext(syncContext) - .setScheduledExecutorService(scheduledExecutor) - .setServiceConfigParser(serviceConfigParser) - .setChannelLogger(channelLogger) - .setOffloadExecutor( - // Avoid creating the offloadExecutor until it is first used - new Executor() { - @Override - public void execute(Runnable command) { - offloadExecutorHolder.getExecutor().execute(command); - } - }) - .build(); - this.nameResolver = getNameResolver(target, nameResolverFactory, nameResolverArgs); - this.balancerRpcExecutorPool = checkNotNull(balancerRpcExecutorPool, "balancerRpcExecutorPool"); - this.balancerRpcExecutorHolder = new ExecutorHolder(balancerRpcExecutorPool); - this.delayedTransport = new DelayedClientTransport(this.executor, this.syncContext); - this.delayedTransport.start(delayedTransportListener); - this.backoffPolicyProvider = backoffPolicyProvider; - - serviceConfigInterceptor = new ServiceConfigInterceptor2(retryEnabled); - if (builder.defaultServiceConfig != null) { - ConfigOrError parsedDefaultServiceConfig = - serviceConfigParser.parseServiceConfig(builder.defaultServiceConfig); - checkState( - parsedDefaultServiceConfig.getError() == null, - "Default config is invalid: %s", - parsedDefaultServiceConfig.getError()); - this.defaultServiceConfig = - new ServiceConfigHolder( - builder.defaultServiceConfig, - (ManagedChannelServiceConfig2) parsedDefaultServiceConfig.getConfig()); - this.lastServiceConfig = this.defaultServiceConfig; - } else { - this.defaultServiceConfig = null; - } - this.lookUpServiceConfig = builder.lookUpServiceConfig; - Channel channel = new RealChannel(nameResolver.getServiceAuthority()); - channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor); - if (builder.binlog != null) { - channel = builder.binlog.wrapChannel(channel); - } - this.interceptorChannel = ClientInterceptors.intercept(channel, interceptors); - this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier"); - if (builder.idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) { - this.idleTimeoutMillis = builder.idleTimeoutMillis; - } else { - checkArgument( - builder.idleTimeoutMillis - >= AbstractManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS, - "invalid idleTimeoutMillis %s", builder.idleTimeoutMillis); - this.idleTimeoutMillis = builder.idleTimeoutMillis; - } - - idleTimer = new Rescheduler( - new IdleModeTimer(), - syncContext, - transportFactory.getScheduledExecutorService(), - stopwatchSupplier.get()); - this.fullStreamDecompression = builder.fullStreamDecompression; - this.decompressorRegistry = checkNotNull(builder.decompressorRegistry, "decompressorRegistry"); - this.compressorRegistry = checkNotNull(builder.compressorRegistry, "compressorRegistry"); - this.userAgent = builder.userAgent; - - this.channelBufferLimit = builder.retryBufferSize; - this.perRpcBufferLimit = builder.perRpcBufferLimit; - final class ChannelCallTracerFactory implements CallTracer.Factory { - @Override - public CallTracer create() { - return new CallTracer(timeProvider); - } - } - - this.callTracerFactory = new ChannelCallTracerFactory(); - channelCallTracer = callTracerFactory.create(); - this.channelz = checkNotNull(builder.channelz); - channelz.addRootChannel(this); - - if (!lookUpServiceConfig) { - if (defaultServiceConfig != null) { - channelLogger.log( - ChannelLogLevel.INFO, "Service config look-up disabled, using default service config"); - } - handleServiceConfigUpdate(); - } - } - - // May only be called in constructor or syncContext - private void handleServiceConfigUpdate() { - serviceConfigUpdated = true; - serviceConfigInterceptor.handleUpdate(lastServiceConfig.managedChannelServiceConfig); - } - - @VisibleForTesting - static NameResolver getNameResolver(String target, NameResolver.Factory nameResolverFactory, - NameResolver.Args nameResolverArgs) { - // Finding a NameResolver. Try using the target string as the URI. If that fails, try prepending - // "dns:///". - URI targetUri = null; - StringBuilder uriSyntaxErrors = new StringBuilder(); - try { - targetUri = new URI(target); - // For "localhost:8080" this would likely cause newNameResolver to return null, because - // "localhost" is parsed as the scheme. Will fall into the next branch and try - // "dns:///localhost:8080". - } catch (URISyntaxException e) { - // Can happen with ip addresses like "[::1]:1234" or 127.0.0.1:1234. - uriSyntaxErrors.append(e.getMessage()); - } - if (targetUri != null) { - NameResolver resolver = nameResolverFactory.newNameResolver(targetUri, nameResolverArgs); - if (resolver != null) { - return resolver; - } - // "foo.googleapis.com:8080" cause resolver to be null, because "foo.googleapis.com" is an - // unmapped scheme. Just fall through and will try "dns:///foo.googleapis.com:8080" - } - - // If we reached here, the targetUri couldn't be used. - if (!URI_PATTERN.matcher(target).matches()) { - // It doesn't look like a URI target. Maybe it's an authority string. Try with the default - // scheme from the factory. - try { - targetUri = new URI(nameResolverFactory.getDefaultScheme(), "", "/" + target, null); - } catch (URISyntaxException e) { - // Should not be possible. - throw new IllegalArgumentException(e); - } - NameResolver resolver = nameResolverFactory.newNameResolver(targetUri, nameResolverArgs); - if (resolver != null) { - return resolver; - } - } - throw new IllegalArgumentException(String.format( - "cannot find a NameResolver for %s%s", - target, uriSyntaxErrors.length() > 0 ? " (" + uriSyntaxErrors + ")" : "")); - } - - /** - * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately - * cancelled. - */ - @Override - public ManagedChannelImpl2 shutdown() { - channelLogger.log(ChannelLogLevel.DEBUG, "shutdown() called"); - if (!shutdown.compareAndSet(false, true)) { - return this; - } - - // Put gotoState(SHUTDOWN) as early into the syncContext's queue as possible. - // delayedTransport.shutdown() may also add some tasks into the queue. But some things inside - // delayedTransport.shutdown() like setting delayedTransport.shutdown = true are not run in the - // syncContext's queue and should not be blocked, so we do not drain() immediately here. - final class Shutdown implements Runnable { - @Override - public void run() { - channelLogger.log(ChannelLogLevel.INFO, "Entering SHUTDOWN state"); - channelStateManager.gotoState(SHUTDOWN); - } - } - - syncContext.executeLater(new Shutdown()); - - uncommittedRetriableStreamsRegistry.onShutdown(SHUTDOWN_STATUS); - final class CancelIdleTimer implements Runnable { - @Override - public void run() { - cancelIdleTimer(/* permanent= */ true); - } - } - - syncContext.execute(new CancelIdleTimer()); - return this; - } - - /** - * Initiates a forceful shutdown in which preexisting and new calls are cancelled. Although - * forceful, the shutdown process is still not instantaneous; {@link #isTerminated()} will likely - * return {@code false} immediately after this method returns. - */ - @Override - public ManagedChannelImpl2 shutdownNow() { - channelLogger.log(ChannelLogLevel.DEBUG, "shutdownNow() called"); - shutdown(); - uncommittedRetriableStreamsRegistry.onShutdownNow(SHUTDOWN_NOW_STATUS); - final class ShutdownNow implements Runnable { - @Override - public void run() { - if (shutdownNowed) { - return; - } - shutdownNowed = true; - maybeShutdownNowSubchannels(); - } - } - - syncContext.execute(new ShutdownNow()); - return this; - } - - // Called from syncContext - @VisibleForTesting - void panic(final Throwable t) { - if (panicMode) { - // Preserve the first panic information - return; - } - panicMode = true; - cancelIdleTimer(/* permanent= */ true); - shutdownNameResolverAndLoadBalancer(false); - final class PanicSubchannelPicker extends SubchannelPicker { - private final PickResult panicPickResult = - PickResult.withDrop( - Status.INTERNAL.withDescription("Panic! This is a bug!").withCause(t)); - - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return panicPickResult; - } - } - - updateSubchannelPicker(new PanicSubchannelPicker()); - channelLogger.log(ChannelLogLevel.ERROR, "PANIC! Entering TRANSIENT_FAILURE"); - channelStateManager.gotoState(TRANSIENT_FAILURE); - } - - @VisibleForTesting - boolean isInPanicMode() { - return panicMode; - } - - // Called from syncContext - private void updateSubchannelPicker(SubchannelPicker newPicker) { - subchannelPicker = newPicker; - delayedTransport.reprocess(newPicker); - } - - @Override - public boolean isShutdown() { - return shutdown.get(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return terminatedLatch.await(timeout, unit); - } - - @Override - public boolean isTerminated() { - return terminated; - } - - /* - * Creates a new outgoing call on the channel. - */ - @Override - public ClientCall newCall(MethodDescriptor method, - CallOptions callOptions) { - return interceptorChannel.newCall(method, callOptions); - } - - @Override - public String authority() { - return interceptorChannel.authority(); - } - - private Executor getCallExecutor(CallOptions callOptions) { - Executor executor = callOptions.getExecutor(); - if (executor == null) { - executor = this.executor; - } - return executor; - } - - private class RealChannel extends Channel { - // Set when the NameResolver is initially created. When we create a new NameResolver for the - // same target, the new instance must have the same value. - private final String authority; - - private RealChannel(String authority) { - this.authority = checkNotNull(authority, "authority"); - } - - @Override - public ClientCall newCall(MethodDescriptor method, - CallOptions callOptions) { - return new ClientCallImpl<>( - method, - getCallExecutor(callOptions), - callOptions, - transportProvider, - terminated ? null : transportFactory.getScheduledExecutorService(), - channelCallTracer, - retryEnabled) - .setFullStreamDecompression(fullStreamDecompression) - .setDecompressorRegistry(decompressorRegistry) - .setCompressorRegistry(compressorRegistry); - } - - @Override - public String authority() { - return authority; - } - } - - /** - * Terminate the channel if termination conditions are met. - */ - // Must be run from syncContext - private void maybeTerminateChannel() { - if (terminated) { - return; - } - if (shutdown.get() && subchannels.isEmpty() && oobChannels.isEmpty()) { - channelLogger.log(ChannelLogLevel.INFO, "Terminated"); - channelz.removeRootChannel(this); - executorPool.returnObject(executor); - balancerRpcExecutorHolder.release(); - offloadExecutorHolder.release(); - // Release the transport factory so that it can deallocate any resources. - transportFactory.close(); - - terminated = true; - terminatedLatch.countDown(); - } - } - - // Must be called from syncContext - private void handleInternalSubchannelState(ConnectivityStateInfo newState) { - if (newState.getState() == TRANSIENT_FAILURE || newState.getState() == IDLE) { - refreshAndResetNameResolution(); - } - } - - @Override - @SuppressWarnings("deprecation") - public ConnectivityState getState(boolean requestConnection) { - ConnectivityState savedChannelState = channelStateManager.getState(); - if (requestConnection && savedChannelState == IDLE) { - final class RequestConnection implements Runnable { - @Override - public void run() { - exitIdleMode(); - if (subchannelPicker != null) { - subchannelPicker.requestConnection(); - } - if (lbHelper != null) { - lbHelper.lb.requestConnection(); - } - } - } - - syncContext.execute(new RequestConnection()); - } - return savedChannelState; - } - - @Override - public void notifyWhenStateChanged(final ConnectivityState source, final Runnable callback) { - final class NotifyStateChanged implements Runnable { - @Override - public void run() { - channelStateManager.notifyWhenStateChanged(callback, executor, source); - } - } - - syncContext.execute(new NotifyStateChanged()); - } - - @Override - public void resetConnectBackoff() { - final class ResetConnectBackoff implements Runnable { - @Override - public void run() { - if (shutdown.get()) { - return; - } - if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh.isPending()) { - checkState(nameResolverStarted, "name resolver must be started"); - refreshAndResetNameResolution(); - } - for (InternalSubchannel subchannel : subchannels) { - subchannel.resetConnectBackoff(); - } - for (OobChannel oobChannel : oobChannels) { - oobChannel.resetConnectBackoff(); - } - } - } - - syncContext.execute(new ResetConnectBackoff()); - } - - @Override - public void enterIdle() { - final class PrepareToLoseNetworkRunnable implements Runnable { - @Override - public void run() { - if (shutdown.get() || lbHelper == null) { - return; - } - cancelIdleTimer(/* permanent= */ false); - enterIdleMode(); - } - } - - syncContext.execute(new PrepareToLoseNetworkRunnable()); - } - - /** - * A registry that prevents channel shutdown from killing existing retry attempts that are in - * backoff. - */ - private final class UncommittedRetriableStreamsRegistry { - // TODO(zdapeng): This means we would acquire a lock for each new retry-able stream, - // it's worthwhile to look for a lock-free approach. - final Object lock = new Object(); - - @GuardedBy("lock") - Collection uncommittedRetriableStreams = new HashSet<>(); - - @GuardedBy("lock") - Status shutdownStatus; - - void onShutdown(Status reason) { - boolean shouldShutdownDelayedTransport = false; - synchronized (lock) { - if (shutdownStatus != null) { - return; - } - shutdownStatus = reason; - // Keep the delayedTransport open until there is no more uncommitted streams, b/c those - // retriable streams, which may be in backoff and not using any transport, are already - // started RPCs. - if (uncommittedRetriableStreams.isEmpty()) { - shouldShutdownDelayedTransport = true; - } - } - - if (shouldShutdownDelayedTransport) { - delayedTransport.shutdown(reason); - } - } - - void onShutdownNow(Status reason) { - onShutdown(reason); - Collection streams; - - synchronized (lock) { - streams = new ArrayList<>(uncommittedRetriableStreams); - } - - for (ClientStream stream : streams) { - stream.cancel(reason); - } - delayedTransport.shutdownNow(reason); - } - - /** - * Registers a RetriableStream and return null if not shutdown, otherwise just returns the - * shutdown Status. - */ - @Nullable - Status add(RetriableStream retriableStream) { - synchronized (lock) { - if (shutdownStatus != null) { - return shutdownStatus; - } - uncommittedRetriableStreams.add(retriableStream); - return null; - } - } - - void remove(RetriableStream retriableStream) { - Status shutdownStatusCopy = null; - - synchronized (lock) { - uncommittedRetriableStreams.remove(retriableStream); - if (uncommittedRetriableStreams.isEmpty()) { - shutdownStatusCopy = shutdownStatus; - // Because retriable transport is long-lived, we take this opportunity to down-size the - // hashmap. - uncommittedRetriableStreams = new HashSet<>(); - } - } - - if (shutdownStatusCopy != null) { - delayedTransport.shutdown(shutdownStatusCopy); - } - } - } - - private class LbHelperImpl extends LoadBalancer.Helper { - AutoConfiguredLoadBalancer lb; - - @Deprecated - @Override - public AbstractSubchannel createSubchannel( - List addressGroups, Attributes attrs) { - logWarningIfNotInSyncContext("createSubchannel()"); - // TODO(ejona): can we be even stricter? Like loadBalancer == null? - checkNotNull(addressGroups, "addressGroups"); - checkNotNull(attrs, "attrs"); - final SubchannelImpl subchannel = createSubchannelInternal( - CreateSubchannelArgs.newBuilder() - .setAddresses(addressGroups) - .setAttributes(attrs) - .build()); - - final SubchannelStateListener listener = - new LoadBalancer.SubchannelStateListener() { - @Override - public void onSubchannelState(ConnectivityStateInfo newState) { - // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. - if (LbHelperImpl.this != ManagedChannelImpl2.this.lbHelper) { - return; - } - lb.handleSubchannelState(subchannel, newState); - } - }; - - subchannel.internalStart(listener); - return subchannel; - } - - @Override - public AbstractSubchannel createSubchannel(CreateSubchannelArgs args) { - syncContext.throwIfNotInThisSynchronizationContext(); - return createSubchannelInternal(args); - } - - private SubchannelImpl createSubchannelInternal(CreateSubchannelArgs args) { - // TODO(ejona): can we be even stricter? Like loadBalancer == null? - checkState(!terminated, "Channel is terminated"); - return new SubchannelImpl(args, this); - } - - @Override - public void updateBalancingState( - final ConnectivityState newState, final SubchannelPicker newPicker) { - checkNotNull(newState, "newState"); - checkNotNull(newPicker, "newPicker"); - logWarningIfNotInSyncContext("updateBalancingState()"); - final class UpdateBalancingState implements Runnable { - @Override - public void run() { - if (LbHelperImpl.this != lbHelper) { - return; - } - updateSubchannelPicker(newPicker); - // It's not appropriate to report SHUTDOWN state from lb. - // Ignore the case of newState == SHUTDOWN for now. - if (newState != SHUTDOWN) { - channelLogger.log(ChannelLogLevel.INFO, "Entering {0} state", newState); - channelStateManager.gotoState(newState); - } - } - } - - syncContext.execute(new UpdateBalancingState()); - } - - @Override - public void refreshNameResolution() { - logWarningIfNotInSyncContext("refreshNameResolution()"); - final class LoadBalancerRefreshNameResolution implements Runnable { - @Override - public void run() { - refreshAndResetNameResolution(); - } - } - - syncContext.execute(new LoadBalancerRefreshNameResolution()); - } - - @Deprecated - @Override - public void updateSubchannelAddresses( - LoadBalancer.Subchannel subchannel, List addrs) { - checkArgument(subchannel instanceof SubchannelImpl, - "subchannel must have been returned from createSubchannel"); - logWarningIfNotInSyncContext("updateSubchannelAddresses()"); - ((InternalSubchannel) subchannel.getInternalSubchannel()).updateAddresses(addrs); - } - - @Override - public ManagedChannel createOobChannel(EquivalentAddressGroup addressGroup, String authority) { - // TODO(ejona): can we be even stricter? Like terminating? - checkState(!terminated, "Channel is terminated"); - long oobChannelCreationTime = timeProvider.currentTimeNanos(); - InternalLogId oobLogId = InternalLogId.allocate("OobChannel", /*details=*/ null); - InternalLogId subchannelLogId = - InternalLogId.allocate("Subchannel-OOB", /*details=*/ authority); - ChannelTracer oobChannelTracer = - new ChannelTracer( - oobLogId, maxTraceEvents, oobChannelCreationTime, - "OobChannel for " + addressGroup); - final OobChannel oobChannel = new OobChannel( - authority, balancerRpcExecutorPool, transportFactory.getScheduledExecutorService(), - syncContext, callTracerFactory.create(), oobChannelTracer, channelz, timeProvider); - channelTracer.reportEvent(new ChannelTrace.Event.Builder() - .setDescription("Child OobChannel created") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(oobChannelCreationTime) - .setChannelRef(oobChannel) - .build()); - ChannelTracer subchannelTracer = - new ChannelTracer(subchannelLogId, maxTraceEvents, oobChannelCreationTime, - "Subchannel for " + addressGroup); - ChannelLogger subchannelLogger = new ChannelLoggerImpl(subchannelTracer, timeProvider); - final class ManagedOobChannelCallback extends InternalSubchannel.Callback { - @Override - void onTerminated(InternalSubchannel is) { - oobChannels.remove(oobChannel); - channelz.removeSubchannel(is); - oobChannel.handleSubchannelTerminated(); - maybeTerminateChannel(); - } - - @Override - void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) { - handleInternalSubchannelState(newState); - oobChannel.handleSubchannelStateChange(newState); - } - } - - final InternalSubchannel internalSubchannel = new InternalSubchannel( - Collections.singletonList(addressGroup), - authority, userAgent, backoffPolicyProvider, transportFactory, - transportFactory.getScheduledExecutorService(), stopwatchSupplier, syncContext, - // All callback methods are run from syncContext - new ManagedOobChannelCallback(), - channelz, - callTracerFactory.create(), - subchannelTracer, - subchannelLogId, - subchannelLogger); - oobChannelTracer.reportEvent(new ChannelTrace.Event.Builder() - .setDescription("Child Subchannel created") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(oobChannelCreationTime) - .setSubchannelRef(internalSubchannel) - .build()); - channelz.addSubchannel(oobChannel); - channelz.addSubchannel(internalSubchannel); - oobChannel.setSubchannel(internalSubchannel); - final class AddOobChannel implements Runnable { - @Override - public void run() { - if (terminating) { - oobChannel.shutdown(); - } - if (!terminated) { - // If channel has not terminated, it will track the subchannel and block termination - // for it. - oobChannels.add(oobChannel); - } - } - } - - syncContext.execute(new AddOobChannel()); - return oobChannel; - } - - @Override - public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressGroup eag) { - checkArgument(channel instanceof OobChannel, - "channel must have been returned from createOobChannel"); - ((OobChannel) channel).updateAddresses(eag); - } - - @Override - public String getAuthority() { - return ManagedChannelImpl2.this.authority(); - } - - @Deprecated - @Override - public NameResolver.Factory getNameResolverFactory() { - return nameResolverFactory; - } - - @Override - public SynchronizationContext getSynchronizationContext() { - return syncContext; - } - - @Override - public ScheduledExecutorService getScheduledExecutorService() { - return scheduledExecutor; - } - - @Override - public ChannelLogger getChannelLogger() { - return channelLogger; - } - - @Override - public NameResolver.Args getNameResolverArgs() { - return nameResolverArgs; - } - - @Override - public NameResolverRegistry getNameResolverRegistry() { - return nameResolverRegistry; - } - } - - private final class NameResolverListener extends NameResolver.Listener2 { - final LbHelperImpl helper; - final NameResolver resolver; - - NameResolverListener(LbHelperImpl helperImpl, NameResolver resolver) { - this.helper = checkNotNull(helperImpl, "helperImpl"); - this.resolver = checkNotNull(resolver, "resolver"); - } - - @Override - public void onResult(final ResolutionResult resolutionResult) { - final class NamesResolved implements Runnable { - - @SuppressWarnings({"ReferenceEquality", "deprecation"}) - @Override - public void run() { - List servers = resolutionResult.getAddresses(); - Attributes attrs = resolutionResult.getAttributes(); - channelLogger.log( - ChannelLogLevel.DEBUG, "Resolved address: {0}, config={1}", servers, attrs); - ResolutionState lastResolutionStateCopy = lastResolutionState; - - if (lastResolutionState != ResolutionState.SUCCESS) { - channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); - lastResolutionState = ResolutionState.SUCCESS; - } - - nameResolverBackoffPolicy = null; - ConfigOrError configOrError = resolutionResult.getServiceConfig(); - ServiceConfigHolder validServiceConfig = null; - Status serviceConfigError = null; - if (configOrError != null) { - Map rawServiceConfig = - resolutionResult.getAttributes().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG); - validServiceConfig = configOrError.getConfig() == null - ? null - : new ServiceConfigHolder( - rawServiceConfig, (ManagedChannelServiceConfig2) configOrError.getConfig()); - serviceConfigError = configOrError.getError(); - } - - ServiceConfigHolder effectiveServiceConfig; - if (!lookUpServiceConfig) { - if (validServiceConfig != null) { - channelLogger.log( - ChannelLogLevel.INFO, - "Service config from name resolver discarded by channel settings"); - } - effectiveServiceConfig = - defaultServiceConfig == null ? EMPTY_SERVICE_CONFIG : defaultServiceConfig; - attrs = attrs.toBuilder().discard(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG).build(); - } else { - // Try to use config if returned from name resolver - // Otherwise, try to use the default config if available - if (validServiceConfig != null) { - effectiveServiceConfig = validServiceConfig; - } else if (defaultServiceConfig != null) { - effectiveServiceConfig = defaultServiceConfig; - channelLogger.log( - ChannelLogLevel.INFO, - "Received no service config, using default service config"); - } else if (serviceConfigError != null) { - if (!serviceConfigUpdated) { - // First DNS lookup has invalid service config, and cannot fall back to default - channelLogger.log( - ChannelLogLevel.INFO, - "Fallback to error due to invalid first service config without default config"); - onError(configOrError.getError()); - return; - } else { - effectiveServiceConfig = lastServiceConfig; - } - } else { - effectiveServiceConfig = EMPTY_SERVICE_CONFIG; - } - if (!effectiveServiceConfig.equals(lastServiceConfig)) { - channelLogger.log( - ChannelLogLevel.INFO, - "Service config changed{0}", - effectiveServiceConfig == EMPTY_SERVICE_CONFIG ? " to empty" : ""); - lastServiceConfig = effectiveServiceConfig; - } - - try { - // TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS - // and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But, - // lbNeedAddress is not deterministic - handleServiceConfigUpdate(); - } catch (RuntimeException re) { - logger.log( - Level.WARNING, - "[" + getLogId() + "] Unexpected exception from parsing service config", - re); - } - } - - // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. - if (NameResolverListener.this.helper == ManagedChannelImpl2.this.lbHelper) { - Attributes effectiveAttrs = attrs; - if (effectiveServiceConfig != validServiceConfig) { - effectiveAttrs = attrs.toBuilder() - .set( - GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, - effectiveServiceConfig.rawServiceConfig) - .build(); - } - - Status handleResult = helper.lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(effectiveAttrs) - .setLoadBalancingPolicyConfig( - effectiveServiceConfig.managedChannelServiceConfig.getLoadBalancingConfig()) - .build()); - - if (!handleResult.isOk()) { - if (servers.isEmpty() && lastResolutionStateCopy == ResolutionState.SUCCESS) { - // lb doesn't expose that it needs address or not, because for some LB it is not - // deterministic. Assuming lb needs address if LB returns error when the address is - // empty and it is not the first resolution. - scheduleExponentialBackOffInSyncContext(); - } else { - handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used")); - } - } - } - } - } - - syncContext.execute(new NamesResolved()); - } - - @Override - public void onError(final Status error) { - checkArgument(!error.isOk(), "the error status must not be OK"); - final class NameResolverErrorHandler implements Runnable { - @Override - public void run() { - handleErrorInSyncContext(error); - } - } - - syncContext.execute(new NameResolverErrorHandler()); - } - - private void handleErrorInSyncContext(Status error) { - logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}", - new Object[] {getLogId(), error}); - if (lastResolutionState != ResolutionState.ERROR) { - channelLogger.log(ChannelLogLevel.WARNING, "Failed to resolve name: {0}", error); - lastResolutionState = ResolutionState.ERROR; - } - // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. - if (NameResolverListener.this.helper != ManagedChannelImpl2.this.lbHelper) { - return; - } - - helper.lb.handleNameResolutionError(error); - - scheduleExponentialBackOffInSyncContext(); - } - - private void scheduleExponentialBackOffInSyncContext() { - if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh.isPending()) { - // The name resolver may invoke onError multiple times, but we only want to - // schedule one backoff attempt - // TODO(ericgribkoff) Update contract of NameResolver.Listener or decide if we - // want to reset the backoff interval upon repeated onError() calls - return; - } - if (nameResolverBackoffPolicy == null) { - nameResolverBackoffPolicy = backoffPolicyProvider.get(); - } - long delayNanos = nameResolverBackoffPolicy.nextBackoffNanos(); - channelLogger.log( - ChannelLogLevel.DEBUG, - "Scheduling DNS resolution backoff for {0} ns", delayNanos); - scheduledNameResolverRefresh = - syncContext.schedule( - new DelayedNameResolverRefresh(), delayNanos, TimeUnit.NANOSECONDS, - transportFactory .getScheduledExecutorService()); - } - } - - private final class SubchannelImpl extends AbstractSubchannel { - final CreateSubchannelArgs args; - final LbHelperImpl helper; - final InternalLogId subchannelLogId; - final ChannelLoggerImpl subchannelLogger; - final ChannelTracer subchannelTracer; - SubchannelStateListener listener; - InternalSubchannel subchannel; - boolean started; - boolean shutdown; - ScheduledHandle delayedShutdownTask; - - SubchannelImpl(CreateSubchannelArgs args, LbHelperImpl helper) { - this.args = checkNotNull(args, "args"); - this.helper = checkNotNull(helper, "helper"); - subchannelLogId = InternalLogId.allocate("Subchannel", /*details=*/ authority()); - subchannelTracer = new ChannelTracer( - subchannelLogId, maxTraceEvents, timeProvider.currentTimeNanos(), - "Subchannel for " + args.getAddresses()); - subchannelLogger = new ChannelLoggerImpl(subchannelTracer, timeProvider); - } - - // This can be called either in or outside of syncContext - // TODO(zhangkun83): merge it back into start() once the caller createSubchannel() is deleted. - private void internalStart(final SubchannelStateListener listener) { - checkState(!started, "already started"); - checkState(!shutdown, "already shutdown"); - started = true; - this.listener = listener; - // TODO(zhangkun): possibly remove the volatile of terminating when this whole method is - // required to be called from syncContext - if (terminating) { - syncContext.execute(new Runnable() { - @Override - public void run() { - listener.onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); - } - }); - return; - } - final class ManagedInternalSubchannelCallback extends InternalSubchannel.Callback { - // All callbacks are run in syncContext - @Override - void onTerminated(InternalSubchannel is) { - subchannels.remove(is); - channelz.removeSubchannel(is); - maybeTerminateChannel(); - } - - @Override - void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) { - handleInternalSubchannelState(newState); - checkState(listener != null, "listener is null"); - listener.onSubchannelState(newState); - } - - @Override - void onInUse(InternalSubchannel is) { - inUseStateAggregator.updateObjectInUse(is, true); - } - - @Override - void onNotInUse(InternalSubchannel is) { - inUseStateAggregator.updateObjectInUse(is, false); - } - } - - final InternalSubchannel internalSubchannel = new InternalSubchannel( - args.getAddresses(), - authority(), - userAgent, - backoffPolicyProvider, - transportFactory, - transportFactory.getScheduledExecutorService(), - stopwatchSupplier, - syncContext, - new ManagedInternalSubchannelCallback(), - channelz, - callTracerFactory.create(), - subchannelTracer, - subchannelLogId, - subchannelLogger); - - channelTracer.reportEvent(new ChannelTrace.Event.Builder() - .setDescription("Child Subchannel started") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timeProvider.currentTimeNanos()) - .setSubchannelRef(internalSubchannel) - .build()); - - this.subchannel = internalSubchannel; - // TODO(zhangkun83): no need to schedule on syncContext when this whole method is required - // to be called from syncContext - syncContext.execute(new Runnable() { - @Override - public void run() { - channelz.addSubchannel(internalSubchannel); - subchannels.add(internalSubchannel); - } - }); - } - - @Override - public void start(SubchannelStateListener listener) { - syncContext.throwIfNotInThisSynchronizationContext(); - internalStart(listener); - } - - @Override - InternalInstrumented getInstrumentedInternalSubchannel() { - checkState(started, "not started"); - return subchannel; - } - - @Override - public void shutdown() { - // TODO(zhangkun83): replace shutdown() with internalShutdown() to turn the warning into an - // exception. - logWarningIfNotInSyncContext("Subchannel.shutdown()"); - syncContext.execute(new Runnable() { - @Override - public void run() { - internalShutdown(); - } - }); - } - - private void internalShutdown() { - syncContext.throwIfNotInThisSynchronizationContext(); - if (subchannel == null) { - // start() was not successful - shutdown = true; - return; - } - if (shutdown) { - if (terminating && delayedShutdownTask != null) { - // shutdown() was previously called when terminating == false, thus a delayed shutdown() - // was scheduled. Now since terminating == true, We should expedite the shutdown. - delayedShutdownTask.cancel(); - delayedShutdownTask = null; - // Will fall through to the subchannel.shutdown() at the end. - } else { - return; - } - } else { - shutdown = true; - } - // Add a delay to shutdown to deal with the race between 1) a transport being picked and - // newStream() being called on it, and 2) its Subchannel is shut down by LoadBalancer (e.g., - // because of address change, or because LoadBalancer is shutdown by Channel entering idle - // mode). If (2) wins, the app will see a spurious error. We work around this by delaying - // shutdown of Subchannel for a few seconds here. - // - // TODO(zhangkun83): consider a better approach - // (https://github.com/grpc/grpc-java/issues/2562). - if (!terminating) { - final class ShutdownSubchannel implements Runnable { - @Override - public void run() { - subchannel.shutdown(SUBCHANNEL_SHUTDOWN_STATUS); - } - } - - delayedShutdownTask = syncContext.schedule( - new LogExceptionRunnable(new ShutdownSubchannel()), - SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS, - transportFactory.getScheduledExecutorService()); - return; - } - // When terminating == true, no more real streams will be created. It's safe and also - // desirable to shutdown timely. - subchannel.shutdown(SHUTDOWN_STATUS); - } - - @Override - public void requestConnection() { - logWarningIfNotInSyncContext("Subchannel.requestConnection()"); - checkState(started, "not started"); - subchannel.obtainActiveTransport(); - } - - @Override - public List getAllAddresses() { - logWarningIfNotInSyncContext("Subchannel.getAllAddresses()"); - checkState(started, "not started"); - return subchannel.getAddressGroups(); - } - - @Override - public Attributes getAttributes() { - return args.getAttributes(); - } - - @Override - public String toString() { - return subchannelLogId.toString(); - } - - @Override - public Channel asChannel() { - checkState(started, "not started"); - return new SubchannelChannel( - subchannel, balancerRpcExecutorHolder.getExecutor(), - transportFactory.getScheduledExecutorService(), - callTracerFactory.create()); - } - - @Override - public Object getInternalSubchannel() { - checkState(started, "Subchannel is not started"); - return subchannel; - } - - @Override - public ChannelLogger getChannelLogger() { - return subchannelLogger; - } - - @Override - public void updateAddresses(List addrs) { - syncContext.throwIfNotInThisSynchronizationContext(); - subchannel.updateAddresses(addrs); - } - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("logId", logId.getId()) - .add("target", target) - .toString(); - } - - /** - * Called from syncContext. - */ - private final class DelayedTransportListener implements ManagedClientTransport.Listener { - @Override - public void transportShutdown(Status s) { - checkState(shutdown.get(), "Channel must have been shut down"); - } - - @Override - public void transportReady() { - // Don't care - } - - @Override - public void transportInUse(final boolean inUse) { - inUseStateAggregator.updateObjectInUse(delayedTransport, inUse); - } - - @Override - public void transportTerminated() { - checkState(shutdown.get(), "Channel must have been shut down"); - terminating = true; - shutdownNameResolverAndLoadBalancer(false); - // No need to call channelStateManager since we are already in SHUTDOWN state. - // Until LoadBalancer is shutdown, it may still create new subchannels. We catch them - // here. - maybeShutdownNowSubchannels(); - maybeTerminateChannel(); - } - } - - /** - * Must be accessed from syncContext. - */ - private final class IdleModeStateAggregator extends InUseStateAggregator { - @Override - protected void handleInUse() { - exitIdleMode(); - } - - @Override - protected void handleNotInUse() { - if (shutdown.get()) { - return; - } - rescheduleIdleTimer(); - } - } - - /** - * Lazily request for Executor from an executor pool. - */ - private static final class ExecutorHolder { - private final ObjectPool pool; - private Executor executor; - - ExecutorHolder(ObjectPool executorPool) { - this.pool = checkNotNull(executorPool, "executorPool"); - } - - synchronized Executor getExecutor() { - if (executor == null) { - executor = checkNotNull(pool.getObject(), "%s.getObject()", executor); - } - return executor; - } - - synchronized void release() { - if (executor != null) { - executor = pool.returnObject(executor); - } - } - } - - private static final class RestrictedScheduledExecutor implements ScheduledExecutorService { - final ScheduledExecutorService delegate; - - private RestrictedScheduledExecutor(ScheduledExecutorService delegate) { - this.delegate = checkNotNull(delegate, "delegate"); - } - - @Override - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - return delegate.schedule(callable, delay, unit); - } - - @Override - public ScheduledFuture schedule(Runnable cmd, long delay, TimeUnit unit) { - return delegate.schedule(cmd, delay, unit); - } - - @Override - public ScheduledFuture scheduleAtFixedRate( - Runnable command, long initialDelay, long period, TimeUnit unit) { - return delegate.scheduleAtFixedRate(command, initialDelay, period, unit); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay( - Runnable command, long initialDelay, long delay, TimeUnit unit) { - return delegate.scheduleWithFixedDelay(command, initialDelay, delay, unit); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) - throws InterruptedException { - return delegate.awaitTermination(timeout, unit); - } - - @Override - public List> invokeAll(Collection> tasks) - throws InterruptedException { - return delegate.invokeAll(tasks); - } - - @Override - public List> invokeAll( - Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException { - return delegate.invokeAll(tasks, timeout, unit); - } - - @Override - public T invokeAny(Collection> tasks) - throws InterruptedException, ExecutionException { - return delegate.invokeAny(tasks); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return delegate.invokeAny(tasks, timeout, unit); - } - - @Override - public boolean isShutdown() { - return delegate.isShutdown(); - } - - @Override - public boolean isTerminated() { - return delegate.isTerminated(); - } - - @Override - public void shutdown() { - throw new UnsupportedOperationException("Restricted: shutdown() is not allowed"); - } - - @Override - public List shutdownNow() { - throw new UnsupportedOperationException("Restricted: shutdownNow() is not allowed"); - } - - @Override - public Future submit(Callable task) { - return delegate.submit(task); - } - - @Override - public Future submit(Runnable task) { - return delegate.submit(task); - } - - @Override - public Future submit(Runnable task, T result) { - return delegate.submit(task, result); - } - - @Override - public void execute(Runnable command) { - delegate.execute(command); - } - } - - @VisibleForTesting - static final class ScParser extends NameResolver.ServiceConfigParser { - - private final boolean retryEnabled; - private final int maxRetryAttemptsLimit; - private final int maxHedgedAttemptsLimit; - private final AutoConfiguredLoadBalancerFactory2 autoLoadBalancerFactory; - private final ChannelLogger channelLogger; - - ScParser( - boolean retryEnabled, - int maxRetryAttemptsLimit, - int maxHedgedAttemptsLimit, - AutoConfiguredLoadBalancerFactory2 autoLoadBalancerFactory, - ChannelLogger channelLogger) { - this.retryEnabled = retryEnabled; - this.maxRetryAttemptsLimit = maxRetryAttemptsLimit; - this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit; - this.autoLoadBalancerFactory = - checkNotNull(autoLoadBalancerFactory, "autoLoadBalancerFactory"); - this.channelLogger = checkNotNull(channelLogger, "channelLogger"); - } - - @Override - public ConfigOrError parseServiceConfig(Map rawServiceConfig) { - try { - Object loadBalancingPolicySelection; - ConfigOrError choiceFromLoadBalancer = - autoLoadBalancerFactory.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); - if (choiceFromLoadBalancer == null) { - loadBalancingPolicySelection = null; - } else if (choiceFromLoadBalancer.getError() != null) { - return ConfigOrError.fromError(choiceFromLoadBalancer.getError()); - } else { - loadBalancingPolicySelection = choiceFromLoadBalancer.getConfig(); - } - return ConfigOrError.fromConfig( - ManagedChannelServiceConfig2.fromServiceConfig( - rawServiceConfig, - retryEnabled, - maxRetryAttemptsLimit, - maxHedgedAttemptsLimit, - loadBalancingPolicySelection)); - } catch (RuntimeException e) { - return ConfigOrError.fromError( - Status.UNKNOWN.withDescription("failed to parse service config").withCause(e)); - } - } - } - - private void logWarningIfNotInSyncContext(String method) { - try { - syncContext.throwIfNotInThisSynchronizationContext(); - } catch (IllegalStateException e) { - logger.log(Level.WARNING, - method + " should be called from SynchronizationContext. " - + "This warning will become an exception in a future release. " - + "See https://github.com/grpc/grpc-java/issues/5015 for more details", e); - } - } - - /** - * A ResolutionState indicates the status of last name resolution. - */ - enum ResolutionState { - NO_RESOLUTION, - SUCCESS, - ERROR - } - - // TODO(creamsoup) remove this class when AutoConfiguredLoadBalancerFactory doesn't require raw - // service config. - private static final class ServiceConfigHolder { - Map rawServiceConfig; - ManagedChannelServiceConfig2 managedChannelServiceConfig; - - ServiceConfigHolder( - Map rawServiceConfig, ManagedChannelServiceConfig2 managedChannelServiceConfig) { - this.rawServiceConfig = checkNotNull(rawServiceConfig, "rawServiceConfig"); - this.managedChannelServiceConfig = - checkNotNull(managedChannelServiceConfig, "managedChannelServiceConfig"); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ServiceConfigHolder that = (ServiceConfigHolder) o; - return Objects.equal(rawServiceConfig, that.rawServiceConfig) - && Objects - .equal(managedChannelServiceConfig, that.managedChannelServiceConfig); - } - - @Override - public int hashCode() { - return Objects.hashCode(rawServiceConfig, managedChannelServiceConfig); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("rawServiceConfig", rawServiceConfig) - .add("managedChannelServiceConfig", managedChannelServiceConfig) - .toString(); - } - } -} diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java b/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java index e01bb1b524..32b9433b1e 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java @@ -39,9 +39,7 @@ final class ManagedChannelServiceConfig { private final Map serviceMethodMap; private final Map serviceMap; - // TODO(notcarl/zdapeng): use retryThrottling here @Nullable - @SuppressWarnings("unused") private final Throttle retryThrottling; @Nullable private final Object loadBalancingConfig; @@ -57,6 +55,16 @@ final class ManagedChannelServiceConfig { this.loadBalancingConfig = loadBalancingConfig; } + /** Returns an empty {@link ManagedChannelServiceConfig}. */ + static ManagedChannelServiceConfig empty() { + return + new ManagedChannelServiceConfig( + new HashMap(), + new HashMap(), + /* retryThrottling= */ null, + /* loadBalancingConfig= */ null); + } + /** * Parses the Channel level config values (e.g. excludes load balancing) */ @@ -138,6 +146,41 @@ final class ManagedChannelServiceConfig { return loadBalancingConfig; } + @Nullable + Throttle getRetryThrottling() { + return retryThrottling; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ManagedChannelServiceConfig that = (ManagedChannelServiceConfig) o; + return Objects.equal(serviceMethodMap, that.serviceMethodMap) + && Objects.equal(serviceMap, that.serviceMap) + && Objects.equal(retryThrottling, that.retryThrottling) + && Objects.equal(loadBalancingConfig, that.loadBalancingConfig); + } + + @Override + public int hashCode() { + return Objects.hashCode(serviceMethodMap, serviceMap, retryThrottling, loadBalancingConfig); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("serviceMethodMap", serviceMethodMap) + .add("serviceMap", serviceMap) + .add("retryThrottling", retryThrottling) + .add("loadBalancingConfig", loadBalancingConfig) + .toString(); + } + /** * Equivalent of MethodConfig from a ServiceConfig with restrictions from Channel setting. */ diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig2.java b/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig2.java deleted file mode 100644 index e20336215c..0000000000 --- a/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig2.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * 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.internal; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import com.google.common.base.Strings; -import io.grpc.MethodDescriptor; -import io.grpc.internal.RetriableStream.Throttle; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.Nullable; - -/** - * {@link ManagedChannelServiceConfig2} is a fully parsed and validated representation of service - * configuration data. - */ -final class ManagedChannelServiceConfig2 { - - private final Map serviceMethodMap; - private final Map serviceMap; - @Nullable - private final Throttle retryThrottling; - @Nullable - private final Object loadBalancingConfig; - - ManagedChannelServiceConfig2( - Map serviceMethodMap, - Map serviceMap, - @Nullable Throttle retryThrottling, - @Nullable Object loadBalancingConfig) { - this.serviceMethodMap = Collections.unmodifiableMap(new HashMap<>(serviceMethodMap)); - this.serviceMap = Collections.unmodifiableMap(new HashMap<>(serviceMap)); - this.retryThrottling = retryThrottling; - this.loadBalancingConfig = loadBalancingConfig; - } - - /** Returns an empty {@link ManagedChannelServiceConfig2}. */ - static ManagedChannelServiceConfig2 empty() { - return - new ManagedChannelServiceConfig2( - new HashMap(), - new HashMap(), - /* retryThrottling= */ null, - /* loadBalancingConfig= */ null); - } - - /** - * Parses the Channel level config values (e.g. excludes load balancing) - */ - static ManagedChannelServiceConfig2 fromServiceConfig( - Map serviceConfig, - boolean retryEnabled, - int maxRetryAttemptsLimit, - int maxHedgedAttemptsLimit, - @Nullable Object loadBalancingConfig) { - Throttle retryThrottling = null; - if (retryEnabled) { - retryThrottling = ServiceConfigUtil.getThrottlePolicy(serviceConfig); - } - Map serviceMethodMap = new HashMap<>(); - Map serviceMap = new HashMap<>(); - - // Try and do as much validation here before we swap out the existing configuration. In case - // the input is invalid, we don't want to lose the existing configuration. - List> methodConfigs = - ServiceConfigUtil.getMethodConfigFromServiceConfig(serviceConfig); - - if (methodConfigs == null) { - // this is surprising, but possible. - return new ManagedChannelServiceConfig2( - serviceMethodMap, serviceMap, retryThrottling, loadBalancingConfig); - } - - for (Map methodConfig : methodConfigs) { - MethodInfo info = new MethodInfo( - methodConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit); - - List> nameList = - ServiceConfigUtil.getNameListFromMethodConfig(methodConfig); - - checkArgument( - nameList != null && !nameList.isEmpty(), "no names in method config %s", methodConfig); - for (Map name : nameList) { - String serviceName = ServiceConfigUtil.getServiceFromName(name); - checkArgument(!Strings.isNullOrEmpty(serviceName), "missing service name"); - String methodName = ServiceConfigUtil.getMethodFromName(name); - if (Strings.isNullOrEmpty(methodName)) { - // Service scoped config - checkArgument( - !serviceMap.containsKey(serviceName), "Duplicate service %s", serviceName); - serviceMap.put(serviceName, info); - } else { - // Method scoped config - String fullMethodName = MethodDescriptor.generateFullMethodName(serviceName, methodName); - checkArgument( - !serviceMethodMap.containsKey(fullMethodName), - "Duplicate method name %s", - fullMethodName); - serviceMethodMap.put(fullMethodName, info); - } - } - } - - return new ManagedChannelServiceConfig2( - serviceMethodMap, serviceMap, retryThrottling, loadBalancingConfig); - } - - /** - * Returns the per-service configuration for the channel. - */ - Map getServiceMap() { - return serviceMap; - } - - /** - * Returns the per-method configuration for the channel. - */ - Map getServiceMethodMap() { - return serviceMethodMap; - } - - @VisibleForTesting - @Nullable - Object getLoadBalancingConfig() { - return loadBalancingConfig; - } - - @Nullable - Throttle getRetryThrottling() { - return retryThrottling; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ManagedChannelServiceConfig2 that = (ManagedChannelServiceConfig2) o; - return Objects.equal(serviceMethodMap, that.serviceMethodMap) - && Objects.equal(serviceMap, that.serviceMap) - && Objects.equal(retryThrottling, that.retryThrottling) - && Objects.equal(loadBalancingConfig, that.loadBalancingConfig); - } - - @Override - public int hashCode() { - return Objects.hashCode(serviceMethodMap, serviceMap, retryThrottling, loadBalancingConfig); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("serviceMethodMap", serviceMethodMap) - .add("serviceMap", serviceMap) - .add("retryThrottling", retryThrottling) - .add("loadBalancingConfig", loadBalancingConfig) - .toString(); - } - - /** - * Equivalent of MethodConfig from a ServiceConfig with restrictions from Channel setting. - */ - static final class MethodInfo { - // TODO(carl-mastrangelo): add getters for these fields and make them private. - final Long timeoutNanos; - final Boolean waitForReady; - final Integer maxInboundMessageSize; - final Integer maxOutboundMessageSize; - final RetryPolicy retryPolicy; - final HedgingPolicy hedgingPolicy; - - /** - * Constructor. - * - * @param retryEnabled when false, the argument maxRetryAttemptsLimit will have no effect. - */ - MethodInfo( - Map methodConfig, boolean retryEnabled, int maxRetryAttemptsLimit, - int maxHedgedAttemptsLimit) { - timeoutNanos = ServiceConfigUtil.getTimeoutFromMethodConfig(methodConfig); - waitForReady = ServiceConfigUtil.getWaitForReadyFromMethodConfig(methodConfig); - maxInboundMessageSize = - ServiceConfigUtil.getMaxResponseMessageBytesFromMethodConfig(methodConfig); - if (maxInboundMessageSize != null) { - checkArgument( - maxInboundMessageSize >= 0, - "maxInboundMessageSize %s exceeds bounds", maxInboundMessageSize); - } - maxOutboundMessageSize = - ServiceConfigUtil.getMaxRequestMessageBytesFromMethodConfig(methodConfig); - if (maxOutboundMessageSize != null) { - checkArgument( - maxOutboundMessageSize >= 0, - "maxOutboundMessageSize %s exceeds bounds", maxOutboundMessageSize); - } - - Map retryPolicyMap = - retryEnabled ? ServiceConfigUtil.getRetryPolicyFromMethodConfig(methodConfig) : null; - retryPolicy = retryPolicyMap == null - ? RetryPolicy.DEFAULT : retryPolicy(retryPolicyMap, maxRetryAttemptsLimit); - - Map hedgingPolicyMap = - retryEnabled ? ServiceConfigUtil.getHedgingPolicyFromMethodConfig(methodConfig) : null; - hedgingPolicy = hedgingPolicyMap == null - ? HedgingPolicy.DEFAULT : hedgingPolicy(hedgingPolicyMap, maxHedgedAttemptsLimit); - } - - @Override - public int hashCode() { - return Objects.hashCode( - timeoutNanos, - waitForReady, - maxInboundMessageSize, - maxOutboundMessageSize, - retryPolicy, - hedgingPolicy); - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof MethodInfo)) { - return false; - } - MethodInfo that = (MethodInfo) other; - return Objects.equal(this.timeoutNanos, that.timeoutNanos) - && Objects.equal(this.waitForReady, that.waitForReady) - && Objects.equal(this.maxInboundMessageSize, that.maxInboundMessageSize) - && Objects.equal(this.maxOutboundMessageSize, that.maxOutboundMessageSize) - && Objects.equal(this.retryPolicy, that.retryPolicy) - && Objects.equal(this.hedgingPolicy, that.hedgingPolicy); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("timeoutNanos", timeoutNanos) - .add("waitForReady", waitForReady) - .add("maxInboundMessageSize", maxInboundMessageSize) - .add("maxOutboundMessageSize", maxOutboundMessageSize) - .add("retryPolicy", retryPolicy) - .add("hedgingPolicy", hedgingPolicy) - .toString(); - } - - private static RetryPolicy retryPolicy(Map retryPolicy, int maxAttemptsLimit) { - int maxAttempts = checkNotNull( - ServiceConfigUtil.getMaxAttemptsFromRetryPolicy(retryPolicy), - "maxAttempts cannot be empty"); - checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts); - maxAttempts = Math.min(maxAttempts, maxAttemptsLimit); - - long initialBackoffNanos = checkNotNull( - ServiceConfigUtil.getInitialBackoffNanosFromRetryPolicy(retryPolicy), - "initialBackoff cannot be empty"); - checkArgument( - initialBackoffNanos > 0, - "initialBackoffNanos must be greater than 0: %s", - initialBackoffNanos); - - long maxBackoffNanos = checkNotNull( - ServiceConfigUtil.getMaxBackoffNanosFromRetryPolicy(retryPolicy), - "maxBackoff cannot be empty"); - checkArgument( - maxBackoffNanos > 0, "maxBackoff must be greater than 0: %s", maxBackoffNanos); - - double backoffMultiplier = checkNotNull( - ServiceConfigUtil.getBackoffMultiplierFromRetryPolicy(retryPolicy), - "backoffMultiplier cannot be empty"); - checkArgument( - backoffMultiplier > 0, - "backoffMultiplier must be greater than 0: %s", - backoffMultiplier); - - return new RetryPolicy( - maxAttempts, initialBackoffNanos, maxBackoffNanos, backoffMultiplier, - ServiceConfigUtil.getRetryableStatusCodesFromRetryPolicy(retryPolicy)); - } - - private static HedgingPolicy hedgingPolicy( - Map hedgingPolicy, int maxAttemptsLimit) { - int maxAttempts = checkNotNull( - ServiceConfigUtil.getMaxAttemptsFromHedgingPolicy(hedgingPolicy), - "maxAttempts cannot be empty"); - checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts); - maxAttempts = Math.min(maxAttempts, maxAttemptsLimit); - - long hedgingDelayNanos = checkNotNull( - ServiceConfigUtil.getHedgingDelayNanosFromHedgingPolicy(hedgingPolicy), - "hedgingDelay cannot be empty"); - checkArgument( - hedgingDelayNanos >= 0, "hedgingDelay must not be negative: %s", hedgingDelayNanos); - - return new HedgingPolicy( - maxAttempts, hedgingDelayNanos, - ServiceConfigUtil.getNonFatalStatusCodesFromHedgingPolicy(hedgingPolicy)); - } - } -} diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java index f33eaf39f7..f27f9efa78 100644 --- a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java +++ b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java @@ -26,8 +26,6 @@ import io.grpc.ClientInterceptor; import io.grpc.Deadline; import io.grpc.MethodDescriptor; import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.CheckForNull; @@ -40,34 +38,20 @@ final class ServiceConfigInterceptor implements ClientInterceptor { // Map from method name to MethodInfo @VisibleForTesting - final AtomicReference managedChannelServiceConfig - = new AtomicReference<>(); + final AtomicReference managedChannelServiceConfig = + new AtomicReference<>(); private final boolean retryEnabled; - private final int maxRetryAttemptsLimit; - private final int maxHedgedAttemptsLimit; // Setting this to true and observing this equal to true are run in different threads. private volatile boolean initComplete; - ServiceConfigInterceptor( - boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit) { + ServiceConfigInterceptor(boolean retryEnabled) { this.retryEnabled = retryEnabled; - this.maxRetryAttemptsLimit = maxRetryAttemptsLimit; - this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit; } - void handleUpdate(@Nullable Map serviceConfig) { - // TODO(carl-mastrangelo): delete this. - ManagedChannelServiceConfig conf; - if (serviceConfig == null) { - conf = new ManagedChannelServiceConfig( - new HashMap(), new HashMap(), null, null); - } else { - conf = ManagedChannelServiceConfig.fromServiceConfig( - serviceConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit, null); - } - managedChannelServiceConfig.set(conf); + void handleUpdate(@Nullable ManagedChannelServiceConfig serviceConfig) { + managedChannelServiceConfig.set(serviceConfig); initComplete = true; } diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor2.java b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor2.java deleted file mode 100644 index bd1476d4b4..0000000000 --- a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor2.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2018 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.internal; - -import static com.google.common.base.Verify.verify; - -import com.google.common.annotations.VisibleForTesting; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.Deadline; -import io.grpc.MethodDescriptor; -import io.grpc.internal.ManagedChannelServiceConfig2.MethodInfo; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -/** - * Modifies RPCs in conformance with a Service Config. - */ -final class ServiceConfigInterceptor2 implements ClientInterceptor { - - // Map from method name to MethodInfo - @VisibleForTesting - final AtomicReference managedChannelServiceConfig = - new AtomicReference<>(); - - private final boolean retryEnabled; - - // Setting this to true and observing this equal to true are run in different threads. - private volatile boolean initComplete; - - ServiceConfigInterceptor2(boolean retryEnabled) { - this.retryEnabled = retryEnabled; - } - - void handleUpdate(@Nullable ManagedChannelServiceConfig2 serviceConfig) { - managedChannelServiceConfig.set(serviceConfig); - initComplete = true; - } - - static final CallOptions.Key RETRY_POLICY_KEY = - CallOptions.Key.create("internal-retry-policy"); - static final CallOptions.Key HEDGING_POLICY_KEY = - CallOptions.Key.create("internal-hedging-policy"); - - @Override - public ClientCall interceptCall( - final MethodDescriptor method, CallOptions callOptions, Channel next) { - if (retryEnabled) { - if (initComplete) { - final RetryPolicy retryPolicy = getRetryPolicyFromConfig(method); - final class ImmediateRetryPolicyProvider implements RetryPolicy.Provider { - @Override - public RetryPolicy get() { - return retryPolicy; - } - } - - final HedgingPolicy hedgingPolicy = getHedgingPolicyFromConfig(method); - final class ImmediateHedgingPolicyProvider implements HedgingPolicy.Provider { - @Override - public HedgingPolicy get() { - return hedgingPolicy; - } - } - - verify( - retryPolicy.equals(RetryPolicy.DEFAULT) || hedgingPolicy.equals(HedgingPolicy.DEFAULT), - "Can not apply both retry and hedging policy for the method '%s'", method); - - callOptions = callOptions - .withOption(RETRY_POLICY_KEY, new ImmediateRetryPolicyProvider()) - .withOption(HEDGING_POLICY_KEY, new ImmediateHedgingPolicyProvider()); - } else { - final class DelayedRetryPolicyProvider implements RetryPolicy.Provider { - /** - * Returns RetryPolicy.DEFAULT if name resolving is not complete at the moment the method - * is invoked, otherwise returns the RetryPolicy computed from service config. - * - *

Note that this method is used no more than once for each call. - */ - @Override - public RetryPolicy get() { - if (!initComplete) { - return RetryPolicy.DEFAULT; - } - return getRetryPolicyFromConfig(method); - } - } - - final class DelayedHedgingPolicyProvider implements HedgingPolicy.Provider { - /** - * Returns HedgingPolicy.DEFAULT if name resolving is not complete at the moment the - * method is invoked, otherwise returns the HedgingPolicy computed from service config. - * - *

Note that this method is used no more than once for each call. - */ - @Override - public HedgingPolicy get() { - if (!initComplete) { - return HedgingPolicy.DEFAULT; - } - HedgingPolicy hedgingPolicy = getHedgingPolicyFromConfig(method); - verify( - hedgingPolicy.equals(HedgingPolicy.DEFAULT) - || getRetryPolicyFromConfig(method).equals(RetryPolicy.DEFAULT), - "Can not apply both retry and hedging policy for the method '%s'", method); - return hedgingPolicy; - } - } - - callOptions = callOptions - .withOption(RETRY_POLICY_KEY, new DelayedRetryPolicyProvider()) - .withOption(HEDGING_POLICY_KEY, new DelayedHedgingPolicyProvider()); - } - } - - MethodInfo info = getMethodInfo(method); - if (info == null) { - return next.newCall(method, callOptions); - } - - if (info.timeoutNanos != null) { - Deadline newDeadline = Deadline.after(info.timeoutNanos, TimeUnit.NANOSECONDS); - Deadline existingDeadline = callOptions.getDeadline(); - // If the new deadline is sooner than the existing deadline, swap them. - if (existingDeadline == null || newDeadline.compareTo(existingDeadline) < 0) { - callOptions = callOptions.withDeadline(newDeadline); - } - } - if (info.waitForReady != null) { - callOptions = - info.waitForReady ? callOptions.withWaitForReady() : callOptions.withoutWaitForReady(); - } - if (info.maxInboundMessageSize != null) { - Integer existingLimit = callOptions.getMaxInboundMessageSize(); - if (existingLimit != null) { - callOptions = callOptions.withMaxInboundMessageSize( - Math.min(existingLimit, info.maxInboundMessageSize)); - } else { - callOptions = callOptions.withMaxInboundMessageSize(info.maxInboundMessageSize); - } - } - if (info.maxOutboundMessageSize != null) { - Integer existingLimit = callOptions.getMaxOutboundMessageSize(); - if (existingLimit != null) { - callOptions = callOptions.withMaxOutboundMessageSize( - Math.min(existingLimit, info.maxOutboundMessageSize)); - } else { - callOptions = callOptions.withMaxOutboundMessageSize(info.maxOutboundMessageSize); - } - } - - return next.newCall(method, callOptions); - } - - @CheckForNull - private MethodInfo getMethodInfo(MethodDescriptor method) { - ManagedChannelServiceConfig2 mcsc = managedChannelServiceConfig.get(); - MethodInfo info = null; - if (mcsc != null) { - info = mcsc.getServiceMethodMap().get(method.getFullMethodName()); - } - if (info == null && mcsc != null) { - String serviceName = method.getServiceName(); - info = mcsc.getServiceMap().get(serviceName); - } - return info; - } - - @VisibleForTesting - RetryPolicy getRetryPolicyFromConfig(MethodDescriptor method) { - MethodInfo info = getMethodInfo(method); - return info == null ? RetryPolicy.DEFAULT : info.retryPolicy; - } - - @VisibleForTesting - HedgingPolicy getHedgingPolicyFromConfig(MethodDescriptor method) { - MethodInfo info = getMethodInfo(method); - return info == null ? HedgingPolicy.DEFAULT : info.hedgingPolicy; - } -} diff --git a/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java b/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java index 1a39671af5..f7f79f0b5f 100644 --- a/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java +++ b/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.mock; import com.google.common.util.concurrent.MoreExecutors; @@ -481,19 +480,6 @@ public class AbstractManagedChannelImplBuilderTest { assertThat(builder.lookUpServiceConfig).isFalse(); } - @Test - public void enableServiceConfigErrorHandling() { - String propertyValue = System.getProperty( - AbstractManagedChannelImplBuilder.ENABLE_SERVICE_CONFIG_ERROR_HANDLING_PROPERTY); - assumeTrue(propertyValue == null); - - Builder builder = new Builder("target"); - assertThat(builder.enableServiceConfigErrorHandling).isFalse(); - - builder.enableServiceConfigErrorHandling(); - assertThat(builder.enableServiceConfigErrorHandling).isTrue(); - } - static class Builder extends AbstractManagedChannelImplBuilder { Builder(String target) { super(target); diff --git a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java index 66e69bc4b2..9232772778 100644 --- a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java +++ b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java @@ -51,23 +51,25 @@ import io.grpc.LoadBalancer.SubchannelStateListener; import io.grpc.LoadBalancerProvider; import io.grpc.LoadBalancerRegistry; import io.grpc.ManagedChannel; +import io.grpc.NameResolver.ConfigOrError; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.grpclb.GrpclbLoadBalancerProvider; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicyException; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicySelection; +import io.grpc.internal.AutoConfiguredLoadBalancerFactory.ResolvedPolicySelection; import io.grpc.util.ForwardingLoadBalancerHelper; import java.net.SocketAddress; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -78,8 +80,8 @@ import org.mockito.ArgumentCaptor; /** * Unit tests for {@link AutoConfiguredLoadBalancerFactory}. */ -@Deprecated // to be migrate to AutoConfiguredLoadBalancerFactoryTest2 @RunWith(JUnit4.class) +// TODO(creamsoup) remove backward compatible check when fully migrated @SuppressWarnings("deprecation") public class AutoConfiguredLoadBalancerFactoryTest { private static final LoadBalancerRegistry defaultRegistry = @@ -90,12 +92,18 @@ public class AutoConfiguredLoadBalancerFactoryTest { private final ChannelLogger channelLogger = mock(ChannelLogger.class); private final LoadBalancer testLbBalancer = mock(LoadBalancer.class); private final LoadBalancer testLbBalancer2 = mock(LoadBalancer.class); - private final LoadBalancerProvider testLbBalancerProvider = - mock(LoadBalancerProvider.class, - delegatesTo(new FakeLoadBalancerProvider("test_lb", testLbBalancer))); - private final LoadBalancerProvider testLbBalancerProvider2 = - mock(LoadBalancerProvider.class, - delegatesTo(new FakeLoadBalancerProvider("test_lb2", testLbBalancer2))); + private final AtomicReference nextParsedConfigOrError = + new AtomicReference<>(ConfigOrError.fromConfig("default")); + private final AtomicReference nextParsedConfigOrError2 = + new AtomicReference<>(ConfigOrError.fromConfig("default2")); + private final FakeLoadBalancerProvider testLbBalancerProvider = + mock(FakeLoadBalancerProvider.class, + delegatesTo( + new FakeLoadBalancerProvider("test_lb", testLbBalancer, nextParsedConfigOrError))); + private final FakeLoadBalancerProvider testLbBalancerProvider2 = + mock(FakeLoadBalancerProvider.class, + delegatesTo( + new FakeLoadBalancerProvider("test_lb2", testLbBalancer2, nextParsedConfigOrError2))); @Before public void setUp() { @@ -190,6 +198,7 @@ public class AutoConfiguredLoadBalancerFactoryTest { ResolvedAddresses.newBuilder() .setAddresses(servers) .setAttributes(Attributes.EMPTY) + .setLoadBalancingPolicyConfig(null) .build()); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); @@ -197,13 +206,11 @@ public class AutoConfiguredLoadBalancerFactoryTest { } @Test - public void handleResolvedAddressGroups_shutsDownOldBalancer() { - Map serviceConfig = new HashMap<>(); - serviceConfig.put("loadBalancingPolicy", "round_robin"); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); + public void handleResolvedAddressGroups_shutsDownOldBalancer() throws Exception { + Map serviceConfig = + parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": { } } ] }"); + ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + final List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); Helper helper = new TestHelper() { @@ -232,7 +239,7 @@ public class AutoConfiguredLoadBalancerFactoryTest { Status handleResult = lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) - .setAttributes(serviceConfigAttrs) + .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); @@ -242,13 +249,13 @@ public class AutoConfiguredLoadBalancerFactoryTest { } @Test + @SuppressWarnings("unchecked") public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exception { - Map serviceConfig = + Map rawServiceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); + ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); + assertThat(lbConfigs.getConfig()).isNotNull(); + final List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); Helper helper = new TestHelper(); @@ -257,7 +264,7 @@ public class AutoConfiguredLoadBalancerFactoryTest { Status handleResult = lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) - .setAttributes(serviceConfigAttrs) + .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); verify(testLbBalancerProvider).newLoadBalancer(same(helper)); @@ -267,22 +274,22 @@ public class AutoConfiguredLoadBalancerFactoryTest { ArgumentCaptor.forClass(ResolvedAddresses.class); verify(testLbBalancer).handleResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); - Attributes actualAttributes = resultCaptor.getValue().getAttributes(); - assertThat(actualAttributes.get(ATTR_LOAD_BALANCING_CONFIG)) - .isEqualTo(Collections.singletonMap("setting1", "high")); + assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG)) + .isEqualTo(rawServiceConfig); verify(testLbBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution(); + ArgumentCaptor> lbConfigCaptor = ArgumentCaptor.forClass(Map.class); + verify(testLbBalancerProvider).parseLoadBalancingPolicyConfig(lbConfigCaptor.capture()); + assertThat(lbConfigCaptor.getValue()).containsExactly("setting1", "high"); verifyNoMoreInteractions(testLbBalancer); - serviceConfig = + rawServiceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"low\" } } ] }"); - serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); + lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); + handleResult = lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) - .setAttributes(serviceConfigAttrs) + .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); resultCaptor = @@ -290,10 +297,11 @@ public class AutoConfiguredLoadBalancerFactoryTest { verify(testLbBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); - actualAttributes = resultCaptor.getValue().getAttributes(); - // But the balancer config is changed. - assertThat(actualAttributes.get(ATTR_LOAD_BALANCING_CONFIG)) - .isEqualTo(Collections.singletonMap("setting1", "low")); + assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG)) + .isEqualTo(rawServiceConfig); + verify(testLbBalancerProvider, times(2)) + .parseLoadBalancingPolicyConfig(lbConfigCaptor.capture()); + assertThat(lbConfigCaptor.getValue()).containsExactly("setting1", "low"); // Service config didn't change policy, thus the delegateLb is not swapped verifyNoMoreInteractions(testLbBalancer); verify(testLbBalancerProvider).newLoadBalancer(any(Helper.class)); @@ -304,7 +312,9 @@ public class AutoConfiguredLoadBalancerFactoryTest { // This case only happens when grpclb is missing. We will use a local registry LoadBalancerRegistry registry = new LoadBalancerRegistry(); registry.register(new PickFirstLoadBalancerProvider()); - registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer)); + registry.register( + new FakeLoadBalancerProvider( + "round_robin", testLbBalancer, /* nextParsedLbPolicyConfig= */ null)); final List servers = Arrays.asList( @@ -339,11 +349,11 @@ public class AutoConfiguredLoadBalancerFactoryTest { Map serviceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); + ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(serviceConfig, helper.getChannelLogger()); Status handleResult = lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(Collections.emptyList()) - .setAttributes(Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build()) + .setLoadBalancingPolicyConfig(lbConfig.getConfig()) .build()); assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse(); @@ -358,13 +368,14 @@ public class AutoConfiguredLoadBalancerFactoryTest { Helper helper = new TestHelper(); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - Map serviceConfig = + Map rawServiceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb2\": { \"setting1\": \"high\" } } ] }"); + ConfigOrError lbConfigs = + lbf.parseLoadBalancerPolicy(rawServiceConfig, helper.getChannelLogger()); Status handleResult = lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(Collections.emptyList()) - .setAttributes(Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build()) + .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); @@ -374,26 +385,25 @@ public class AutoConfiguredLoadBalancerFactoryTest { ArgumentCaptor.forClass(ResolvedAddresses.class); verify(testLbBalancer2).handleResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).isEmpty(); - Attributes actualAttributes = resultCaptor.getValue().getAttributes(); - - Map lbConfig = actualAttributes.get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG); - assertThat(lbConfig).isEqualTo(Collections.singletonMap("setting1", "high")); - assertThat(actualAttributes.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isSameInstanceAs(serviceConfig); + assertThat(resultCaptor.getValue().getLoadBalancingPolicyConfig()) + .isEqualTo(nextParsedConfigOrError2.get().getConfig()); + assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG)) + .isEqualTo(rawServiceConfig); } @Test public void decideLoadBalancerProvider_noBalancerAddresses_noServiceConfig_pickFirst() throws Exception { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = null; + PolicySelection policySelection = null; List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - assertThat(selection.provider).isInstanceOf(PickFirstLoadBalancerProvider.class); + assertThat(selection.policySelection.provider) + .isInstanceOf(PickFirstLoadBalancerProvider.class); assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isNull(); + assertThat(selection.policySelection.config).isNull(); verifyZeroInteractions(channelLogger); } @@ -402,39 +412,43 @@ public class AutoConfiguredLoadBalancerFactoryTest { throws Exception { AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory("test_lb") .newLoadBalancer(new TestHelper()); - Map serviceConfig = null; + PolicySelection policySelection = null; List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - assertThat(selection.provider).isSameInstanceAs(testLbBalancerProvider); + assertThat(selection.policySelection.provider).isSameInstanceAs(testLbBalancerProvider); assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isNull(); + assertThat(selection.policySelection.config).isNull(); verifyZeroInteractions(channelLogger); } @Test public void decideLoadBalancerProvider_oneBalancer_noServiceConfig_grpclb() throws Exception { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = null; + PolicySelection policySelection = null; List servers = Collections.singletonList( new EquivalentAddressGroup( new SocketAddress(){}, Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - assertThat(selection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); + assertThat(selection.policySelection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isNull(); + assertThat(selection.policySelection.config).isNull(); verifyZeroInteractions(channelLogger); } @Test public void decideLoadBalancerProvider_serviceConfigLbPolicy() throws Exception { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = new HashMap<>(); - serviceConfig.put("loadBalancingPolicy", "round_robin"); + Map rawServiceConfig = + parseConfig("{\"loadBalancingPolicy\": \"round_robin\"}"); + + ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); + assertThat(lbConfig.getConfig()).isNotNull(); + PolicySelection policySelection = (PolicySelection) lbConfig.getConfig(); List servers = Arrays.asList( new EquivalentAddressGroup( @@ -443,21 +457,23 @@ public class AutoConfiguredLoadBalancerFactoryTest { new EquivalentAddressGroup( new SocketAddress(){})); List backends = Arrays.asList(servers.get(1)); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - assertThat(selection.provider.getClass().getName()).isEqualTo( + assertThat(selection.policySelection.provider.getClass().getName()).isEqualTo( "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); assertThat(selection.serverList).isEqualTo(backends); - assertThat(selection.config).isEqualTo(Collections.emptyMap()); verifyZeroInteractions(channelLogger); } - @SuppressWarnings("unchecked") @Test public void decideLoadBalancerProvider_serviceConfigLbConfig() throws Exception { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {} } ] }"); + Map rawServiceConfig = + parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); + + ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); + assertThat(lbConfig.getConfig()).isNotNull(); + PolicySelection policySelection = (PolicySelection) lbConfig.getConfig(); List servers = Arrays.asList( new EquivalentAddressGroup( @@ -466,55 +482,54 @@ public class AutoConfiguredLoadBalancerFactoryTest { new EquivalentAddressGroup( new SocketAddress(){})); List backends = Arrays.asList(servers.get(1)); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - assertThat(selection.provider.getClass().getName()).isEqualTo( + assertThat(selection.policySelection.provider.getClass().getName()).isEqualTo( "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); assertThat(selection.serverList).isEqualTo(backends); - assertThat(selection.config).isEqualTo(Collections.emptyMap()); verifyZeroInteractions(channelLogger); } @Test public void decideLoadBalancerProvider_grpclbConfigPropagated() throws Exception { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = + Map rawServiceConfig = parseConfig( "{\"loadBalancingConfig\": [" - + "{\"grpclb\": {\"childPolicy\": [ {\"pick_first\": {} } ] } }" - + "] }"); + + "{\"grpclb\": {\"childPolicy\": [ {\"pick_first\": {} } ] } }" + + "] }"); + ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); + assertThat(lbConfig.getConfig()).isNotNull(); + PolicySelection policySelection = (PolicySelection) lbConfig.getConfig(); + List servers = Collections.singletonList( new EquivalentAddressGroup( new SocketAddress(){}, Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - assertThat(selection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); + assertThat(selection.policySelection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isEqualTo( - parseConfig("{\"childPolicy\": [ {\"pick_first\": {} } ] }")); + assertThat(selection.policySelection.config) + .isEqualTo(((PolicySelection) lbConfig.getConfig()).config); verifyZeroInteractions(channelLogger); } @Test public void decideLoadBalancerProvider_policyUnavailButGrpclbAddressPresent() throws Exception { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = - parseConfig( - "{\"loadBalancingConfig\": [" - + "{\"unavail\": {} }" - + "] }"); + List servers = Collections.singletonList( new EquivalentAddressGroup( new SocketAddress(){}, Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, null); - assertThat(selection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); + assertThat(selection.policySelection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isNull(); + assertThat(selection.policySelection.config).isNull(); verifyZeroInteractions(channelLogger); } @@ -524,34 +539,32 @@ public class AutoConfiguredLoadBalancerFactoryTest { LoadBalancerRegistry registry = new LoadBalancerRegistry(); registry.register(new PickFirstLoadBalancerProvider()); LoadBalancerProvider fakeRoundRobinProvider = - new FakeLoadBalancerProvider("round_robin", testLbBalancer); + new FakeLoadBalancerProvider("round_robin", testLbBalancer, null); registry.register(fakeRoundRobinProvider); AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory( registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper()); - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"grpclb\": {} } ] }"); List servers = Arrays.asList( new EquivalentAddressGroup( new SocketAddress(){}, Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()), new EquivalentAddressGroup(new SocketAddress(){})); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, null); - assertThat(selection.provider).isSameInstanceAs(fakeRoundRobinProvider); - assertThat(selection.config).isNull(); + assertThat(selection.policySelection.provider).isSameInstanceAs(fakeRoundRobinProvider); + assertThat(selection.policySelection.config).isNull(); verify(channelLogger).log( eq(ChannelLogLevel.ERROR), startsWith("Found balancer addresses but grpclb runtime is missing")); // Called for the second time, the warning is only logged once - selection = lb.decideLoadBalancerProvider(servers, serviceConfig); + selection = lb.resolveLoadBalancerProvider(servers, null); - assertThat(selection.provider).isSameInstanceAs(fakeRoundRobinProvider); + assertThat(selection.policySelection.provider).isSameInstanceAs(fakeRoundRobinProvider); + assertThat(selection.policySelection.config).isNull(); // Balancer addresses are filtered out in the server list passed to round_robin assertThat(selection.serverList).containsExactly(servers.get(1)); - assertThat(selection.config).isNull(); - verifyNoMoreInteractions(channelLogger); + verifyNoMoreInteractions(channelLogger);; } @Test @@ -559,18 +572,16 @@ public class AutoConfiguredLoadBalancerFactoryTest { throws Exception { LoadBalancerRegistry registry = new LoadBalancerRegistry(); registry.register(new PickFirstLoadBalancerProvider()); - registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer)); + registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer, null)); AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory( registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper()); - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"grpclb\": {} } ] }"); List servers = Collections.singletonList( new EquivalentAddressGroup( new SocketAddress(){}, Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); try { - lb.decideLoadBalancerProvider(servers, serviceConfig); + lb.resolveLoadBalancerProvider(servers, null); fail("Should throw"); } catch (PolicyException e) { assertThat(e) @@ -579,105 +590,26 @@ public class AutoConfiguredLoadBalancerFactoryTest { } } - @Test - public void decideLoadBalancerProvider_serviceConfigLbPolicyOverridesDefault() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = new HashMap<>(); - serviceConfig.put("loadBalancingPolicy", "round_robin"); - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); - - assertThat(selection.provider.getClass().getName()).isEqualTo( - "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - assertThat(selection.config).isEqualTo(Collections.emptyMap()); - verifyZeroInteractions(channelLogger); - } - @Test public void decideLoadBalancerProvider_serviceConfigLbConfigOverridesDefault() throws Exception { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {\"setting1\": \"high\"} } ] }"); + Map rawServiceConfig = + parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {} } ] }"); + ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); + assertThat(lbConfigs.getConfig()).isNotNull(); + PolicySelection policySelection = (PolicySelection) lbConfigs.getConfig(); List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); - assertThat(selection.provider.getClass().getName()).isEqualTo( + ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); + + assertThat(selection.policySelection.provider.getClass().getName()).isEqualTo( "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isEqualTo(Collections.singletonMap("setting1", "high")); verifyZeroInteractions(channelLogger); } @Test - public void decideLoadBalancerProvider_serviceConfigLbPolicyFailsOnUnknown() { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = new HashMap<>(); - serviceConfig.put("loadBalancingPolicy", "MAGIC_BALANCER"); - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - try { - lb.decideLoadBalancerProvider(servers, serviceConfig); - fail(); - } catch (PolicyException e) { - assertThat(e).hasMessageThat().isEqualTo( - "None of [magic_balancer] specified by Service Config are available."); - } - } - - @Test - public void decideLoadBalancerProvider_serviceConfigLbConfigFailsOnUnknown() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"magic_balancer\": {} } ] }"); - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - try { - lb.decideLoadBalancerProvider(servers, serviceConfig); - fail(); - } catch (PolicyException e) { - assertThat(e).hasMessageThat().isEqualTo( - "None of [magic_balancer] specified by Service Config are available."); - } - } - - @Test - public void decideLoadBalancerProvider_serviceConfigLbConfigSkipUnknown() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map serviceConfig = - parseConfig( - "{\"loadBalancingConfig\": [ {\"magic_balancer\": {} }, {\"round_robin\": {} } ] }"); - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); - - assertThat(selection.provider.getClass().getName()).isEqualTo( - "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isEqualTo(Collections.emptyMap()); - verify(channelLogger).log( - eq(ChannelLogLevel.DEBUG), - eq("{0} specified by Service Config are not available"), - eq(new LinkedHashSet<>(Arrays.asList("magic_balancer")))); - } - - @Test - public void decideLoadBalancerProvider_serviceConfigHasZeroLbConfig() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - PolicySelection selection = lb.decideLoadBalancerProvider( - servers, Collections.emptyMap()); - - assertThat(selection.provider).isInstanceOf(PickFirstLoadBalancerProvider.class); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.config).isNull(); - verifyZeroInteractions(channelLogger); - } - - @Test - public void channelTracing_lbPolicyChanged() { + public void channelTracing_lbPolicyChanged() throws Exception { final FakeClock clock = new FakeClock(); List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); @@ -734,38 +666,44 @@ public class AutoConfiguredLoadBalancerFactoryTest { assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); verifyNoMoreInteractions(channelLogger); - Map serviceConfig = new HashMap<>(); - serviceConfig.put("loadBalancingPolicy", "round_robin"); + ConfigOrError testLbParsedConfig = ConfigOrError.fromConfig("foo"); + nextParsedConfigOrError.set(testLbParsedConfig); + Map serviceConfig = + parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { } } ] }"); + ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); handleResult = lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) - .setAttributes(Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build()) + .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); verify(channelLogger).log( eq(ChannelLogLevel.INFO), eq("Load balancer changed from {0} to {1}"), - eq("PickFirstLoadBalancer"), eq("RoundRobinLoadBalancer")); + eq("PickFirstLoadBalancer"), + eq(testLbBalancer.getClass().getSimpleName())); + verify(channelLogger).log( eq(ChannelLogLevel.DEBUG), eq("Load-balancing config: {0}"), - eq(Collections.emptyMap())); + eq(testLbParsedConfig.getConfig())); verifyNoMoreInteractions(channelLogger); - serviceConfig.put("loadBalancingPolicy", "round_robin"); + testLbParsedConfig = ConfigOrError.fromConfig("bar"); + nextParsedConfigOrError.set(testLbParsedConfig); + serviceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { } } ] }"); + lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); handleResult = lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) - .setAttributes(Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build()) + .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - verify(channelLogger, times(2)).log( + verify(channelLogger).log( eq(ChannelLogLevel.DEBUG), eq("Load-balancing config: {0}"), - eq(Collections.emptyMap())); + eq(testLbParsedConfig.getConfig())); verifyNoMoreInteractions(channelLogger); servers = Collections.singletonList(new EquivalentAddressGroup( @@ -781,11 +719,122 @@ public class AutoConfiguredLoadBalancerFactoryTest { verify(channelLogger).log( eq(ChannelLogLevel.INFO), eq("Load balancer changed from {0} to {1}"), - eq("RoundRobinLoadBalancer"), eq("GrpclbLoadBalancer")); + eq(testLbBalancer.getClass().getSimpleName()), eq("GrpclbLoadBalancer")); verifyNoMoreInteractions(channelLogger); } + @Test + public void parseLoadBalancerConfig_failedOnUnknown() throws Exception { + Map serviceConfig = + parseConfig("{\"loadBalancingConfig\": [ {\"magic_balancer\": {} } ] }"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed.getError()).isNotNull(); + assertThat(parsed.getError().getDescription()) + .isEqualTo("None of [magic_balancer] specified by Service Config are available."); + } + + @Test + public void parseLoadBalancerPolicy_failedOnUnknown() throws Exception { + Map serviceConfig = + parseConfig("{\"loadBalancingPolicy\": \"magic_balancer\"}"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed.getError()).isNotNull(); + assertThat(parsed.getError().getDescription()) + .isEqualTo("None of [magic_balancer] specified by Service Config are available."); + } + + @Test + public void parseLoadBalancerConfig_multipleValidPolicies() throws Exception { + Map serviceConfig = + parseConfig( + "{\"loadBalancingConfig\": [" + + "{\"round_robin\": {}}," + + "{\"test_lb\": {} } ] }"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed).isNotNull(); + assertThat(parsed.getError()).isNull(); + assertThat(parsed.getConfig()).isInstanceOf(PolicySelection.class); + assertThat(((PolicySelection) parsed.getConfig()).provider.getClass().getName()) + .isEqualTo("io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); + } + + @Test + public void parseLoadBalancerConfig_policyShouldBeIgnoredIfConfigExists() throws Exception { + Map serviceConfig = + parseConfig( + "{\"loadBalancingConfig\": [{\"round_robin\": {} } ]," + + "\"loadBalancingPolicy\": \"pick_first\" }"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed).isNotNull(); + assertThat(parsed.getError()).isNull(); + assertThat(parsed.getConfig()).isInstanceOf(PolicySelection.class); + assertThat(((PolicySelection) parsed.getConfig()).provider.getClass().getName()) + .isEqualTo("io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); + } + + @Test + public void parseLoadBalancerConfig_policyShouldBeIgnoredEvenIfUnknownPolicyExists() + throws Exception { + Map serviceConfig = + parseConfig( + "{\"loadBalancingConfig\": [{\"magic_balancer\": {} } ]," + + "\"loadBalancingPolicy\": \"round_robin\" }"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed.getError()).isNotNull(); + assertThat(parsed.getError().getDescription()) + .isEqualTo("None of [magic_balancer] specified by Service Config are available."); + } + + @Test + @SuppressWarnings("unchecked") + public void parseLoadBalancerConfig_firstInvalidPolicy() throws Exception { + when(testLbBalancerProvider.parseLoadBalancingPolicyConfig(any(Map.class))) + .thenReturn(ConfigOrError.fromError(Status.UNKNOWN)); + Map serviceConfig = + parseConfig( + "{\"loadBalancingConfig\": [" + + "{\"test_lb\": {}}," + + "{\"round_robin\": {} } ] }"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed).isNotNull(); + assertThat(parsed.getConfig()).isNull(); + assertThat(parsed.getError()).isEqualTo(Status.UNKNOWN); + } + + @Test + @SuppressWarnings("unchecked") + public void parseLoadBalancerConfig_firstValidSecondInvalidPolicy() throws Exception { + when(testLbBalancerProvider.parseLoadBalancingPolicyConfig(any(Map.class))) + .thenReturn(ConfigOrError.fromError(Status.UNKNOWN)); + Map serviceConfig = + parseConfig( + "{\"loadBalancingConfig\": [" + + "{\"round_robin\": {}}," + + "{\"test_lb\": {} } ] }"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed).isNotNull(); + assertThat(parsed.getConfig()).isNotNull(); + assertThat(((PolicySelection) parsed.getConfig()).config).isNotNull(); + } + + @Test + public void parseLoadBalancerConfig_someProvidesAreNotAvailable() throws Exception { + Map serviceConfig = + parseConfig("{\"loadBalancingConfig\": [ " + + "{\"magic_balancer\": {} }," + + "{\"round_robin\": {}} ] }"); + ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); + assertThat(parsed).isNotNull(); + assertThat(parsed.getConfig()).isNotNull(); + assertThat(((PolicySelection) parsed.getConfig()).config).isNotNull(); + verify(channelLogger).log( + eq(ChannelLogLevel.DEBUG), + eq("{0} specified by Service Config are not available"), + eq(new ArrayList<>(Collections.singletonList("magic_balancer")))); + } + + public static class ForwardingLoadBalancer extends LoadBalancer { private final LoadBalancer delegate; @@ -886,13 +935,18 @@ public class AutoConfiguredLoadBalancerFactoryTest { } } - private static final class FakeLoadBalancerProvider extends LoadBalancerProvider { + private static class FakeLoadBalancerProvider extends LoadBalancerProvider { private final String policyName; private final LoadBalancer balancer; + private final AtomicReference nextParsedLbPolicyConfig; - FakeLoadBalancerProvider(String policyName, LoadBalancer balancer) { + FakeLoadBalancerProvider( + String policyName, + LoadBalancer balancer, + AtomicReference nextParsedLbPolicyConfig) { this.policyName = policyName; this.balancer = balancer; + this.nextParsedLbPolicyConfig = nextParsedLbPolicyConfig; } @Override @@ -914,5 +968,14 @@ public class AutoConfiguredLoadBalancerFactoryTest { public LoadBalancer newLoadBalancer(Helper helper) { return balancer; } + + @Override + public ConfigOrError parseLoadBalancingPolicyConfig( + Map rawLoadBalancingPolicyConfig) { + if (nextParsedLbPolicyConfig == null) { + return super.parseLoadBalancingPolicyConfig(rawLoadBalancingPolicyConfig); + } + return nextParsedLbPolicyConfig.get(); + } } } diff --git a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest2.java b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest2.java deleted file mode 100644 index c60c6eb439..0000000000 --- a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest2.java +++ /dev/null @@ -1,981 +0,0 @@ -/* - * Copyright 2018 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.internal; - -import static com.google.common.truth.Truth.assertThat; -import static io.grpc.LoadBalancer.ATTR_LOAD_BALANCING_CONFIG; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.AdditionalAnswers.delegatesTo; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.ArgumentMatchers.startsWith; -import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import com.google.common.base.Preconditions; -import io.grpc.Attributes; -import io.grpc.ChannelLogger; -import io.grpc.ChannelLogger.ChannelLogLevel; -import io.grpc.ConnectivityState; -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.LoadBalancer.SubchannelStateListener; -import io.grpc.LoadBalancerProvider; -import io.grpc.LoadBalancerRegistry; -import io.grpc.ManagedChannel; -import io.grpc.NameResolver.ConfigOrError; -import io.grpc.Status; -import io.grpc.SynchronizationContext; -import io.grpc.grpclb.GrpclbLoadBalancerProvider; -import io.grpc.internal.AutoConfiguredLoadBalancerFactory2.AutoConfiguredLoadBalancer; -import io.grpc.internal.AutoConfiguredLoadBalancerFactory2.PolicyException; -import io.grpc.internal.AutoConfiguredLoadBalancerFactory2.PolicySelection; -import io.grpc.internal.AutoConfiguredLoadBalancerFactory2.ResolvedPolicySelection; -import io.grpc.util.ForwardingLoadBalancerHelper; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; - -/** - * Unit tests for {@link AutoConfiguredLoadBalancerFactory}. - */ -@RunWith(JUnit4.class) -// TODO(creamsoup) remove backward compatible check when fully migrated -@SuppressWarnings("deprecation") -public class AutoConfiguredLoadBalancerFactoryTest2 { - private static final LoadBalancerRegistry defaultRegistry = - LoadBalancerRegistry.getDefaultRegistry(); - private final AutoConfiguredLoadBalancerFactory2 lbf = - new AutoConfiguredLoadBalancerFactory2(GrpcUtil.DEFAULT_LB_POLICY); - - private final ChannelLogger channelLogger = mock(ChannelLogger.class); - private final LoadBalancer testLbBalancer = mock(LoadBalancer.class); - private final LoadBalancer testLbBalancer2 = mock(LoadBalancer.class); - private final AtomicReference nextParsedConfigOrError = - new AtomicReference<>(ConfigOrError.fromConfig("default")); - private final AtomicReference nextParsedConfigOrError2 = - new AtomicReference<>(ConfigOrError.fromConfig("default2")); - private final FakeLoadBalancerProvider testLbBalancerProvider = - mock(FakeLoadBalancerProvider.class, - delegatesTo( - new FakeLoadBalancerProvider("test_lb", testLbBalancer, nextParsedConfigOrError))); - private final FakeLoadBalancerProvider testLbBalancerProvider2 = - mock(FakeLoadBalancerProvider.class, - delegatesTo( - new FakeLoadBalancerProvider("test_lb2", testLbBalancer2, nextParsedConfigOrError2))); - - @Before - public void setUp() { - when(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod(); - assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse(); - when(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); - defaultRegistry.register(testLbBalancerProvider); - defaultRegistry.register(testLbBalancerProvider2); - } - - @After - public void tearDown() { - defaultRegistry.deregister(testLbBalancerProvider); - defaultRegistry.deregister(testLbBalancerProvider2); - } - - @Test - public void newLoadBalancer_isAuto() { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - - assertThat(lb).isInstanceOf(AutoConfiguredLoadBalancer.class); - } - - @Test - public void defaultIsPickFirst() { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - - assertThat(lb.getDelegateProvider()).isInstanceOf(PickFirstLoadBalancerProvider.class); - assertThat(lb.getDelegate().getClass().getName()).contains("PickFirst"); - } - - @Test - public void defaultIsConfigurable() { - AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory2("test_lb") - .newLoadBalancer(new TestHelper()); - - assertThat(lb.getDelegateProvider()).isSameInstanceAs(testLbBalancerProvider); - assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); - } - - @SuppressWarnings("deprecation") - @Test - public void forwardsCalls() { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - - final AtomicInteger calls = new AtomicInteger(); - TestLoadBalancer testlb = new TestLoadBalancer() { - - @Override - public void handleNameResolutionError(Status error) { - calls.getAndSet(1); - } - - @Override - public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) { - calls.getAndSet(2); - } - - @Override - public void shutdown() { - calls.getAndSet(3); - } - }; - - lb.setDelegate(testlb); - - lb.handleNameResolutionError(Status.RESOURCE_EXHAUSTED); - assertThat(calls.getAndSet(0)).isEqualTo(1); - - lb.handleSubchannelState(null, null); - assertThat(calls.getAndSet(0)).isEqualTo(2); - - lb.shutdown(); - assertThat(calls.getAndSet(0)).isEqualTo(3); - } - - @Test - public void handleResolvedAddressGroups_keepOldBalancer() { - final List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - Helper helper = new TestHelper() { - @Override - public Subchannel createSubchannel(CreateSubchannelArgs args) { - assertThat(args.getAddresses()).isEqualTo(servers); - return new TestSubchannel(args); - } - }; - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - LoadBalancer oldDelegate = lb.getDelegate(); - - Status handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(Attributes.EMPTY) - .setLoadBalancingPolicyConfig(null) - .build()); - - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - assertThat(lb.getDelegate()).isSameInstanceAs(oldDelegate); - } - - @Test - public void handleResolvedAddressGroups_shutsDownOldBalancer() throws Exception { - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": { } } ] }"); - ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - - final List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - Helper helper = new TestHelper() { - @Override - public Subchannel createSubchannel(CreateSubchannelArgs args) { - assertThat(args.getAddresses()).isEqualTo(servers); - return new TestSubchannel(args); - } - }; - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - final AtomicBoolean shutdown = new AtomicBoolean(); - TestLoadBalancer testlb = new TestLoadBalancer() { - - @Override - public void handleNameResolutionError(Status error) { - // noop - } - - @Override - public void shutdown() { - shutdown.set(true); - } - }; - lb.setDelegate(testlb); - - Status handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) - .build()); - - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - assertThat(lb.getDelegateProvider().getClass().getName()).isEqualTo( - "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - assertTrue(shutdown.get()); - } - - @Test - @SuppressWarnings("unchecked") - public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exception { - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); - ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); - assertThat(lbConfigs.getConfig()).isNotNull(); - - final List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - Helper helper = new TestHelper(); - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - - Status handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) - .build()); - - verify(testLbBalancerProvider).newLoadBalancer(same(helper)); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(testLbBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); - assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG)) - .isEqualTo(rawServiceConfig); - verify(testLbBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution(); - ArgumentCaptor> lbConfigCaptor = ArgumentCaptor.forClass(Map.class); - verify(testLbBalancerProvider).parseLoadBalancingPolicyConfig(lbConfigCaptor.capture()); - assertThat(lbConfigCaptor.getValue()).containsExactly("setting1", "high"); - verifyNoMoreInteractions(testLbBalancer); - - rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"low\" } } ] }"); - lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); - - handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) - .build()); - - resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(testLbBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); - assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG)) - .isEqualTo(rawServiceConfig); - verify(testLbBalancerProvider, times(2)) - .parseLoadBalancingPolicyConfig(lbConfigCaptor.capture()); - assertThat(lbConfigCaptor.getValue()).containsExactly("setting1", "low"); - // Service config didn't change policy, thus the delegateLb is not swapped - verifyNoMoreInteractions(testLbBalancer); - verify(testLbBalancerProvider).newLoadBalancer(any(Helper.class)); - } - - @Test - public void handleResolvedAddressGroups_propagateOnlyBackendAddrsToDelegate() throws Exception { - // This case only happens when grpclb is missing. We will use a local registry - LoadBalancerRegistry registry = new LoadBalancerRegistry(); - registry.register(new PickFirstLoadBalancerProvider()); - registry.register( - new FakeLoadBalancerProvider( - "round_robin", testLbBalancer, /* nextParsedLbPolicyConfig= */ null)); - - final List servers = - Arrays.asList( - new EquivalentAddressGroup(new SocketAddress(){}), - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - Helper helper = new TestHelper(); - AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory2( - registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(helper); - - Status handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(Attributes.EMPTY) - .build()); - - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); - verify(testLbBalancer).handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Collections.singletonList(servers.get(0))) - .setAttributes(Attributes.EMPTY) - .build()); - } - - @Test - public void handleResolvedAddressGroups_delegateDoNotAcceptEmptyAddressList_nothing() - throws Exception { - Helper helper = new TestHelper(); - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); - ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(serviceConfig, helper.getChannelLogger()); - Status handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Collections.emptyList()) - .setLoadBalancingPolicyConfig(lbConfig.getConfig()) - .build()); - - assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse(); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.UNAVAILABLE); - assertThat(handleResult.getDescription()).startsWith("NameResolver returned no usable address"); - assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); - } - - @Test - public void handleResolvedAddressGroups_delegateAcceptsEmptyAddressList() - throws Exception { - Helper helper = new TestHelper(); - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"test_lb2\": { \"setting1\": \"high\" } } ] }"); - ConfigOrError lbConfigs = - lbf.parseLoadBalancerPolicy(rawServiceConfig, helper.getChannelLogger()); - Status handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Collections.emptyList()) - .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) - .build()); - - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer2); - assertThat(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).isTrue(); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(testLbBalancer2).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).isEmpty(); - assertThat(resultCaptor.getValue().getLoadBalancingPolicyConfig()) - .isEqualTo(nextParsedConfigOrError2.get().getConfig()); - assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG)) - .isEqualTo(rawServiceConfig); - } - - @Test - public void decideLoadBalancerProvider_noBalancerAddresses_noServiceConfig_pickFirst() - throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - PolicySelection policySelection = null; - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - - assertThat(selection.policySelection.provider) - .isInstanceOf(PickFirstLoadBalancerProvider.class); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.policySelection.config).isNull(); - verifyZeroInteractions(channelLogger); - } - - @Test - public void decideLoadBalancerProvider_noBalancerAddresses_noServiceConfig_customDefault() - throws Exception { - AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory2("test_lb") - .newLoadBalancer(new TestHelper()); - PolicySelection policySelection = null; - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - - assertThat(selection.policySelection.provider).isSameInstanceAs(testLbBalancerProvider); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.policySelection.config).isNull(); - verifyZeroInteractions(channelLogger); - } - - @Test - public void decideLoadBalancerProvider_oneBalancer_noServiceConfig_grpclb() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - PolicySelection policySelection = null; - List servers = - Collections.singletonList( - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - - assertThat(selection.policySelection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.policySelection.config).isNull(); - verifyZeroInteractions(channelLogger); - } - - @Test - public void decideLoadBalancerProvider_serviceConfigLbPolicy() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map rawServiceConfig = - parseConfig("{\"loadBalancingPolicy\": \"round_robin\"}"); - - ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); - assertThat(lbConfig.getConfig()).isNotNull(); - PolicySelection policySelection = (PolicySelection) lbConfig.getConfig(); - List servers = - Arrays.asList( - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()), - new EquivalentAddressGroup( - new SocketAddress(){})); - List backends = Arrays.asList(servers.get(1)); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - - assertThat(selection.policySelection.provider.getClass().getName()).isEqualTo( - "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - assertThat(selection.serverList).isEqualTo(backends); - verifyZeroInteractions(channelLogger); - } - - @Test - public void decideLoadBalancerProvider_serviceConfigLbConfig() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); - - ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); - assertThat(lbConfig.getConfig()).isNotNull(); - PolicySelection policySelection = (PolicySelection) lbConfig.getConfig(); - List servers = - Arrays.asList( - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()), - new EquivalentAddressGroup( - new SocketAddress(){})); - List backends = Arrays.asList(servers.get(1)); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - - assertThat(selection.policySelection.provider.getClass().getName()).isEqualTo( - "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - assertThat(selection.serverList).isEqualTo(backends); - verifyZeroInteractions(channelLogger); - } - - @Test - public void decideLoadBalancerProvider_grpclbConfigPropagated() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map rawServiceConfig = - parseConfig( - "{\"loadBalancingConfig\": [" - + "{\"grpclb\": {\"childPolicy\": [ {\"pick_first\": {} } ] } }" - + "] }"); - ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); - assertThat(lbConfig.getConfig()).isNotNull(); - PolicySelection policySelection = (PolicySelection) lbConfig.getConfig(); - - List servers = - Collections.singletonList( - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - - assertThat(selection.policySelection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.policySelection.config) - .isEqualTo(((PolicySelection) lbConfig.getConfig()).config); - verifyZeroInteractions(channelLogger); - } - - @Test - public void decideLoadBalancerProvider_policyUnavailButGrpclbAddressPresent() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - - List servers = - Collections.singletonList( - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, null); - - assertThat(selection.policySelection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); - assertThat(selection.serverList).isEqualTo(servers); - assertThat(selection.policySelection.config).isNull(); - verifyZeroInteractions(channelLogger); - } - - @Test - public void decideLoadBalancerProvider_grpclbProviderNotFound_fallbackToRoundRobin() - throws Exception { - LoadBalancerRegistry registry = new LoadBalancerRegistry(); - registry.register(new PickFirstLoadBalancerProvider()); - LoadBalancerProvider fakeRoundRobinProvider = - new FakeLoadBalancerProvider("round_robin", testLbBalancer, null); - registry.register(fakeRoundRobinProvider); - AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory2( - registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper()); - List servers = - Arrays.asList( - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()), - new EquivalentAddressGroup(new SocketAddress(){})); - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, null); - - assertThat(selection.policySelection.provider).isSameInstanceAs(fakeRoundRobinProvider); - assertThat(selection.policySelection.config).isNull(); - verify(channelLogger).log( - eq(ChannelLogLevel.ERROR), - startsWith("Found balancer addresses but grpclb runtime is missing")); - - // Called for the second time, the warning is only logged once - selection = lb.resolveLoadBalancerProvider(servers, null); - - assertThat(selection.policySelection.provider).isSameInstanceAs(fakeRoundRobinProvider); - assertThat(selection.policySelection.config).isNull(); - // Balancer addresses are filtered out in the server list passed to round_robin - assertThat(selection.serverList).containsExactly(servers.get(1)); - verifyNoMoreInteractions(channelLogger);; - } - - @Test - public void decideLoadBalancerProvider_grpclbProviderNotFound_noBackendAddress() - throws Exception { - LoadBalancerRegistry registry = new LoadBalancerRegistry(); - registry.register(new PickFirstLoadBalancerProvider()); - registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer, null)); - AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory2( - registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper()); - List servers = - Collections.singletonList( - new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - try { - lb.resolveLoadBalancerProvider(servers, null); - fail("Should throw"); - } catch (PolicyException e) { - assertThat(e) - .hasMessageThat() - .isEqualTo("Received ONLY balancer addresses but grpclb runtime is missing"); - } - } - - @Test - public void decideLoadBalancerProvider_serviceConfigLbConfigOverridesDefault() throws Exception { - AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {} } ] }"); - ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger); - assertThat(lbConfigs.getConfig()).isNotNull(); - PolicySelection policySelection = (PolicySelection) lbConfigs.getConfig(); - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - - ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, policySelection); - - assertThat(selection.policySelection.provider.getClass().getName()).isEqualTo( - "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - verifyZeroInteractions(channelLogger); - } - - @Test - public void channelTracing_lbPolicyChanged() throws Exception { - final FakeClock clock = new FakeClock(); - List servers = - Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - Helper helper = new TestHelper() { - @Override - @Deprecated - public Subchannel createSubchannel(List addrs, Attributes attrs) { - return new TestSubchannel(CreateSubchannelArgs.newBuilder() - .setAddresses(addrs) - .setAttributes(attrs) - .build()); - } - - @Override - public Subchannel createSubchannel(CreateSubchannelArgs args) { - return new TestSubchannel(args); - } - - @Override - public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority) { - return mock(ManagedChannel.class, RETURNS_DEEP_STUBS); - } - - @Override - public String getAuthority() { - return "fake_authority"; - } - - @Override - public SynchronizationContext getSynchronizationContext() { - return new SynchronizationContext( - new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - throw new AssertionError(e); - } - }); - } - - @Override - public ScheduledExecutorService getScheduledExecutorService() { - return clock.getScheduledExecutorService(); - } - }; - - AutoConfiguredLoadBalancer lb = - new AutoConfiguredLoadBalancerFactory2(GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(helper); - Status handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(Attributes.EMPTY) - .build()); - - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - verifyNoMoreInteractions(channelLogger); - - ConfigOrError testLbParsedConfig = ConfigOrError.fromConfig("foo"); - nextParsedConfigOrError.set(testLbParsedConfig); - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { } } ] }"); - ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) - .build()); - - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - verify(channelLogger).log( - eq(ChannelLogLevel.INFO), - eq("Load balancer changed from {0} to {1}"), - eq("PickFirstLoadBalancer"), - eq(testLbBalancer.getClass().getSimpleName())); - - verify(channelLogger).log( - eq(ChannelLogLevel.DEBUG), - eq("Load-balancing config: {0}"), - eq(testLbParsedConfig.getConfig())); - verifyNoMoreInteractions(channelLogger); - - testLbParsedConfig = ConfigOrError.fromConfig("bar"); - nextParsedConfigOrError.set(testLbParsedConfig); - serviceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { } } ] }"); - lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) - .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - verify(channelLogger).log( - eq(ChannelLogLevel.DEBUG), - eq("Load-balancing config: {0}"), - eq(testLbParsedConfig.getConfig())); - verifyNoMoreInteractions(channelLogger); - - servers = Collections.singletonList(new EquivalentAddressGroup( - new SocketAddress(){}, - Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); - handleResult = lb.tryHandleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(Attributes.EMPTY) - .build()); - - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - verify(channelLogger).log( - eq(ChannelLogLevel.INFO), - eq("Load balancer changed from {0} to {1}"), - eq(testLbBalancer.getClass().getSimpleName()), eq("GrpclbLoadBalancer")); - - verifyNoMoreInteractions(channelLogger); - } - - @Test - public void parseLoadBalancerConfig_failedOnUnknown() throws Exception { - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"magic_balancer\": {} } ] }"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed.getError()).isNotNull(); - assertThat(parsed.getError().getDescription()) - .isEqualTo("None of [magic_balancer] specified by Service Config are available."); - } - - @Test - public void parseLoadBalancerPolicy_failedOnUnknown() throws Exception { - Map serviceConfig = - parseConfig("{\"loadBalancingPolicy\": \"magic_balancer\"}"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed.getError()).isNotNull(); - assertThat(parsed.getError().getDescription()) - .isEqualTo("None of [magic_balancer] specified by Service Config are available."); - } - - @Test - public void parseLoadBalancerConfig_multipleValidPolicies() throws Exception { - Map serviceConfig = - parseConfig( - "{\"loadBalancingConfig\": [" - + "{\"round_robin\": {}}," - + "{\"test_lb\": {} } ] }"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed).isNotNull(); - assertThat(parsed.getError()).isNull(); - assertThat(parsed.getConfig()).isInstanceOf(PolicySelection.class); - assertThat(((PolicySelection) parsed.getConfig()).provider.getClass().getName()) - .isEqualTo("io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - } - - @Test - public void parseLoadBalancerConfig_policyShouldBeIgnoredIfConfigExists() throws Exception { - Map serviceConfig = - parseConfig( - "{\"loadBalancingConfig\": [{\"round_robin\": {} } ]," - + "\"loadBalancingPolicy\": \"pick_first\" }"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed).isNotNull(); - assertThat(parsed.getError()).isNull(); - assertThat(parsed.getConfig()).isInstanceOf(PolicySelection.class); - assertThat(((PolicySelection) parsed.getConfig()).provider.getClass().getName()) - .isEqualTo("io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); - } - - @Test - public void parseLoadBalancerConfig_policyShouldBeIgnoredEvenIfUnknownPolicyExists() - throws Exception { - Map serviceConfig = - parseConfig( - "{\"loadBalancingConfig\": [{\"magic_balancer\": {} } ]," - + "\"loadBalancingPolicy\": \"round_robin\" }"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed.getError()).isNotNull(); - assertThat(parsed.getError().getDescription()) - .isEqualTo("None of [magic_balancer] specified by Service Config are available."); - } - - @Test - @SuppressWarnings("unchecked") - public void parseLoadBalancerConfig_firstInvalidPolicy() throws Exception { - when(testLbBalancerProvider.parseLoadBalancingPolicyConfig(any(Map.class))) - .thenReturn(ConfigOrError.fromError(Status.UNKNOWN)); - Map serviceConfig = - parseConfig( - "{\"loadBalancingConfig\": [" - + "{\"test_lb\": {}}," - + "{\"round_robin\": {} } ] }"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed).isNotNull(); - assertThat(parsed.getConfig()).isNull(); - assertThat(parsed.getError()).isEqualTo(Status.UNKNOWN); - } - - @Test - @SuppressWarnings("unchecked") - public void parseLoadBalancerConfig_firstValidSecondInvalidPolicy() throws Exception { - when(testLbBalancerProvider.parseLoadBalancingPolicyConfig(any(Map.class))) - .thenReturn(ConfigOrError.fromError(Status.UNKNOWN)); - Map serviceConfig = - parseConfig( - "{\"loadBalancingConfig\": [" - + "{\"round_robin\": {}}," - + "{\"test_lb\": {} } ] }"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed).isNotNull(); - assertThat(parsed.getConfig()).isNotNull(); - assertThat(((PolicySelection) parsed.getConfig()).config).isNotNull(); - } - - @Test - public void parseLoadBalancerConfig_someProvidesAreNotAvailable() throws Exception { - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ " - + "{\"magic_balancer\": {} }," - + "{\"round_robin\": {}} ] }"); - ConfigOrError parsed = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger); - assertThat(parsed).isNotNull(); - assertThat(parsed.getConfig()).isNotNull(); - assertThat(((PolicySelection) parsed.getConfig()).config).isNotNull(); - verify(channelLogger).log( - eq(ChannelLogLevel.DEBUG), - eq("{0} specified by Service Config are not available"), - eq(new ArrayList<>(Collections.singletonList("magic_balancer")))); - } - - - public static class ForwardingLoadBalancer extends LoadBalancer { - private final LoadBalancer delegate; - - public ForwardingLoadBalancer(LoadBalancer delegate) { - this.delegate = delegate; - } - - protected LoadBalancer delegate() { - return delegate; - } - - @Override - @Deprecated - public void handleResolvedAddressGroups( - List servers, Attributes attributes) { - delegate().handleResolvedAddressGroups(servers, attributes); - } - - @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - delegate().handleResolvedAddresses(resolvedAddresses); - } - - @Override - public void handleNameResolutionError(Status error) { - delegate().handleNameResolutionError(error); - } - - @Override - public void shutdown() { - delegate().shutdown(); - } - } - - @SuppressWarnings("unchecked") - private static Map parseConfig(String json) throws Exception { - return (Map) JsonParser.parse(json); - } - - private static class TestLoadBalancer extends ForwardingLoadBalancer { - TestLoadBalancer() { - super(null); - } - } - - private class TestHelper extends ForwardingLoadBalancerHelper { - @Override - protected Helper delegate() { - return null; - } - - @Override - public ChannelLogger getChannelLogger() { - return channelLogger; - } - - @Override - public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) { - // noop - } - } - - private static class TestSubchannel extends Subchannel { - TestSubchannel(CreateSubchannelArgs args) { - this.addrs = args.getAddresses(); - this.attrs = args.getAttributes(); - } - - List addrs; - final Attributes attrs; - - @Override - public void start(SubchannelStateListener listener) { - } - - @Override - public void shutdown() { - } - - @Override - public void requestConnection() { - } - - @Override - public List getAllAddresses() { - return addrs; - } - - @Override - public Attributes getAttributes() { - return attrs; - } - - @Override - public void updateAddresses(List addrs) { - Preconditions.checkNotNull(addrs, "addrs"); - this.addrs = addrs; - } - } - - private static class FakeLoadBalancerProvider extends LoadBalancerProvider { - private final String policyName; - private final LoadBalancer balancer; - private final AtomicReference nextParsedLbPolicyConfig; - - FakeLoadBalancerProvider( - String policyName, - LoadBalancer balancer, - AtomicReference nextParsedLbPolicyConfig) { - this.policyName = policyName; - this.balancer = balancer; - this.nextParsedLbPolicyConfig = nextParsedLbPolicyConfig; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public int getPriority() { - return 5; - } - - @Override - public String getPolicyName() { - return policyName; - } - - @Override - public LoadBalancer newLoadBalancer(Helper helper) { - return balancer; - } - - @Override - public ConfigOrError parseLoadBalancingPolicyConfig( - Map rawLoadBalancingPolicyConfig) { - if (nextParsedLbPolicyConfig == null) { - return super.parseLoadBalancingPolicyConfig(rawLoadBalancingPolicyConfig); - } - return nextParsedLbPolicyConfig.get(); - } - } -} diff --git a/core/src/test/java/io/grpc/internal/HedgingPolicyTest.java b/core/src/test/java/io/grpc/internal/HedgingPolicyTest.java index 8280623286..7c640862f8 100644 --- a/core/src/test/java/io/grpc/internal/HedgingPolicyTest.java +++ b/core/src/test/java/io/grpc/internal/HedgingPolicyTest.java @@ -59,10 +59,17 @@ public class HedgingPolicyTest { @SuppressWarnings("unchecked") Map serviceConfig = (Map) serviceConfigObj; - ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( - /* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 3, - /* maxHedgedAttemptsLimit = */ 4); - serviceConfigInterceptor.handleUpdate(serviceConfig); + ServiceConfigInterceptor serviceConfigInterceptor = + new ServiceConfigInterceptor(/* retryEnabled= */ true); + serviceConfigInterceptor + .handleUpdate( + ManagedChannelServiceConfig + .fromServiceConfig( + serviceConfig, + /* retryEnabled= */ true, + /* maxRetryAttemptsLimit= */ 3, + /* maxHedgedAttemptsLimit= */ 4, + /* loadBalancingConfig= */ null)); MethodDescriptor.Builder builder = TestMethodDescriptors.voidMethod().toBuilder(); @@ -131,10 +138,17 @@ public class HedgingPolicyTest { @SuppressWarnings("unchecked") Map serviceConfig = (Map) serviceConfigObj; - ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( - /* retryEnabled = */ false, /* maxRetryAttemptsLimit = */ 3, - /* maxHedgedAttemptsLimit = */ 4); - serviceConfigInterceptor.handleUpdate(serviceConfig); + ServiceConfigInterceptor serviceConfigInterceptor = + new ServiceConfigInterceptor(/* retryEnabled= */ false); + serviceConfigInterceptor + .handleUpdate( + ManagedChannelServiceConfig + .fromServiceConfig( + serviceConfig, + /* retryEnabled= */ false, + /* maxRetryAttemptsLimit= */ 3, + /* maxHedgedAttemptsLimit= */ 4, + /* loadBalancingConfig= */ null)); MethodDescriptor.Builder builder = TestMethodDescriptors.voidMethod().toBuilder(); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java index 191533333c..22cd9879e8 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java @@ -16,6 +16,7 @@ package io.grpc.internal; +import static com.google.common.truth.Truth.assertThat; import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static org.junit.Assert.assertEquals; @@ -87,7 +88,6 @@ import org.mockito.junit.MockitoRule; /** * Unit tests for {@link ManagedChannelImpl}'s idle mode. */ -@Deprecated // migrate to ManagedChannelImplIdlenessTest2 @RunWith(JUnit4.class) public class ManagedChannelImplIdlenessTest { @Rule @@ -234,9 +234,12 @@ public class ManagedChannelImplIdlenessTest { .setAttributes(Attributes.EMPTY) .build(); nameResolverListenerCaptor.getValue().onResult(resolutionResult); - verify(mockLoadBalancer).handleResolvedAddresses( - ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) - .build()); + + ArgumentCaptor resolvedAddressCaptor = + ArgumentCaptor.forClass(ResolvedAddresses.class); + verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + assertThat(resolvedAddressCaptor.getValue().getAddresses()) + .containsExactlyElementsIn(servers); } @Test diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest2.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest2.java deleted file mode 100644 index 840bdaa22d..0000000000 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest2.java +++ /dev/null @@ -1,560 +0,0 @@ -/* - * Copyright 2016 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.internal; - -import static com.google.common.truth.Truth.assertThat; -import static io.grpc.ConnectivityState.READY; -import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.AdditionalAnswers.delegatesTo; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -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.when; - -import com.google.common.collect.Lists; -import io.grpc.Attributes; -import io.grpc.CallOptions; -import io.grpc.ChannelLogger; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.ConnectivityState; -import io.grpc.EquivalentAddressGroup; -import io.grpc.IntegerMarshaller; -import io.grpc.LoadBalancer; -import io.grpc.LoadBalancer.CreateSubchannelArgs; -import io.grpc.LoadBalancer.Helper; -import io.grpc.LoadBalancer.PickResult; -import io.grpc.LoadBalancer.PickSubchannelArgs; -import io.grpc.LoadBalancer.ResolvedAddresses; -import io.grpc.LoadBalancer.Subchannel; -import io.grpc.LoadBalancer.SubchannelPicker; -import io.grpc.LoadBalancer.SubchannelStateListener; -import io.grpc.LoadBalancerProvider; -import io.grpc.LoadBalancerRegistry; -import io.grpc.ManagedChannel; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.MethodDescriptor.MethodType; -import io.grpc.NameResolver; -import io.grpc.NameResolver.ResolutionResult; -import io.grpc.Status; -import io.grpc.StringMarshaller; -import io.grpc.internal.FakeClock.ScheduledTask; -import io.grpc.internal.TestUtils.MockClientTransportInfo; -import java.net.SocketAddress; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -/** - * Unit tests for {@link ManagedChannelImpl2}'s idle mode. - */ -@RunWith(JUnit4.class) -public class ManagedChannelImplIdlenessTest2 { - @Rule - public final MockitoRule mocks = MockitoJUnit.rule(); - private final FakeClock timer = new FakeClock(); - private final FakeClock executor = new FakeClock(); - private final FakeClock oobExecutor = new FakeClock(); - private static final String AUTHORITY = "fakeauthority"; - private static final String USER_AGENT = "fakeagent"; - private static final long IDLE_TIMEOUT_SECONDS = 30; - private static final String MOCK_POLICY_NAME = "mock_lb"; - private ManagedChannelImpl2 channel; - - private final MethodDescriptor method = - MethodDescriptor.newBuilder() - .setType(MethodType.UNKNOWN) - .setFullMethodName("service/method") - .setRequestMarshaller(new StringMarshaller()) - .setResponseMarshaller(new IntegerMarshaller()) - .build(); - - private final List servers = Lists.newArrayList(); - private final ObjectPool executorPool = - new FixedObjectPool(executor.getScheduledExecutorService()); - private final ObjectPool oobExecutorPool = - new FixedObjectPool(oobExecutor.getScheduledExecutorService()); - - @Mock private ClientTransportFactory mockTransportFactory; - @Mock private LoadBalancer mockLoadBalancer; - @Mock private SubchannelStateListener subchannelStateListener; - private final LoadBalancerProvider mockLoadBalancerProvider = - mock(LoadBalancerProvider.class, delegatesTo(new LoadBalancerProvider() { - @Override - public LoadBalancer newLoadBalancer(Helper helper) { - return mockLoadBalancer; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public int getPriority() { - return 999; - } - - @Override - public String getPolicyName() { - return MOCK_POLICY_NAME; - } - })); - - @Mock private NameResolver mockNameResolver; - @Mock private NameResolver.Factory mockNameResolverFactory; - @Mock private ClientCall.Listener mockCallListener; - @Mock private ClientCall.Listener mockCallListener2; - @Captor private ArgumentCaptor nameResolverListenerCaptor; - private BlockingQueue newTransports; - - @Before - @SuppressWarnings("deprecation") // For NameResolver.Listener - public void setUp() { - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - when(mockNameResolver.getServiceAuthority()).thenReturn(AUTHORITY); - when(mockNameResolverFactory - .newNameResolver(any(URI.class), any(NameResolver.Args.class))) - .thenReturn(mockNameResolver); - when(mockTransportFactory.getScheduledExecutorService()) - .thenReturn(timer.getScheduledExecutorService()); - - class Builder extends AbstractManagedChannelImplBuilder { - Builder(String target) { - super(target); - } - - @Override protected ClientTransportFactory buildTransportFactory() { - throw new UnsupportedOperationException(); - } - - @Override public Builder usePlaintext() { - throw new UnsupportedOperationException(); - } - } - - Builder builder = new Builder("fake://target") - .nameResolverFactory(mockNameResolverFactory) - .defaultLoadBalancingPolicy(MOCK_POLICY_NAME) - .idleTimeout(IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .userAgent(USER_AGENT); - builder.executorPool = executorPool; - channel = new ManagedChannelImpl2( - builder, mockTransportFactory, new FakeBackoffPolicyProvider(), - oobExecutorPool, timer.getStopwatchSupplier(), - Collections.emptyList(), - TimeProvider.SYSTEM_TIME_PROVIDER); - newTransports = TestUtils.captureTransports(mockTransportFactory); - - for (int i = 0; i < 2; i++) { - ArrayList addrs = Lists.newArrayList(); - for (int j = 0; j < 2; j++) { - addrs.add(new FakeSocketAddress("servergroup" + i + "server" + j)); - } - servers.add(new EquivalentAddressGroup(addrs)); - } - verify(mockNameResolverFactory).newNameResolver(any(URI.class), any(NameResolver.Args.class)); - // Verify the initial idleness - verify(mockLoadBalancerProvider, never()).newLoadBalancer(any(Helper.class)); - verify(mockTransportFactory, never()).newClientTransport( - any(SocketAddress.class), - any(ClientTransportFactory.ClientTransportOptions.class), - any(ChannelLogger.class)); - verify(mockNameResolver, never()).start(any(NameResolver.Listener.class)); - verify(mockNameResolver, never()).start(any(NameResolver.Listener2.class)); - } - - @After - public void allPendingTasksAreRun() { - Collection pendingTimerTasks = timer.getPendingTasks(); - for (ScheduledTask a : pendingTimerTasks) { - assertFalse(Rescheduler.isEnabled(a.command)); - } - assertEquals(executor.getPendingTasks() + " should be empty", 0, executor.numPendingTasks()); - } - - @After - public void cleanUp() { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - - @Test - public void newCallExitsIdleness() throws Exception { - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - - verify(mockNameResolver).start(nameResolverListenerCaptor.capture()); - // Simulate new address resolved to make sure the LoadBalancer is correctly linked to - // the NameResolver. - ResolutionResult resolutionResult = - ResolutionResult.newBuilder() - .setAddresses(servers) - .setAttributes(Attributes.EMPTY) - .build(); - nameResolverListenerCaptor.getValue().onResult(resolutionResult); - - ArgumentCaptor resolvedAddressCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); - assertThat(resolvedAddressCaptor.getValue().getAddresses()) - .containsExactlyElementsIn(servers); - } - - @Test - public void newCallRefreshesIdlenessTimer() throws Exception { - // First call to exit the initial idleness, then immediately cancel the call. - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - call.cancel("For testing", null); - - // Verify that we have exited the idle mode - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - assertFalse(channel.inUseStateAggregator.isInUse()); - - // Move closer to idleness, but not yet. - timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS); - verify(mockLoadBalancer, never()).shutdown(); - assertFalse(channel.inUseStateAggregator.isInUse()); - - // A new call would refresh the timer - call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - call.cancel("For testing", null); - assertFalse(channel.inUseStateAggregator.isInUse()); - - // ... so that passing the same length of time will not trigger idle mode - timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS); - verify(mockLoadBalancer, never()).shutdown(); - assertFalse(channel.inUseStateAggregator.isInUse()); - - // ... until the time since last call has reached the timeout - timer.forwardTime(1, TimeUnit.SECONDS); - verify(mockLoadBalancer).shutdown(); - assertFalse(channel.inUseStateAggregator.isInUse()); - - // Drain the app executor, which runs the call listeners - verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class)); - assertEquals(2, executor.runDueTasks()); - verify(mockCallListener, times(2)).onClose(any(Status.class), any(Metadata.class)); - } - - @Test - public void delayedTransportHoldsOffIdleness() throws Exception { - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - assertTrue(channel.inUseStateAggregator.isInUse()); - - // As long as the delayed transport is in-use (by the pending RPC), the channel won't go idle. - timer.forwardTime(IDLE_TIMEOUT_SECONDS * 2, TimeUnit.SECONDS); - assertTrue(channel.inUseStateAggregator.isInUse()); - - // Cancelling the only RPC will reset the in-use state. - assertEquals(0, executor.numPendingTasks()); - call.cancel("In test", null); - assertEquals(1, executor.runDueTasks()); - assertFalse(channel.inUseStateAggregator.isInUse()); - // And allow the channel to go idle. - timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS); - verify(mockLoadBalancer, never()).shutdown(); - timer.forwardTime(1, TimeUnit.SECONDS); - verify(mockLoadBalancer).shutdown(); - } - - @Test - public void realTransportsHoldsOffIdleness() throws Exception { - final EquivalentAddressGroup addressGroup = servers.get(1); - - // Start a call, which goes to delayed transport - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - - // Verify that we have exited the idle mode - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - Helper helper = helperCaptor.getValue(); - assertTrue(channel.inUseStateAggregator.isInUse()); - - // Assume LoadBalancer has received an address, then create a subchannel. - Subchannel subchannel = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY); - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo t0 = newTransports.poll(); - t0.listener.transportReady(); - - SubchannelPicker mockPicker = mock(SubchannelPicker.class); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - updateBalancingStateSafely(helper, READY, mockPicker); - // Delayed transport creates real streams in the app executor - executor.runDueTasks(); - - // Delayed transport exits in-use, while real transport has not entered in-use yet. - assertFalse(channel.inUseStateAggregator.isInUse()); - - // Now it's in-use - t0.listener.transportInUse(true); - assertTrue(channel.inUseStateAggregator.isInUse()); - - // As long as the transport is in-use, the channel won't go idle. - timer.forwardTime(IDLE_TIMEOUT_SECONDS * 2, TimeUnit.SECONDS); - assertTrue(channel.inUseStateAggregator.isInUse()); - - t0.listener.transportInUse(false); - assertFalse(channel.inUseStateAggregator.isInUse()); - // And allow the channel to go idle. - timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS); - verify(mockLoadBalancer, never()).shutdown(); - timer.forwardTime(1, TimeUnit.SECONDS); - verify(mockLoadBalancer).shutdown(); - } - - @Test - public void updateSubchannelAddresses_newAddressConnects() { - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); // Create LB - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - Helper helper = helperCaptor.getValue(); - Subchannel subchannel = createSubchannelSafely(helper, servers.get(0), Attributes.EMPTY); - - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo t0 = newTransports.poll(); - t0.listener.transportReady(); - - updateSubchannelAddressesSafely(helper, subchannel, servers.get(1)); - - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo t1 = newTransports.poll(); - t1.listener.transportReady(); - } - - @Test - public void updateSubchannelAddresses_existingAddressDoesNotConnect() { - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); // Create LB - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - Helper helper = helperCaptor.getValue(); - Subchannel subchannel = createSubchannelSafely(helper, servers.get(0), Attributes.EMPTY); - - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo t0 = newTransports.poll(); - t0.listener.transportReady(); - - List changedList = new ArrayList<>(servers.get(0).getAddresses()); - changedList.add(new FakeSocketAddress("aDifferentServer")); - updateSubchannelAddressesSafely(helper, subchannel, new EquivalentAddressGroup(changedList)); - - requestConnectionSafely(helper, subchannel); - assertNull(newTransports.poll()); - } - - @Test - public void oobTransportDoesNotAffectIdleness() { - // Start a call, which goes to delayed transport - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - - // Verify that we have exited the idle mode - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - Helper helper = helperCaptor.getValue(); - - // Fail the RPC - SubchannelPicker failingPicker = mock(SubchannelPicker.class); - when(failingPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withError(Status.UNAVAILABLE)); - updateBalancingStateSafely(helper, TRANSIENT_FAILURE, failingPicker); - executor.runDueTasks(); - verify(mockCallListener).onClose(same(Status.UNAVAILABLE), any(Metadata.class)); - - // ... so that the channel resets its in-use state - assertFalse(channel.inUseStateAggregator.isInUse()); - - // Now make an RPC on an OOB channel - ManagedChannel oob = helper.createOobChannel(servers.get(0), "oobauthority"); - verify(mockTransportFactory, never()) - .newClientTransport( - any(SocketAddress.class), - eq(new ClientTransportFactory.ClientTransportOptions() - .setAuthority("oobauthority") - .setUserAgent(USER_AGENT)), - any(ChannelLogger.class)); - ClientCall oobCall = oob.newCall(method, CallOptions.DEFAULT); - oobCall.start(mockCallListener2, new Metadata()); - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), - eq(new ClientTransportFactory.ClientTransportOptions() - .setAuthority("oobauthority") - .setUserAgent(USER_AGENT)), - any(ChannelLogger.class)); - MockClientTransportInfo oobTransportInfo = newTransports.poll(); - assertEquals(0, newTransports.size()); - // The OOB transport reports in-use state - oobTransportInfo.listener.transportInUse(true); - - // But it won't stop the channel from going idle - verify(mockLoadBalancer, never()).shutdown(); - timer.forwardTime(IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS); - verify(mockLoadBalancer).shutdown(); - } - - @Test - public void updateOobChannelAddresses_newAddressConnects() { - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); // Create LB - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - Helper helper = helperCaptor.getValue(); - ManagedChannel oobChannel = helper.createOobChannel(servers.get(0), "localhost"); - - oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata()); - MockClientTransportInfo t0 = newTransports.poll(); - t0.listener.transportReady(); - - helper.updateOobChannelAddresses(oobChannel, servers.get(1)); - - oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata()); - MockClientTransportInfo t1 = newTransports.poll(); - t1.listener.transportReady(); - } - - @Test - public void updateOobChannelAddresses_existingAddressDoesNotConnect() { - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); // Create LB - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - Helper helper = helperCaptor.getValue(); - ManagedChannel oobChannel = helper.createOobChannel(servers.get(0), "localhost"); - - oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata()); - MockClientTransportInfo t0 = newTransports.poll(); - t0.listener.transportReady(); - - List changedList = new ArrayList<>(servers.get(0).getAddresses()); - changedList.add(new FakeSocketAddress("aDifferentServer")); - helper.updateOobChannelAddresses(oobChannel, new EquivalentAddressGroup(changedList)); - - oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata()); - assertNull(newTransports.poll()); - } - - private static class FakeBackoffPolicyProvider implements BackoffPolicy.Provider { - @Override - public BackoffPolicy get() { - return new BackoffPolicy() { - @Override - public long nextBackoffNanos() { - return 1; - } - }; - } - } - - private static class FakeSocketAddress extends SocketAddress { - final String name; - - FakeSocketAddress(String name) { - this.name = name; - } - - @Override - public String toString() { - return "FakeSocketAddress-" + name; - } - } - - // Helper methods to call methods from SynchronizationContext - private Subchannel createSubchannelSafely( - final Helper helper, final EquivalentAddressGroup addressGroup, final Attributes attrs) { - final AtomicReference resultCapture = new AtomicReference<>(); - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - Subchannel s = helper.createSubchannel(CreateSubchannelArgs.newBuilder() - .setAddresses(addressGroup) - .setAttributes(attrs) - .build()); - s.start(subchannelStateListener); - resultCapture.set(s); - } - }); - return resultCapture.get(); - } - - private static void requestConnectionSafely(Helper helper, final Subchannel subchannel) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - subchannel.requestConnection(); - } - }); - } - - private static void updateBalancingStateSafely( - final Helper helper, final ConnectivityState state, final SubchannelPicker picker) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - helper.updateBalancingState(state, picker); - } - }); - } - - private static void updateSubchannelAddressesSafely( - final Helper helper, final Subchannel subchannel, final EquivalentAddressGroup addrs) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - subchannel.updateAddresses(Collections.singletonList(addrs)); - } - }); - } -} diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 99b05926b6..f272316ded 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -53,7 +53,6 @@ import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import com.google.common.truth.Truth; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; @@ -103,6 +102,7 @@ import io.grpc.ServerMethodDefinition; import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.StringMarshaller; +import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicySelection; import io.grpc.internal.ClientTransportFactory.ClientTransportOptions; import io.grpc.internal.InternalSubchannel.TransportLogger; import io.grpc.internal.ManagedChannelImpl.ScParser; @@ -152,8 +152,9 @@ import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; /** Unit tests for {@link ManagedChannelImpl}. */ -@Deprecated // to be migrated to ManagedChannelImplTest2 @RunWith(JUnit4.class) +// TODO(creamsoup) remove backward compatible check when fully migrated +@SuppressWarnings("deprecation") public class ManagedChannelImplTest { private static final int DEFAULT_PORT = 447; @@ -272,6 +273,8 @@ public class ManagedChannelImplTest { private boolean requestConnection = true; private BlockingQueue transports; private boolean panicExpected; + @Captor + private ArgumentCaptor resolvedAddressCaptor; private ArgumentCaptor streamListenerCaptor = ArgumentCaptor.forClass(ClientStreamListener.class); @@ -749,11 +752,8 @@ public class ManagedChannelImplTest { FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - verify(mockLoadBalancer).handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Arrays.asList(addressGroup)) - .setAttributes(Attributes.EMPTY) - .build()); + verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class); SubchannelStateListener stateListener2 = mock(SubchannelStateListener.class); @@ -970,13 +970,12 @@ public class ManagedChannelImplTest { // Pass a FakeNameResolverFactory with an empty list and LB config FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri).build(); - Map serviceConfig = + Map rawServiceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }"); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextConfigOrError.set(ConfigOrError.fromConfig(parsedServiceConfig)); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); channelBuilder.nameResolverFactory(nameResolverFactory); createChannel(); @@ -985,7 +984,7 @@ public class ManagedChannelImplTest { verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture()); Status status = statusCaptor.getValue(); assertSame(Status.Code.UNAVAILABLE, status.getCode()); - Truth.assertThat(status.getDescription()).startsWith(errorDescription); + assertThat(status.getDescription()).startsWith(errorDescription); // A resolution retry has been scheduled assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); @@ -998,13 +997,18 @@ public class ManagedChannelImplTest { // Pass a FakeNameResolverFactory with an empty list and LB config FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri).build(); - Map serviceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }"); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + String rawLbConfig = "{ \"setting1\": \"high\" }"; + Map rawServiceConfig = + parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": " + rawLbConfig + " } ] }"); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig( + rawServiceConfig, + new PolicySelection( + mockLoadBalancerProvider, + parseConfig(rawLbConfig), + new Object())); + nameResolverFactory.nextConfigOrError.set(ConfigOrError.fromConfig(parsedServiceConfig)); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); channelBuilder.nameResolverFactory(nameResolverFactory); createChannel(); @@ -1018,7 +1022,7 @@ public class ManagedChannelImplTest { Map lbConfig = actualAttrs.get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG); assertEquals(ImmutableMap.of("setting1", "high"), lbConfig); assertSame( - serviceConfig, actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)); + rawServiceConfig, actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)); // A no resolution retry assertEquals(0, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); @@ -1099,10 +1103,8 @@ public class ManagedChannelImplTest { // Simulate name resolution results EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); - inOrder.verify(mockLoadBalancer).handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Arrays.asList(addressGroup)) - .build()); + inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); Subchannel subchannel = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) @@ -1248,10 +1250,9 @@ public class ManagedChannelImplTest { // Simulate name resolution results EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); - inOrder.verify(mockLoadBalancer).handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Arrays.asList(addressGroup)) - .build()); + inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); + Subchannel subchannel = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) @@ -2760,7 +2761,6 @@ public class ManagedChannelImplTest { .setAddresses(Collections.singletonList( new EquivalentAddressGroup( Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setAttributes(Attributes.EMPTY) .build(); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); @@ -2778,7 +2778,6 @@ public class ManagedChannelImplTest { .setAddresses(Collections.singletonList( new EquivalentAddressGroup( Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setAttributes(Attributes.EMPTY) .build(); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); @@ -2800,11 +2799,16 @@ public class ManagedChannelImplTest { Attributes.newBuilder() .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, new HashMap()) .build(); + ManagedChannelServiceConfig mcsc1 = createManagedChannelServiceConfig( + ImmutableMap.of(), + new PolicySelection( + mockLoadBalancerProvider, ImmutableMap.of("foo", "bar"), null)); ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() .setAddresses(Collections.singletonList( new EquivalentAddressGroup( Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) .setAttributes(attributes) + .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) .build(); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); @@ -2821,6 +2825,7 @@ public class ManagedChannelImplTest { new EquivalentAddressGroup( Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) .setAttributes(attributes) + .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) .build(); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); @@ -2838,6 +2843,7 @@ public class ManagedChannelImplTest { new EquivalentAddressGroup( Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) .setAttributes(attributes) + .setServiceConfig(ConfigOrError.fromConfig(ManagedChannelServiceConfig.empty())) .build(); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult3); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); @@ -3187,16 +3193,21 @@ public class ManagedChannelImplTest { name.put("service", "service"); methodConfig.put("name", Arrays.asList(name)); methodConfig.put("retryPolicy", retryPolicy); - Map serviceConfig = new HashMap<>(); - serviceConfig.put("methodConfig", Arrays.asList(methodConfig)); + Map rawServiceConfig = new HashMap<>(); + rawServiceConfig.put("methodConfig", Arrays.asList(methodConfig)); Attributes attributesWithRetryPolicy = Attributes - .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build(); + .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig).build(); FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri) .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) .build(); - nameResolverFactory.nextResolvedAttributes.set(attributesWithRetryPolicy); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); + channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.executor(MoreExecutors.directExecutor()); channelBuilder.enableRetry(); @@ -3296,16 +3307,21 @@ public class ManagedChannelImplTest { name.put("service", "service"); methodConfig.put("name", Arrays.asList(name)); methodConfig.put("hedgingPolicy", hedgingPolicy); - Map serviceConfig = new HashMap<>(); - serviceConfig.put("methodConfig", Arrays.asList(methodConfig)); + Map rawServiceConfig = new HashMap<>(); + rawServiceConfig.put("methodConfig", Arrays.asList(methodConfig)); Attributes attributesWithRetryPolicy = Attributes - .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build(); + .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig).build(); FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri) .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) .build(); - nameResolverFactory.nextResolvedAttributes.set(attributesWithRetryPolicy); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); + channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.executor(MoreExecutors.directExecutor()); channelBuilder.enableRetry(); @@ -3388,6 +3404,8 @@ public class ManagedChannelImplTest { @Test public void badServiceConfigIsRecoverable() throws Exception { + final Map invalidServiceConfig = + parseConfig("{\"loadBalancingConfig\": [{\"kaboom\": {}}]}"); final List addresses = ImmutableList.of(new EquivalentAddressGroup(new SocketAddress() {})); final class FakeNameResolver extends NameResolver { @@ -3407,9 +3425,11 @@ public class ManagedChannelImplTest { .setAttributes( Attributes.newBuilder() .set( - GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, - ImmutableMap.of("loadBalancingPolicy", "kaboom")) + GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, invalidServiceConfig) .build()) + .setServiceConfig( + ConfigOrError.fromError( + Status.INTERNAL.withDescription("kaboom is invalid"))) .build()); } @@ -3454,23 +3474,31 @@ public class ManagedChannelImplTest { ListenableFuture future1 = ClientCalls.futureUnaryCall(call1, null); executor.runDueTasks(); try { - future1.get(); + future1.get(1, TimeUnit.SECONDS); Assert.fail(); } catch (ExecutionException e) { assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("kaboom"); } // ok the service config is bad, let's fix it. - + Map rawServiceConfig = + parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); + Object fakeLbConfig = new Object(); + PolicySelection lbConfigs = + new PolicySelection( + mockLoadBalancerProvider, rawServiceConfig, fakeLbConfig); + mockLoadBalancerProvider.parseLoadBalancingPolicyConfig(rawServiceConfig); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, lbConfigs); factory.resolver.listener.onResult( ResolutionResult.newBuilder() .setAddresses(addresses) .setAttributes( Attributes.newBuilder() .set( - GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, - ImmutableMap.of("loadBalancingPolicy", "round_robin")) + GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig) .build()) + .setServiceConfig(ConfigOrError.fromConfig(managedChannelServiceConfig)) .build()); ClientCall call2 = mychannel.newCall( @@ -3621,7 +3649,8 @@ public class ManagedChannelImplTest { retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit, - autoConfiguredLoadBalancerFactory); + autoConfiguredLoadBalancerFactory, + mock(ChannelLogger.class)); ConfigOrError coe = parser.parseServiceConfig(ImmutableMap.of()); @@ -3643,7 +3672,8 @@ public class ManagedChannelImplTest { retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit, - autoConfiguredLoadBalancerFactory); + autoConfiguredLoadBalancerFactory, + mock(ChannelLogger.class)); ConfigOrError coe = parser.parseServiceConfig(ImmutableMap.of("methodConfig", "bogus")); @@ -3666,14 +3696,15 @@ public class ManagedChannelImplTest { retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit, - autoConfiguredLoadBalancerFactory); + autoConfiguredLoadBalancerFactory, + mock(ChannelLogger.class)); ConfigOrError coe = parser.parseServiceConfig(ImmutableMap.of("loadBalancingConfig", ImmutableList.of())); assertThat(coe.getError()).isNull(); ManagedChannelServiceConfig cfg = (ManagedChannelServiceConfig) coe.getConfig(); - assertThat(cfg.getLoadBalancingConfig()).isEqualTo(null); + assertThat(cfg.getLoadBalancingConfig()).isNull(); } @Test @@ -3686,15 +3717,15 @@ public class ManagedChannelImplTest { channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.disableServiceConfigLookUp(); - Map serviceConfig = + Map rawServiceConfig = parseConfig("{\"methodConfig\":[{" + "\"name\":[{\"service\":\"SimpleService1\"}]," + "\"waitForReady\":true}]}"); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); createChannel(); @@ -3703,7 +3734,7 @@ public class ManagedChannelImplTest { verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)).isNull(); + assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)).isEmpty(); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); @@ -3725,12 +3756,12 @@ public class ManagedChannelImplTest { + "\"waitForReady\":true}]}"); channelBuilder.defaultServiceConfig(defaultServiceConfig); - Map serviceConfig = new HashMap<>(); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + Map rawServiceConfig = new HashMap<>(); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); createChannel(); @@ -3757,15 +3788,15 @@ public class ManagedChannelImplTest { .setServers(ImmutableList.of(addressGroup)).build(); channelBuilder.nameResolverFactory(nameResolverFactory); - Map serviceConfig = + Map rawServiceConfig = parseConfig("{\"methodConfig\":[{" + "\"name\":[{\"service\":\"SimpleService1\"}]," + "\"waitForReady\":true}]}"); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); createChannel(); ArgumentCaptor resultCaptor = @@ -3775,19 +3806,19 @@ public class ManagedChannelImplTest { Attributes actualAttrs = resultCaptor.getValue().getAttributes(); assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(serviceConfig); + .isEqualTo(rawServiceConfig); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); // new config - serviceConfig = + rawServiceConfig = parseConfig("{\"methodConfig\":[{" + "\"name\":[{\"service\":\"SimpleService1\"}]," + "\"waitForReady\":false}]}"); - serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); nameResolverFactory.allResolved(); resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); @@ -3795,7 +3826,7 @@ public class ManagedChannelImplTest { assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); actualAttrs = resultCaptor.getValue().getAttributes(); assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(serviceConfig); + .isEqualTo(rawServiceConfig); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); @@ -3816,15 +3847,15 @@ public class ManagedChannelImplTest { + "\"waitForReady\":true}]}"); channelBuilder.defaultServiceConfig(defaultServiceConfig); - Map serviceConfig = + Map rawServiceConfig = parseConfig("{\"methodConfig\":[{" + "\"name\":[{\"service\":\"SimpleService2\"}]," + "\"waitForReady\":false}]}"); - Attributes serviceConfigAttrs = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); createChannel(); ArgumentCaptor resultCaptor = @@ -3833,7 +3864,7 @@ public class ManagedChannelImplTest { assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); Attributes actualAttrs = resultCaptor.getValue().getAttributes(); assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(serviceConfig); + .isEqualTo(rawServiceConfig); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); @@ -3855,8 +3886,8 @@ public class ManagedChannelImplTest { + "\"waitForReady\":true}]}"); channelBuilder.defaultServiceConfig(defaultServiceConfig); - Attributes serviceConfigAttrs = Attributes.EMPTY; - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + nameResolverFactory.nextRawServiceConfig.set(null); + nameResolverFactory.nextConfigOrError.set(null); createChannel(); ArgumentCaptor resultCaptor = @@ -3881,8 +3912,12 @@ public class ManagedChannelImplTest { .setServers(ImmutableList.of(addressGroup)).build(); channelBuilder.nameResolverFactory(nameResolverFactory); - Attributes serviceConfigAttrs = Attributes.EMPTY; - nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); + Map rawServiceConfig = Collections.emptyMap(); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); createChannel(); ArgumentCaptor resultCaptor = @@ -3890,7 +3925,7 @@ public class ManagedChannelImplTest { verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)).isNull(); + assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)).isEmpty(); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); @@ -3983,10 +4018,9 @@ public class ManagedChannelImplTest { final List servers; final boolean resolvedAtStart; final Status error; - final ArrayList resolvers = new ArrayList<>(); - // The Attributes argument of the next invocation of listener.onAddresses(servers, attrs) - final AtomicReference nextResolvedAttributes = - new AtomicReference<>(Attributes.EMPTY); + final ArrayList resolvers = new ArrayList<>(); + final AtomicReference nextConfigOrError = new AtomicReference<>(); + final AtomicReference> nextRawServiceConfig = new AtomicReference<>(); FakeNameResolverFactory( URI expectedUri, @@ -4005,7 +4039,8 @@ public class ManagedChannelImplTest { return null; } assertEquals(DEFAULT_PORT, args.getDefaultPort()); - FakeNameResolver resolver = new FakeNameResolver(error); + FakeNameResolverFactory.FakeNameResolver resolver = + new FakeNameResolverFactory.FakeNameResolver(error); resolvers.add(resolver); return resolver; } @@ -4016,7 +4051,7 @@ public class ManagedChannelImplTest { } void allResolved() { - for (FakeNameResolver resolver : resolvers) { + for (FakeNameResolverFactory.FakeNameResolver resolver : resolvers) { resolver.resolved(); } } @@ -4052,11 +4087,22 @@ public class ManagedChannelImplTest { listener.onError(error); return; } - listener.onResult( + ResolutionResult.Builder builder = ResolutionResult.newBuilder() - .setAddresses(servers) - .setAttributes(nextResolvedAttributes.get()) - .build()); + .setAddresses(servers); + ConfigOrError configOrError = nextConfigOrError.get(); + Map rawServiceConfig = nextRawServiceConfig.get(); + if (configOrError != null) { + builder.setServiceConfig(configOrError); + } + if (rawServiceConfig != null) { + builder.setAttributes( + Attributes.newBuilder() + .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig) + .build()); + } + + listener.onResult(builder.build()); } @Override public void shutdown() { @@ -4079,17 +4125,17 @@ public class ManagedChannelImplTest { this.expectedUri = expectedUri; } - Builder setServers(List servers) { + FakeNameResolverFactory.Builder setServers(List servers) { this.servers = servers; return this; } - Builder setResolvedAtStart(boolean resolvedAtStart) { + FakeNameResolverFactory.Builder setResolvedAtStart(boolean resolvedAtStart) { this.resolvedAtStart = resolvedAtStart; return this; } - Builder setError(Status error) { + FakeNameResolverFactory.Builder setError(Status error) { this.error = error; return this; } @@ -4207,4 +4253,11 @@ public class ManagedChannelImplTest { private static Map parseConfig(String json) throws Exception { return (Map) JsonParser.parse(json); } + + private static ManagedChannelServiceConfig createManagedChannelServiceConfig( + Map rawServiceConfig, PolicySelection policySelection) { + // Provides dummy variable for retry related params (not used in this test class) + return ManagedChannelServiceConfig + .fromServiceConfig(rawServiceConfig, true, 3, 4, policySelection); + } } diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest2.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest2.java deleted file mode 100644 index bde0d08caa..0000000000 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest2.java +++ /dev/null @@ -1,4263 +0,0 @@ -/* - * Copyright 2015 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.internal; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.truth.Truth.assertThat; -import static io.grpc.ConnectivityState.CONNECTING; -import static io.grpc.ConnectivityState.IDLE; -import static io.grpc.ConnectivityState.READY; -import static io.grpc.ConnectivityState.SHUTDOWN; -import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; -import static junit.framework.TestCase.assertNotSame; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.AdditionalAnswers.delegatesTo; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -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.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import io.grpc.Attributes; -import io.grpc.BinaryLog; -import io.grpc.CallCredentials; -import io.grpc.CallCredentials.RequestInfo; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ChannelLogger; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.ClientInterceptors; -import io.grpc.ClientStreamTracer; -import io.grpc.ConnectivityState; -import io.grpc.ConnectivityStateInfo; -import io.grpc.Context; -import io.grpc.EquivalentAddressGroup; -import io.grpc.IntegerMarshaller; -import io.grpc.InternalChannelz; -import io.grpc.InternalChannelz.ChannelStats; -import io.grpc.InternalChannelz.ChannelTrace; -import io.grpc.InternalInstrumented; -import io.grpc.LoadBalancer; -import io.grpc.LoadBalancer.CreateSubchannelArgs; -import io.grpc.LoadBalancer.Helper; -import io.grpc.LoadBalancer.PickResult; -import io.grpc.LoadBalancer.PickSubchannelArgs; -import io.grpc.LoadBalancer.ResolvedAddresses; -import io.grpc.LoadBalancer.Subchannel; -import io.grpc.LoadBalancer.SubchannelPicker; -import io.grpc.LoadBalancer.SubchannelStateListener; -import io.grpc.LoadBalancerProvider; -import io.grpc.LoadBalancerRegistry; -import io.grpc.ManagedChannel; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.MethodDescriptor.MethodType; -import io.grpc.NameResolver; -import io.grpc.NameResolver.ConfigOrError; -import io.grpc.NameResolver.ResolutionResult; -import io.grpc.NameResolverRegistry; -import io.grpc.ProxiedSocketAddress; -import io.grpc.ProxyDetector; -import io.grpc.SecurityLevel; -import io.grpc.ServerMethodDefinition; -import io.grpc.Status; -import io.grpc.Status.Code; -import io.grpc.StringMarshaller; -import io.grpc.internal.AutoConfiguredLoadBalancerFactory2.PolicySelection; -import io.grpc.internal.ClientTransportFactory.ClientTransportOptions; -import io.grpc.internal.InternalSubchannel.TransportLogger; -import io.grpc.internal.ManagedChannelImpl2.ScParser; -import io.grpc.internal.TestUtils.MockClientTransportInfo; -import io.grpc.stub.ClientCalls; -import io.grpc.testing.TestMethodDescriptors; -import io.grpc.util.ForwardingSubchannel; -import java.io.IOException; -import java.net.SocketAddress; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; -import javax.annotation.Nullable; -import org.junit.After; -import org.junit.Assert; -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.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.mockito.stubbing.Answer; - -/** Unit tests for {@link ManagedChannelImpl2}. */ -@RunWith(JUnit4.class) -// TODO(creamsoup) remove backward compatible check when fully migrated -@SuppressWarnings("deprecation") -public class ManagedChannelImplTest2 { - private static final int DEFAULT_PORT = 447; - - private static final MethodDescriptor method = - MethodDescriptor.newBuilder() - .setType(MethodType.UNKNOWN) - .setFullMethodName("service/method") - .setRequestMarshaller(new StringMarshaller()) - .setResponseMarshaller(new IntegerMarshaller()) - .build(); - private static final Attributes.Key SUBCHANNEL_ATTR_KEY = - Attributes.Key.create("subchannel-attr-key"); - private static final long RECONNECT_BACKOFF_INTERVAL_NANOS = 10; - private static final String SERVICE_NAME = "fake.example.com"; - private static final String AUTHORITY = SERVICE_NAME; - private static final String USER_AGENT = "userAgent"; - private static final ClientTransportOptions clientTransportOptions = - new ClientTransportOptions() - .setAuthority(AUTHORITY) - .setUserAgent(USER_AGENT); - private static final String TARGET = "fake://" + SERVICE_NAME; - private static final String MOCK_POLICY_NAME = "mock_lb"; - private URI expectedUri; - private final SocketAddress socketAddress = - new SocketAddress() { - @Override - public String toString() { - return "test-addr"; - } - }; - private final SocketAddress socketAddress2 = - new SocketAddress() { - @Override - public String toString() { - return "test-addr"; - } - }; - private final EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(socketAddress); - private final EquivalentAddressGroup addressGroup2 = - new EquivalentAddressGroup(Arrays.asList(socketAddress, socketAddress2)); - private final FakeClock timer = new FakeClock(); - private final FakeClock executor = new FakeClock(); - private final FakeClock balancerRpcExecutor = new FakeClock(); - private static final FakeClock.TaskFilter NAME_RESOLVER_REFRESH_TASK_FILTER = - new FakeClock.TaskFilter() { - @Override - public boolean shouldAccept(Runnable command) { - return command.toString().contains( - ManagedChannelImpl2.DelayedNameResolverRefresh.class.getName()); - } - }; - - private final InternalChannelz channelz = new InternalChannelz(); - - @Rule public final ExpectedException thrown = ExpectedException.none(); - @Rule public final MockitoRule mocks = MockitoJUnit.rule(); - - private ManagedChannelImpl2 channel; - private Helper helper; - @Captor - private ArgumentCaptor statusCaptor; - @Captor - private ArgumentCaptor callOptionsCaptor; - @Mock - private LoadBalancer mockLoadBalancer; - @Mock - private SubchannelStateListener subchannelStateListener; - private final LoadBalancerProvider mockLoadBalancerProvider = - mock(LoadBalancerProvider.class, delegatesTo(new LoadBalancerProvider() { - @Override - public LoadBalancer newLoadBalancer(Helper helper) { - return mockLoadBalancer; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public int getPriority() { - return 999; - } - - @Override - public String getPolicyName() { - return MOCK_POLICY_NAME; - } - })); - - @Captor - private ArgumentCaptor stateInfoCaptor; - @Mock - private SubchannelPicker mockPicker; - @Mock - private ClientTransportFactory mockTransportFactory; - @Mock - private ClientCall.Listener mockCallListener; - @Mock - private ClientCall.Listener mockCallListener2; - @Mock - private ClientCall.Listener mockCallListener3; - @Mock - private ClientCall.Listener mockCallListener4; - @Mock - private ClientCall.Listener mockCallListener5; - @Mock - private ObjectPool executorPool; - @Mock - private ObjectPool balancerRpcExecutorPool; - @Mock - private CallCredentials creds; - @Mock - private Executor offloadExecutor; - private ChannelBuilder channelBuilder; - private boolean requestConnection = true; - private BlockingQueue transports; - private boolean panicExpected; - @Captor - private ArgumentCaptor resolvedAddressCaptor; - - private ArgumentCaptor streamListenerCaptor = - ArgumentCaptor.forClass(ClientStreamListener.class); - - private void createChannel(ClientInterceptor... interceptors) { - checkState(channel == null); - - channel = new ManagedChannelImpl2( - channelBuilder, mockTransportFactory, new FakeBackoffPolicyProvider(), - balancerRpcExecutorPool, timer.getStopwatchSupplier(), Arrays.asList(interceptors), - timer.getTimeProvider()); - - if (requestConnection) { - int numExpectedTasks = 0; - - // Force-exit the initial idle-mode - channel.syncContext.execute(new Runnable() { - @Override - public void run() { - channel.exitIdleMode(); - } - }); - if (channelBuilder.idleTimeoutMillis != ManagedChannelImpl2.IDLE_TIMEOUT_MILLIS_DISABLE) { - numExpectedTasks += 1; - } - - if (getNameResolverRefresh() != null) { - numExpectedTasks += 1; - } - - assertEquals(numExpectedTasks, timer.numPendingTasks()); - - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - helper = helperCaptor.getValue(); - } - } - - @Before - public void setUp() throws Exception { - when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod(); - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - expectedUri = new URI(TARGET); - transports = TestUtils.captureTransports(mockTransportFactory); - when(mockTransportFactory.getScheduledExecutorService()) - .thenReturn(timer.getScheduledExecutorService()); - when(executorPool.getObject()).thenReturn(executor.getScheduledExecutorService()); - when(balancerRpcExecutorPool.getObject()) - .thenReturn(balancerRpcExecutor.getScheduledExecutorService()); - - channelBuilder = - new ChannelBuilder() - .nameResolverFactory(new FakeNameResolverFactory.Builder(expectedUri).build()) - .defaultLoadBalancingPolicy(MOCK_POLICY_NAME) - .userAgent(USER_AGENT) - .idleTimeout( - AbstractManagedChannelImplBuilder.IDLE_MODE_MAX_TIMEOUT_DAYS, TimeUnit.DAYS) - .offloadExecutor(offloadExecutor); - channelBuilder.executorPool = executorPool; - channelBuilder.binlog = null; - channelBuilder.channelz = channelz; - } - - @After - public void allPendingTasksAreRun() throws Exception { - // The "never" verifications in the tests only hold up if all due tasks are done. - // As for timer, although there may be scheduled tasks in a future time, since we don't test - // any time-related behavior in this test suite, we only care the tasks that are due. This - // would ignore any time-sensitive tasks, e.g., back-off and the idle timer. - assertTrue(timer.getDueTasks() + " should be empty", timer.getDueTasks().isEmpty()); - assertEquals(executor.getPendingTasks() + " should be empty", 0, executor.numPendingTasks()); - if (channel != null) { - if (!panicExpected) { - assertFalse(channel.isInPanicMode()); - } - channel.shutdownNow(); - channel = null; - } - } - - @After - public void cleanUp() { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - - @Deprecated - @Test - public void createSubchannel_old_outsideSynchronizationContextShouldLogWarning() { - createChannel(); - final AtomicReference logRef = new AtomicReference<>(); - Handler handler = new Handler() { - @Override - public void publish(LogRecord record) { - logRef.set(record); - } - - @Override - public void flush() { - } - - @Override - public void close() throws SecurityException { - } - }; - Logger logger = Logger.getLogger(ManagedChannelImpl2.class.getName()); - try { - logger.addHandler(handler); - helper.createSubchannel(addressGroup, Attributes.EMPTY); - LogRecord record = logRef.get(); - assertThat(record.getLevel()).isEqualTo(Level.WARNING); - assertThat(record.getMessage()).contains( - "createSubchannel() should be called from SynchronizationContext"); - assertThat(record.getThrown()).isInstanceOf(IllegalStateException.class); - } finally { - logger.removeHandler(handler); - } - } - - @Deprecated - @Test - public void createSubchannel_old_insideSyncContextFollowedByRequestConnectionShouldSucceed() { - createChannel(); - final AtomicReference error = new AtomicReference<>(); - helper.getSynchronizationContext().execute(new Runnable() { - @Override - public void run() { - try { - Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY); - subchannel.requestConnection(); - } catch (Throwable e) { - error.set(e); - } - } - }); - assertThat(error.get()).isNull(); - } - - @Deprecated - @Test - @SuppressWarnings("deprecation") - public void createSubchannel_old_propagateSubchannelStatesToOldApi() { - createChannel(); - - Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY); - subchannel.requestConnection(); - - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - verify(mockLoadBalancer).handleSubchannelState( - same(subchannel), eq(ConnectivityStateInfo.forNonError(CONNECTING))); - - MockClientTransportInfo transportInfo = transports.poll(); - transportInfo.listener.transportReady(); - - verify(mockLoadBalancer).handleSubchannelState( - same(subchannel), eq(ConnectivityStateInfo.forNonError(READY))); - - channel.shutdown(); - verify(mockLoadBalancer).shutdown(); - subchannel.shutdown(); - - verify(mockLoadBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution(); - verify(mockLoadBalancer, atLeast(0)).handleNameResolutionError(any(Status.class)); - // handleSubchannelState() should not be called after shutdown() - verifyNoMoreInteractions(mockLoadBalancer); - } - - @Test - public void createSubchannel_outsideSynchronizationContextShouldThrow() { - createChannel(); - try { - helper.createSubchannel(CreateSubchannelArgs.newBuilder() - .setAddresses(addressGroup) - .build()); - fail("Should throw"); - } catch (IllegalStateException e) { - assertThat(e).hasMessageThat().isEqualTo("Not called from the SynchronizationContext"); - } - } - - @Test - public void idleModeDisabled() { - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build()); - createChannel(); - - // In this test suite, the channel is always created with idle mode disabled. - // No task is scheduled to enter idle mode - assertEquals(0, timer.numPendingTasks()); - assertEquals(0, executor.numPendingTasks()); - } - - @Test - public void immediateDeadlineExceeded() { - createChannel(); - ClientCall call = - channel.newCall(method, CallOptions.DEFAULT.withDeadlineAfter(0, TimeUnit.NANOSECONDS)); - call.start(mockCallListener, new Metadata()); - assertEquals(1, executor.runDueTasks()); - - verify(mockCallListener).onClose(statusCaptor.capture(), any(Metadata.class)); - Status status = statusCaptor.getValue(); - assertSame(Status.DEADLINE_EXCEEDED.getCode(), status.getCode()); - } - - @Test - public void shutdownWithNoTransportsEverCreated() { - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build()); - createChannel(); - verify(executorPool).getObject(); - verify(executorPool, never()).returnObject(any()); - channel.shutdown(); - assertTrue(channel.isShutdown()); - assertTrue(channel.isTerminated()); - verify(executorPool).returnObject(executor.getScheduledExecutorService()); - } - - @Test - public void channelzMembership() throws Exception { - createChannel(); - assertNotNull(channelz.getRootChannel(channel.getLogId().getId())); - assertFalse(channelz.containsSubchannel(channel.getLogId())); - channel.shutdownNow(); - channel.awaitTermination(5, TimeUnit.SECONDS); - assertNull(channelz.getRootChannel(channel.getLogId().getId())); - assertFalse(channelz.containsSubchannel(channel.getLogId())); - } - - @Test - public void channelzMembership_subchannel() throws Exception { - createChannel(); - assertNotNull(channelz.getRootChannel(channel.getLogId().getId())); - - AbstractSubchannel subchannel = - (AbstractSubchannel) createSubchannelSafely( - helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - // subchannels are not root channels - assertNull( - channelz.getRootChannel(subchannel.getInstrumentedInternalSubchannel().getLogId().getId())); - assertTrue( - channelz.containsSubchannel(subchannel.getInstrumentedInternalSubchannel().getLogId())); - assertThat(getStats(channel).subchannels) - .containsExactly(subchannel.getInstrumentedInternalSubchannel()); - - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo transportInfo = transports.poll(); - assertNotNull(transportInfo); - assertTrue(channelz.containsClientSocket(transportInfo.transport.getLogId())); - transportInfo.listener.transportReady(); - - // terminate transport - transportInfo.listener.transportShutdown(Status.CANCELLED); - transportInfo.listener.transportTerminated(); - assertFalse(channelz.containsClientSocket(transportInfo.transport.getLogId())); - - // terminate subchannel - assertTrue( - channelz.containsSubchannel(subchannel.getInstrumentedInternalSubchannel().getLogId())); - shutdownSafely(helper, subchannel); - timer.forwardTime(ManagedChannelImpl2.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS); - timer.runDueTasks(); - assertFalse( - channelz.containsSubchannel(subchannel.getInstrumentedInternalSubchannel().getLogId())); - assertThat(getStats(channel).subchannels).isEmpty(); - - // channel still appears - assertNotNull(channelz.getRootChannel(channel.getLogId().getId())); - } - - @Test - public void channelzMembership_oob() throws Exception { - createChannel(); - OobChannel oob = (OobChannel) helper.createOobChannel(addressGroup, AUTHORITY); - // oob channels are not root channels - assertNull(channelz.getRootChannel(oob.getLogId().getId())); - assertTrue(channelz.containsSubchannel(oob.getLogId())); - assertThat(getStats(channel).subchannels).containsExactly(oob); - assertTrue(channelz.containsSubchannel(oob.getLogId())); - - AbstractSubchannel subchannel = (AbstractSubchannel) oob.getSubchannel(); - assertTrue( - channelz.containsSubchannel(subchannel.getInstrumentedInternalSubchannel().getLogId())); - assertThat(getStats(oob).subchannels) - .containsExactly(subchannel.getInstrumentedInternalSubchannel()); - assertTrue( - channelz.containsSubchannel(subchannel.getInstrumentedInternalSubchannel().getLogId())); - - oob.getSubchannel().requestConnection(); - MockClientTransportInfo transportInfo = transports.poll(); - assertNotNull(transportInfo); - assertTrue(channelz.containsClientSocket(transportInfo.transport.getLogId())); - - // terminate transport - transportInfo.listener.transportShutdown(Status.INTERNAL); - transportInfo.listener.transportTerminated(); - assertFalse(channelz.containsClientSocket(transportInfo.transport.getLogId())); - - // terminate oobchannel - oob.shutdown(); - assertFalse(channelz.containsSubchannel(oob.getLogId())); - assertThat(getStats(channel).subchannels).isEmpty(); - assertFalse( - channelz.containsSubchannel(subchannel.getInstrumentedInternalSubchannel().getLogId())); - - // channel still appears - assertNotNull(channelz.getRootChannel(channel.getLogId().getId())); - } - - @Test - public void callsAndShutdown() { - subtestCallsAndShutdown(false, false); - } - - @Test - public void callsAndShutdownNow() { - subtestCallsAndShutdown(true, false); - } - - /** Make sure shutdownNow() after shutdown() has an effect. */ - @Test - public void callsAndShutdownAndShutdownNow() { - subtestCallsAndShutdown(false, true); - } - - private void subtestCallsAndShutdown(boolean shutdownNow, boolean shutdownNowAfterShutdown) { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - verify(executorPool).getObject(); - ClientStream mockStream = mock(ClientStream.class); - ClientStream mockStream2 = mock(ClientStream.class); - Metadata headers = new Metadata(); - Metadata headers2 = new Metadata(); - - // Configure the picker so that first RPC goes to delayed transport, and second RPC goes to - // real transport. - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - verify(mockTransport).start(any(ManagedClientTransport.Listener.class)); - ManagedClientTransport.Listener transportListener = transportInfo.listener; - when(mockTransport.newStream(same(method), same(headers), same(CallOptions.DEFAULT))) - .thenReturn(mockStream); - when(mockTransport.newStream(same(method), same(headers2), same(CallOptions.DEFAULT))) - .thenReturn(mockStream2); - transportListener.transportReady(); - when(mockPicker.pickSubchannel( - new PickSubchannelArgsImpl(method, headers, CallOptions.DEFAULT))).thenReturn( - PickResult.withNoResult()); - when(mockPicker.pickSubchannel( - new PickSubchannelArgsImpl(method, headers2, CallOptions.DEFAULT))).thenReturn( - PickResult.withSubchannel(subchannel)); - updateBalancingStateSafely(helper, READY, mockPicker); - - // First RPC, will be pending - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - call.start(mockCallListener, headers); - - verify(mockTransport, never()) - .newStream(same(method), same(headers), same(CallOptions.DEFAULT)); - - // Second RPC, will be assigned to the real transport - ClientCall call2 = channel.newCall(method, CallOptions.DEFAULT); - call2.start(mockCallListener2, headers2); - verify(mockTransport).newStream(same(method), same(headers2), same(CallOptions.DEFAULT)); - verify(mockTransport).newStream(same(method), same(headers2), same(CallOptions.DEFAULT)); - verify(mockStream2).start(any(ClientStreamListener.class)); - - // Shutdown - if (shutdownNow) { - channel.shutdownNow(); - } else { - channel.shutdown(); - if (shutdownNowAfterShutdown) { - channel.shutdownNow(); - shutdownNow = true; - } - } - assertTrue(channel.isShutdown()); - assertFalse(channel.isTerminated()); - assertThat(nameResolverFactory.resolvers).hasSize(1); - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - - // Further calls should fail without going to the transport - ClientCall call3 = channel.newCall(method, CallOptions.DEFAULT); - call3.start(mockCallListener3, headers2); - timer.runDueTasks(); - executor.runDueTasks(); - - verify(mockCallListener3).onClose(statusCaptor.capture(), any(Metadata.class)); - assertSame(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode()); - - if (shutdownNow) { - // LoadBalancer and NameResolver are shut down as soon as delayed transport is terminated. - verify(mockLoadBalancer).shutdown(); - assertTrue(nameResolverFactory.resolvers.get(0).shutdown); - // call should have been aborted by delayed transport - executor.runDueTasks(); - verify(mockCallListener).onClose(same(ManagedChannelImpl2.SHUTDOWN_NOW_STATUS), - any(Metadata.class)); - } else { - // LoadBalancer and NameResolver are still running. - verify(mockLoadBalancer, never()).shutdown(); - assertFalse(nameResolverFactory.resolvers.get(0).shutdown); - // call and call2 are still alive, and can still be assigned to a real transport - SubchannelPicker picker2 = mock(SubchannelPicker.class); - when(picker2.pickSubchannel(new PickSubchannelArgsImpl(method, headers, CallOptions.DEFAULT))) - .thenReturn(PickResult.withSubchannel(subchannel)); - updateBalancingStateSafely(helper, READY, picker2); - executor.runDueTasks(); - verify(mockTransport).newStream(same(method), same(headers), same(CallOptions.DEFAULT)); - verify(mockStream).start(any(ClientStreamListener.class)); - } - - // After call is moved out of delayed transport, LoadBalancer, NameResolver and the transports - // will be shutdown. - verify(mockLoadBalancer).shutdown(); - assertTrue(nameResolverFactory.resolvers.get(0).shutdown); - - if (shutdownNow) { - // Channel shutdownNow() all subchannels after shutting down LoadBalancer - verify(mockTransport).shutdownNow(ManagedChannelImpl2.SHUTDOWN_NOW_STATUS); - } else { - verify(mockTransport, never()).shutdownNow(any(Status.class)); - } - // LoadBalancer should shutdown the subchannel - shutdownSafely(helper, subchannel); - if (shutdownNow) { - verify(mockTransport).shutdown(same(ManagedChannelImpl2.SHUTDOWN_NOW_STATUS)); - } else { - verify(mockTransport).shutdown(same(ManagedChannelImpl2.SHUTDOWN_STATUS)); - } - - // Killing the remaining real transport will terminate the channel - transportListener.transportShutdown(Status.UNAVAILABLE); - assertFalse(channel.isTerminated()); - verify(executorPool, never()).returnObject(any()); - transportListener.transportTerminated(); - assertTrue(channel.isTerminated()); - verify(executorPool).returnObject(executor.getScheduledExecutorService()); - verifyNoMoreInteractions(balancerRpcExecutorPool); - - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - verify(mockTransportFactory).close(); - verify(mockTransport, atLeast(0)).getLogId(); - verifyNoMoreInteractions(mockTransport); - } - - @Test - public void noMoreCallbackAfterLoadBalancerShutdown() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed"); - createChannel(); - - FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); - assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); - - SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class); - SubchannelStateListener stateListener2 = mock(SubchannelStateListener.class); - Subchannel subchannel1 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener1); - Subchannel subchannel2 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener2); - requestConnectionSafely(helper, subchannel1); - requestConnectionSafely(helper, subchannel2); - verify(mockTransportFactory, times(2)) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo1 = transports.poll(); - MockClientTransportInfo transportInfo2 = transports.poll(); - - // LoadBalancer receives all sorts of callbacks - transportInfo1.listener.transportReady(); - - verify(stateListener1, times(2)).onSubchannelState(stateInfoCaptor.capture()); - assertSame(CONNECTING, stateInfoCaptor.getAllValues().get(0).getState()); - assertSame(READY, stateInfoCaptor.getAllValues().get(1).getState()); - - verify(stateListener2).onSubchannelState(stateInfoCaptor.capture()); - assertSame(CONNECTING, stateInfoCaptor.getValue().getState()); - - resolver.listener.onError(resolutionError); - verify(mockLoadBalancer).handleNameResolutionError(resolutionError); - - verifyNoMoreInteractions(mockLoadBalancer); - - channel.shutdown(); - verify(mockLoadBalancer).shutdown(); - verifyNoMoreInteractions(stateListener1, stateListener2); - - // LoadBalancer will normally shutdown all subchannels - subchannel1.shutdown(); - subchannel2.shutdown(); - - // Since subchannels are shutdown, SubchannelStateListeners will only get SHUTDOWN regardless of - // the transport states. - transportInfo1.listener.transportShutdown(Status.UNAVAILABLE); - transportInfo2.listener.transportReady(); - verify(stateListener1).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); - verify(stateListener2).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); - verifyNoMoreInteractions(stateListener1, stateListener2); - - // No more callback should be delivered to LoadBalancer after it's shut down - resolver.listener.onError(resolutionError); - resolver.resolved(); - verifyNoMoreInteractions(mockLoadBalancer); - } - - @Test - public void interceptor() throws Exception { - final AtomicLong atomic = new AtomicLong(); - ClientInterceptor interceptor = new ClientInterceptor() { - @Override - public ClientCall interceptCall( - MethodDescriptor method, CallOptions callOptions, - Channel next) { - atomic.set(1); - return next.newCall(method, callOptions); - } - }; - createChannel(interceptor); - assertNotNull(channel.newCall(method, CallOptions.DEFAULT)); - assertEquals(1, atomic.get()); - } - - @Test - public void callOptionsExecutor() { - Metadata headers = new Metadata(); - ClientStream mockStream = mock(ClientStream.class); - FakeClock callExecutor = new FakeClock(); - createChannel(); - - // Start a call with a call executor - CallOptions options = - CallOptions.DEFAULT.withExecutor(callExecutor.getScheduledExecutorService()); - ClientCall call = channel.newCall(method, options); - call.start(mockCallListener, headers); - - // Make the transport available - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - verify(mockTransportFactory, never()) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - requestConnectionSafely(helper, subchannel); - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class))) - .thenReturn(mockStream); - transportListener.transportReady(); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - assertEquals(0, callExecutor.numPendingTasks()); - updateBalancingStateSafely(helper, READY, mockPicker); - - // Real streams are started in the call executor if they were previously buffered. - assertEquals(1, callExecutor.runDueTasks()); - verify(mockTransport).newStream(same(method), same(headers), same(options)); - verify(mockStream).start(streamListenerCaptor.capture()); - - // Call listener callbacks are also run in the call executor - ClientStreamListener streamListener = streamListenerCaptor.getValue(); - Metadata trailers = new Metadata(); - assertEquals(0, callExecutor.numPendingTasks()); - streamListener.closed(Status.CANCELLED, trailers); - verify(mockCallListener, never()).onClose(same(Status.CANCELLED), same(trailers)); - assertEquals(1, callExecutor.runDueTasks()); - verify(mockCallListener).onClose(same(Status.CANCELLED), same(trailers)); - - - transportListener.transportShutdown(Status.UNAVAILABLE); - transportListener.transportTerminated(); - - // Clean up as much as possible to allow the channel to terminate. - shutdownSafely(helper, subchannel); - timer.forwardNanos( - TimeUnit.SECONDS.toNanos(ManagedChannelImpl2.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS)); - } - - @Test - public void nameResolutionFailed() { - Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error")); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .setError(error) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - // Name resolution is started as soon as channel is created. - createChannel(); - FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); - verify(mockLoadBalancer).handleNameResolutionError(same(error)); - assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); - - timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS - 1); - assertEquals(0, resolver.refreshCalled); - - timer.forwardNanos(1); - assertEquals(1, resolver.refreshCalled); - verify(mockLoadBalancer, times(2)).handleNameResolutionError(same(error)); - - // Verify an additional name resolution failure does not schedule another timer - resolver.refresh(); - verify(mockLoadBalancer, times(3)).handleNameResolutionError(same(error)); - assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); - - // Allow the next refresh attempt to succeed - resolver.error = null; - - // For the second attempt, the backoff should occur at RECONNECT_BACKOFF_INTERVAL_NANOS * 2 - timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS * 2 - 1); - assertEquals(2, resolver.refreshCalled); - timer.forwardNanos(1); - assertEquals(3, resolver.refreshCalled); - assertEquals(0, timer.numPendingTasks()); - - // Verify that the successful resolution reset the backoff policy - resolver.listener.onError(error); - timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS - 1); - assertEquals(3, resolver.refreshCalled); - timer.forwardNanos(1); - assertEquals(4, resolver.refreshCalled); - assertEquals(0, timer.numPendingTasks()); - } - - @Test - public void nameResolutionFailed_delayedTransportShutdownCancelsBackoff() { - Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error")); - - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setError(error).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - // Name resolution is started as soon as channel is created. - createChannel(); - verify(mockLoadBalancer).handleNameResolutionError(same(error)); - - FakeClock.ScheduledTask nameResolverBackoff = getNameResolverRefresh(); - assertNotNull(nameResolverBackoff); - assertFalse(nameResolverBackoff.isCancelled()); - - // Add a pending call to the delayed transport - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - Metadata headers = new Metadata(); - call.start(mockCallListener, headers); - - // The pending call on the delayed transport stops the name resolver backoff from cancelling - channel.shutdown(); - assertFalse(nameResolverBackoff.isCancelled()); - - // Notify that a subchannel is ready, which drains the delayed transport - SubchannelPicker picker = mock(SubchannelPicker.class); - Status status = Status.UNAVAILABLE.withDescription("for test"); - when(picker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withDrop(status)); - updateBalancingStateSafely(helper, READY, picker); - executor.runDueTasks(); - verify(mockCallListener).onClose(same(status), any(Metadata.class)); - - assertTrue(nameResolverBackoff.isCancelled()); - } - - @Test - public void nameResolverReturnsEmptySubLists_becomeErrorByDefault() throws Exception { - String errorDescription = "NameResolver returned no usable address"; - - // Pass a FakeNameResolverFactory with an empty list and LB config - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).build(); - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }"); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextConfigOrError.set(ConfigOrError.fromConfig(parsedServiceConfig)); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - // LoadBalancer received the error - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture()); - Status status = statusCaptor.getValue(); - assertSame(Status.Code.UNAVAILABLE, status.getCode()); - assertThat(status.getDescription()).startsWith(errorDescription); - - // A resolution retry has been scheduled - assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); - } - - @Test - public void nameResolverReturnsEmptySubLists_optionallyAllowed() throws Exception { - when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); - - // Pass a FakeNameResolverFactory with an empty list and LB config - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).build(); - String rawLbConfig = "{ \"setting1\": \"high\" }"; - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": " + rawLbConfig + " } ] }"); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig( - rawServiceConfig, - new PolicySelection( - mockLoadBalancerProvider, - parseConfig(rawLbConfig), - new Object())); - nameResolverFactory.nextConfigOrError.set(ConfigOrError.fromConfig(parsedServiceConfig)); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - // LoadBalancer received the empty list and the LB config - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).isEmpty(); - Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - Map lbConfig = actualAttrs.get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG); - assertEquals(ImmutableMap.of("setting1", "high"), lbConfig); - assertSame( - rawServiceConfig, actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)); - - // A no resolution retry - assertEquals(0, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); - } - - @Test - public void loadBalancerThrowsInHandleResolvedAddresses() { - RuntimeException ex = new RuntimeException("simulated"); - // Delay the success of name resolution until allResolved() is called - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setResolvedAtStart(false) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - doThrow(ex).when(mockLoadBalancer).handleResolvedAddresses(any(ResolvedAddresses.class)); - - // NameResolver returns addresses. - nameResolverFactory.allResolved(); - - // Exception thrown from balancer is caught by ChannelExecutor, making channel enter panic mode. - verifyPanicMode(ex); - } - - @Test - public void nameResolvedAfterChannelShutdown() { - // Delay the success of name resolution until allResolved() is called. - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - channel.shutdown(); - - assertTrue(channel.isShutdown()); - assertTrue(channel.isTerminated()); - verify(mockLoadBalancer).shutdown(); - // Name resolved after the channel is shut down, which is possible if the name resolution takes - // time and is not cancellable. The resolved address will be dropped. - nameResolverFactory.allResolved(); - verifyNoMoreInteractions(mockLoadBalancer); - } - - /** - * Verify that if the first resolved address points to a server that cannot be connected, the call - * will end up with the second address which works. - */ - @Test - public void firstResolvedServerFailedToConnect() throws Exception { - final SocketAddress goodAddress = new SocketAddress() { - @Override public String toString() { - return "goodAddress"; - } - }; - final SocketAddress badAddress = new SocketAddress() { - @Override public String toString() { - return "badAddress"; - } - }; - InOrder inOrder = inOrder(mockLoadBalancer, subchannelStateListener); - - List resolvedAddrs = Arrays.asList(badAddress, goodAddress); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(resolvedAddrs))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - // Start the call - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - Metadata headers = new Metadata(); - call.start(mockCallListener, headers); - executor.runDueTasks(); - - // Simulate name resolution results - EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); - inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); - assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - requestConnectionSafely(helper, subchannel); - inOrder.verify(subchannelStateListener).onSubchannelState(stateInfoCaptor.capture()); - assertEquals(CONNECTING, stateInfoCaptor.getValue().getState()); - - // The channel will starts with the first address (badAddress) - verify(mockTransportFactory) - .newClientTransport( - same(badAddress), any(ClientTransportOptions.class), any(ChannelLogger.class)); - verify(mockTransportFactory, times(0)) - .newClientTransport( - same(goodAddress), any(ClientTransportOptions.class), any(ChannelLogger.class)); - - MockClientTransportInfo badTransportInfo = transports.poll(); - // Which failed to connect - badTransportInfo.listener.transportShutdown(Status.UNAVAILABLE); - inOrder.verifyNoMoreInteractions(); - - // The channel then try the second address (goodAddress) - verify(mockTransportFactory) - .newClientTransport( - same(goodAddress), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo goodTransportInfo = transports.poll(); - when(goodTransportInfo.transport.newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mock(ClientStream.class)); - - goodTransportInfo.listener.transportReady(); - inOrder.verify(subchannelStateListener).onSubchannelState(stateInfoCaptor.capture()); - assertEquals(READY, stateInfoCaptor.getValue().getState()); - - // A typical LoadBalancer will call this once the subchannel becomes READY - updateBalancingStateSafely(helper, READY, mockPicker); - // Delayed transport uses the app executor to create real streams. - executor.runDueTasks(); - - verify(goodTransportInfo.transport).newStream(same(method), same(headers), - same(CallOptions.DEFAULT)); - // The bad transport was never used. - verify(badTransportInfo.transport, times(0)).newStream(any(MethodDescriptor.class), - any(Metadata.class), any(CallOptions.class)); - } - - @Test - public void failFastRpcFailFromErrorFromBalancer() { - subtestFailRpcFromBalancer(false, false, true); - } - - @Test - public void failFastRpcFailFromDropFromBalancer() { - subtestFailRpcFromBalancer(false, true, true); - } - - @Test - public void waitForReadyRpcImmuneFromErrorFromBalancer() { - subtestFailRpcFromBalancer(true, false, false); - } - - @Test - public void waitForReadyRpcFailFromDropFromBalancer() { - subtestFailRpcFromBalancer(true, true, true); - } - - private void subtestFailRpcFromBalancer(boolean waitForReady, boolean drop, boolean shouldFail) { - createChannel(); - - // This call will be buffered by the channel, thus involve delayed transport - CallOptions callOptions = CallOptions.DEFAULT; - if (waitForReady) { - callOptions = callOptions.withWaitForReady(); - } else { - callOptions = callOptions.withoutWaitForReady(); - } - ClientCall call1 = channel.newCall(method, callOptions); - call1.start(mockCallListener, new Metadata()); - - SubchannelPicker picker = mock(SubchannelPicker.class); - Status status = Status.UNAVAILABLE.withDescription("for test"); - - when(picker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(drop ? PickResult.withDrop(status) : PickResult.withError(status)); - updateBalancingStateSafely(helper, READY, picker); - - executor.runDueTasks(); - if (shouldFail) { - verify(mockCallListener).onClose(same(status), any(Metadata.class)); - } else { - verifyZeroInteractions(mockCallListener); - } - - // This call doesn't involve delayed transport - ClientCall call2 = channel.newCall(method, callOptions); - call2.start(mockCallListener2, new Metadata()); - - executor.runDueTasks(); - if (shouldFail) { - verify(mockCallListener2).onClose(same(status), any(Metadata.class)); - } else { - verifyZeroInteractions(mockCallListener2); - } - } - - /** - * Verify that if all resolved addresses failed to connect, a fail-fast call will fail, while a - * wait-for-ready call will still be buffered. - */ - @Test - public void allServersFailedToConnect() throws Exception { - final SocketAddress addr1 = new SocketAddress() { - @Override public String toString() { - return "addr1"; - } - }; - final SocketAddress addr2 = new SocketAddress() { - @Override public String toString() { - return "addr2"; - } - }; - InOrder inOrder = inOrder(mockLoadBalancer, subchannelStateListener); - - List resolvedAddrs = Arrays.asList(addr1, addr2); - - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(resolvedAddrs))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - // Start a wait-for-ready call - ClientCall call = - channel.newCall(method, CallOptions.DEFAULT.withWaitForReady()); - Metadata headers = new Metadata(); - call.start(mockCallListener, headers); - // ... and a fail-fast call - ClientCall call2 = - channel.newCall(method, CallOptions.DEFAULT.withoutWaitForReady()); - call2.start(mockCallListener2, headers); - executor.runDueTasks(); - - // Simulate name resolution results - EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); - inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); - assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); - - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - requestConnectionSafely(helper, subchannel); - - inOrder.verify(subchannelStateListener).onSubchannelState(stateInfoCaptor.capture()); - assertEquals(CONNECTING, stateInfoCaptor.getValue().getState()); - - // Connecting to server1, which will fail - verify(mockTransportFactory) - .newClientTransport( - same(addr1), any(ClientTransportOptions.class), any(ChannelLogger.class)); - verify(mockTransportFactory, times(0)) - .newClientTransport( - same(addr2), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo1 = transports.poll(); - transportInfo1.listener.transportShutdown(Status.UNAVAILABLE); - - // Connecting to server2, which will fail too - verify(mockTransportFactory) - .newClientTransport( - same(addr2), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo2 = transports.poll(); - Status server2Error = Status.UNAVAILABLE.withDescription("Server2 failed to connect"); - transportInfo2.listener.transportShutdown(server2Error); - - // ... which makes the subchannel enter TRANSIENT_FAILURE. The last error Status is propagated - // to LoadBalancer. - inOrder.verify(subchannelStateListener).onSubchannelState(stateInfoCaptor.capture()); - assertEquals(TRANSIENT_FAILURE, stateInfoCaptor.getValue().getState()); - assertSame(server2Error, stateInfoCaptor.getValue().getStatus()); - - // A typical LoadBalancer would create a picker with error - SubchannelPicker picker2 = mock(SubchannelPicker.class); - when(picker2.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withError(server2Error)); - updateBalancingStateSafely(helper, TRANSIENT_FAILURE, picker2); - executor.runDueTasks(); - - // ... which fails the fail-fast call - verify(mockCallListener2).onClose(same(server2Error), any(Metadata.class)); - // ... while the wait-for-ready call stays - verifyNoMoreInteractions(mockCallListener); - // No real stream was ever created - verify(transportInfo1.transport, times(0)) - .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - verify(transportInfo2.transport, times(0)) - .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - } - - @Test - public void subchannels() { - createChannel(); - - // createSubchannel() always return a new Subchannel - Attributes attrs1 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr1").build(); - Attributes attrs2 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr2").build(); - SubchannelStateListener listener1 = mock(SubchannelStateListener.class); - SubchannelStateListener listener2 = mock(SubchannelStateListener.class); - final Subchannel sub1 = createSubchannelSafely(helper, addressGroup, attrs1, listener1); - final Subchannel sub2 = createSubchannelSafely(helper, addressGroup, attrs2, listener2); - assertNotSame(sub1, sub2); - assertNotSame(attrs1, attrs2); - assertSame(attrs1, sub1.getAttributes()); - assertSame(attrs2, sub2.getAttributes()); - - final AtomicBoolean snippetPassed = new AtomicBoolean(false); - helper.getSynchronizationContext().execute(new Runnable() { - @Override - public void run() { - // getAddresses() must be called from sync context - assertSame(addressGroup, sub1.getAddresses()); - assertSame(addressGroup, sub2.getAddresses()); - snippetPassed.set(true); - } - }); - assertThat(snippetPassed.get()).isTrue(); - - // requestConnection() - verify(mockTransportFactory, never()) - .newClientTransport( - any(SocketAddress.class), - any(ClientTransportOptions.class), - any(TransportLogger.class)); - requestConnectionSafely(helper, sub1); - verify(mockTransportFactory) - .newClientTransport( - eq(socketAddress), - eq(clientTransportOptions), - isA(TransportLogger.class)); - MockClientTransportInfo transportInfo1 = transports.poll(); - assertNotNull(transportInfo1); - - requestConnectionSafely(helper, sub2); - verify(mockTransportFactory, times(2)) - .newClientTransport( - eq(socketAddress), - eq(clientTransportOptions), - isA(TransportLogger.class)); - MockClientTransportInfo transportInfo2 = transports.poll(); - assertNotNull(transportInfo2); - - requestConnectionSafely(helper, sub1); - requestConnectionSafely(helper, sub2); - // The subchannel doesn't matter since this isn't called - verify(mockTransportFactory, times(2)) - .newClientTransport( - eq(socketAddress), eq(clientTransportOptions), isA(TransportLogger.class)); - - // updateAddresses() - updateAddressesSafely(helper, sub1, Collections.singletonList(addressGroup2)); - assertThat(((InternalSubchannel) sub1.getInternalSubchannel()).getAddressGroups()) - .isEqualTo(Collections.singletonList(addressGroup2)); - - // shutdown() has a delay - shutdownSafely(helper, sub1); - timer.forwardTime(ManagedChannelImpl2.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS - 1, TimeUnit.SECONDS); - shutdownSafely(helper, sub1); - verify(transportInfo1.transport, never()).shutdown(any(Status.class)); - timer.forwardTime(1, TimeUnit.SECONDS); - verify(transportInfo1.transport).shutdown(same(ManagedChannelImpl2.SUBCHANNEL_SHUTDOWN_STATUS)); - - // ... but not after Channel is terminating - verify(mockLoadBalancer, never()).shutdown(); - channel.shutdown(); - verify(mockLoadBalancer).shutdown(); - verify(transportInfo2.transport, never()).shutdown(any(Status.class)); - - shutdownSafely(helper, sub2); - verify(transportInfo2.transport).shutdown(same(ManagedChannelImpl2.SHUTDOWN_STATUS)); - - // Cleanup - transportInfo1.listener.transportShutdown(Status.UNAVAILABLE); - transportInfo1.listener.transportTerminated(); - transportInfo2.listener.transportShutdown(Status.UNAVAILABLE); - transportInfo2.listener.transportTerminated(); - timer.forwardTime(ManagedChannelImpl2.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS); - } - - @Test - public void subchannelStringableBeforeStart() { - createChannel(); - Subchannel subchannel = createUnstartedSubchannel(helper, addressGroup, Attributes.EMPTY); - assertThat(subchannel.toString()).isNotNull(); - } - - @Test - public void subchannelLoggerCreatedBeforeSubchannelStarted() { - createChannel(); - Subchannel subchannel = createUnstartedSubchannel(helper, addressGroup, Attributes.EMPTY); - assertThat(subchannel.getChannelLogger()).isNotNull(); - } - - @Test - public void subchannelsWhenChannelShutdownNow() { - createChannel(); - Subchannel sub1 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - Subchannel sub2 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, sub1); - requestConnectionSafely(helper, sub2); - - assertThat(transports).hasSize(2); - MockClientTransportInfo ti1 = transports.poll(); - MockClientTransportInfo ti2 = transports.poll(); - - ti1.listener.transportReady(); - ti2.listener.transportReady(); - - channel.shutdownNow(); - verify(ti1.transport).shutdownNow(any(Status.class)); - verify(ti2.transport).shutdownNow(any(Status.class)); - - ti1.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now")); - ti2.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now")); - ti1.listener.transportTerminated(); - - assertFalse(channel.isTerminated()); - ti2.listener.transportTerminated(); - assertTrue(channel.isTerminated()); - } - - @Test - public void subchannelsNoConnectionShutdown() { - createChannel(); - Subchannel sub1 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - Subchannel sub2 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - - channel.shutdown(); - verify(mockLoadBalancer).shutdown(); - shutdownSafely(helper, sub1); - assertFalse(channel.isTerminated()); - shutdownSafely(helper, sub2); - assertTrue(channel.isTerminated()); - verify(mockTransportFactory, never()) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - } - - @Test - public void subchannelsNoConnectionShutdownNow() { - createChannel(); - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - channel.shutdownNow(); - - verify(mockLoadBalancer).shutdown(); - // Channel's shutdownNow() will call shutdownNow() on all subchannels and oobchannels. - // Therefore, channel is terminated without relying on LoadBalancer to shutdown subchannels. - assertTrue(channel.isTerminated()); - verify(mockTransportFactory, never()) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - } - - @Test - public void oobchannels() { - createChannel(); - - ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1authority"); - ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2authority"); - verify(balancerRpcExecutorPool, times(2)).getObject(); - - assertEquals("oob1authority", oob1.authority()); - assertEquals("oob2authority", oob2.authority()); - - // OOB channels create connections lazily. A new call will initiate the connection. - Metadata headers = new Metadata(); - ClientCall call = oob1.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, headers); - verify(mockTransportFactory) - .newClientTransport( - eq(socketAddress), - eq(new ClientTransportOptions().setAuthority("oob1authority").setUserAgent(USER_AGENT)), - isA(ChannelLogger.class)); - MockClientTransportInfo transportInfo = transports.poll(); - assertNotNull(transportInfo); - - assertEquals(0, balancerRpcExecutor.numPendingTasks()); - transportInfo.listener.transportReady(); - assertEquals(1, balancerRpcExecutor.runDueTasks()); - verify(transportInfo.transport).newStream(same(method), same(headers), - same(CallOptions.DEFAULT)); - - // The transport goes away - transportInfo.listener.transportShutdown(Status.UNAVAILABLE); - transportInfo.listener.transportTerminated(); - - // A new call will trigger a new transport - ClientCall call2 = oob1.newCall(method, CallOptions.DEFAULT); - call2.start(mockCallListener2, headers); - ClientCall call3 = - oob1.newCall(method, CallOptions.DEFAULT.withWaitForReady()); - call3.start(mockCallListener3, headers); - verify(mockTransportFactory, times(2)).newClientTransport( - eq(socketAddress), - eq(new ClientTransportOptions().setAuthority("oob1authority").setUserAgent(USER_AGENT)), - isA(ChannelLogger.class)); - transportInfo = transports.poll(); - assertNotNull(transportInfo); - - // This transport fails - Status transportError = Status.UNAVAILABLE.withDescription("Connection refused"); - assertEquals(0, balancerRpcExecutor.numPendingTasks()); - transportInfo.listener.transportShutdown(transportError); - assertTrue(balancerRpcExecutor.runDueTasks() > 0); - - // Fail-fast RPC will fail, while wait-for-ready RPC will still be pending - verify(mockCallListener2).onClose(same(transportError), any(Metadata.class)); - verify(mockCallListener3, never()).onClose(any(Status.class), any(Metadata.class)); - - // Shutdown - assertFalse(oob1.isShutdown()); - assertFalse(oob2.isShutdown()); - oob1.shutdown(); - oob2.shutdownNow(); - assertTrue(oob1.isShutdown()); - assertTrue(oob2.isShutdown()); - assertTrue(oob2.isTerminated()); - verify(balancerRpcExecutorPool).returnObject(balancerRpcExecutor.getScheduledExecutorService()); - - // New RPCs will be rejected. - assertEquals(0, balancerRpcExecutor.numPendingTasks()); - ClientCall call4 = oob1.newCall(method, CallOptions.DEFAULT); - ClientCall call5 = oob2.newCall(method, CallOptions.DEFAULT); - call4.start(mockCallListener4, headers); - call5.start(mockCallListener5, headers); - assertTrue(balancerRpcExecutor.runDueTasks() > 0); - verify(mockCallListener4).onClose(statusCaptor.capture(), any(Metadata.class)); - Status status4 = statusCaptor.getValue(); - assertEquals(Status.Code.UNAVAILABLE, status4.getCode()); - verify(mockCallListener5).onClose(statusCaptor.capture(), any(Metadata.class)); - Status status5 = statusCaptor.getValue(); - assertEquals(Status.Code.UNAVAILABLE, status5.getCode()); - - // The pending RPC will still be pending - verify(mockCallListener3, never()).onClose(any(Status.class), any(Metadata.class)); - - // This will shutdownNow() the delayed transport, terminating the pending RPC - assertEquals(0, balancerRpcExecutor.numPendingTasks()); - oob1.shutdownNow(); - assertTrue(balancerRpcExecutor.runDueTasks() > 0); - verify(mockCallListener3).onClose(any(Status.class), any(Metadata.class)); - - // Shut down the channel, and it will not terminated because OOB channel has not. - channel.shutdown(); - assertFalse(channel.isTerminated()); - // Delayed transport has already terminated. Terminating the transport terminates the - // subchannel, which in turn terimates the OOB channel, which terminates the channel. - assertFalse(oob1.isTerminated()); - verify(balancerRpcExecutorPool).returnObject(balancerRpcExecutor.getScheduledExecutorService()); - transportInfo.listener.transportTerminated(); - assertTrue(oob1.isTerminated()); - assertTrue(channel.isTerminated()); - verify(balancerRpcExecutorPool, times(2)) - .returnObject(balancerRpcExecutor.getScheduledExecutorService()); - } - - @Test - public void oobChannelsWhenChannelShutdownNow() { - createChannel(); - ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority"); - ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority"); - - oob1.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata()); - oob2.newCall(method, CallOptions.DEFAULT).start(mockCallListener2, new Metadata()); - - assertThat(transports).hasSize(2); - MockClientTransportInfo ti1 = transports.poll(); - MockClientTransportInfo ti2 = transports.poll(); - - ti1.listener.transportReady(); - ti2.listener.transportReady(); - - channel.shutdownNow(); - verify(ti1.transport).shutdownNow(any(Status.class)); - verify(ti2.transport).shutdownNow(any(Status.class)); - - ti1.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now")); - ti2.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now")); - ti1.listener.transportTerminated(); - - assertFalse(channel.isTerminated()); - ti2.listener.transportTerminated(); - assertTrue(channel.isTerminated()); - } - - @Test - public void oobChannelsNoConnectionShutdown() { - createChannel(); - ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority"); - ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority"); - channel.shutdown(); - - verify(mockLoadBalancer).shutdown(); - oob1.shutdown(); - assertTrue(oob1.isTerminated()); - assertFalse(channel.isTerminated()); - oob2.shutdown(); - assertTrue(oob2.isTerminated()); - assertTrue(channel.isTerminated()); - verify(mockTransportFactory, never()) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - } - - @Test - public void oobChannelsNoConnectionShutdownNow() { - createChannel(); - helper.createOobChannel(addressGroup, "oob1Authority"); - helper.createOobChannel(addressGroup, "oob2Authority"); - channel.shutdownNow(); - - verify(mockLoadBalancer).shutdown(); - assertTrue(channel.isTerminated()); - // Channel's shutdownNow() will call shutdownNow() on all subchannels and oobchannels. - // Therefore, channel is terminated without relying on LoadBalancer to shutdown oobchannels. - verify(mockTransportFactory, never()) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - } - - @Test - public void subchannelChannel_normalUsage() { - createChannel(); - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - verify(balancerRpcExecutorPool, never()).getObject(); - - Channel sChannel = subchannel.asChannel(); - verify(balancerRpcExecutorPool).getObject(); - - Metadata headers = new Metadata(); - CallOptions callOptions = CallOptions.DEFAULT.withDeadlineAfter(5, TimeUnit.SECONDS); - - // Subchannel must be READY when creating the RPC. - requestConnectionSafely(helper, subchannel); - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - transportListener.transportReady(); - - ClientCall call = sChannel.newCall(method, callOptions); - call.start(mockCallListener, headers); - verify(mockTransport).newStream(same(method), same(headers), callOptionsCaptor.capture()); - - CallOptions capturedCallOption = callOptionsCaptor.getValue(); - assertThat(capturedCallOption.getDeadline()).isSameInstanceAs(callOptions.getDeadline()); - assertThat(capturedCallOption.getOption(GrpcUtil.CALL_OPTIONS_RPC_OWNED_BY_BALANCER)).isTrue(); - } - - @Test - public void subchannelChannel_failWhenNotReady() { - createChannel(); - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - Channel sChannel = subchannel.asChannel(); - Metadata headers = new Metadata(); - - requestConnectionSafely(helper, subchannel); - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - - assertEquals(0, balancerRpcExecutor.numPendingTasks()); - - // Subchannel is still CONNECTING, but not READY yet - ClientCall call = sChannel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, headers); - verify(mockTransport, never()).newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - - verifyZeroInteractions(mockCallListener); - assertEquals(1, balancerRpcExecutor.runDueTasks()); - verify(mockCallListener).onClose( - same(SubchannelChannel.NOT_READY_ERROR), any(Metadata.class)); - } - - @Test - public void subchannelChannel_failWaitForReady() { - createChannel(); - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - Channel sChannel = subchannel.asChannel(); - Metadata headers = new Metadata(); - - // Subchannel must be READY when creating the RPC. - requestConnectionSafely(helper, subchannel); - verify(mockTransportFactory) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - transportListener.transportReady(); - assertEquals(0, balancerRpcExecutor.numPendingTasks()); - - // Wait-for-ready RPC is not allowed - ClientCall call = - sChannel.newCall(method, CallOptions.DEFAULT.withWaitForReady()); - call.start(mockCallListener, headers); - verify(mockTransport, never()).newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - - verifyZeroInteractions(mockCallListener); - assertEquals(1, balancerRpcExecutor.runDueTasks()); - verify(mockCallListener).onClose( - same(SubchannelChannel.WAIT_FOR_READY_ERROR), any(Metadata.class)); - } - - @Test - public void lbHelper_getScheduledExecutorService() { - createChannel(); - - ScheduledExecutorService ses = helper.getScheduledExecutorService(); - Runnable task = mock(Runnable.class); - helper.getSynchronizationContext().schedule(task, 110, TimeUnit.NANOSECONDS, ses); - timer.forwardNanos(109); - verify(task, never()).run(); - timer.forwardNanos(1); - verify(task).run(); - - try { - ses.shutdown(); - fail("Should throw"); - } catch (UnsupportedOperationException e) { - // expected - } - - try { - ses.shutdownNow(); - fail("Should throw"); - } catch (UnsupportedOperationException e) { - // expected - } - } - - @Test - public void lbHelper_getNameResolverArgs() { - createChannel(); - - NameResolver.Args args = helper.getNameResolverArgs(); - assertThat(args.getDefaultPort()).isEqualTo(DEFAULT_PORT); - assertThat(args.getProxyDetector()).isSameInstanceAs(GrpcUtil.DEFAULT_PROXY_DETECTOR); - assertThat(args.getSynchronizationContext()) - .isSameInstanceAs(helper.getSynchronizationContext()); - assertThat(args.getServiceConfigParser()).isNotNull(); - } - - @Test - public void lbHelper_getNameResolverRegistry() { - createChannel(); - - assertThat(helper.getNameResolverRegistry()) - .isSameInstanceAs(NameResolverRegistry.getDefaultRegistry()); - } - - @Test - public void refreshNameResolution_whenSubchannelConnectionFailed_notIdle() { - subtestNameResolutionRefreshWhenConnectionFailed(false, false); - } - - @Test - public void refreshNameResolution_whenOobChannelConnectionFailed_notIdle() { - subtestNameResolutionRefreshWhenConnectionFailed(true, false); - } - - @Test - public void notRefreshNameResolution_whenSubchannelConnectionFailed_idle() { - subtestNameResolutionRefreshWhenConnectionFailed(false, true); - } - - @Test - public void notRefreshNameResolution_whenOobChannelConnectionFailed_idle() { - subtestNameResolutionRefreshWhenConnectionFailed(true, true); - } - - private void subtestNameResolutionRefreshWhenConnectionFailed( - boolean isOobChannel, boolean isIdle) { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - if (isOobChannel) { - OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobAuthority"); - oobChannel.getSubchannel().requestConnection(); - } else { - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - } - - MockClientTransportInfo transportInfo = transports.poll(); - assertNotNull(transportInfo); - - FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.remove(0); - - if (isIdle) { - channel.enterIdle(); - // Entering idle mode will result in a new resolver - resolver = nameResolverFactory.resolvers.remove(0); - } - - assertEquals(0, nameResolverFactory.resolvers.size()); - - int expectedRefreshCount = 0; - - // Transport closed when connecting - assertEquals(expectedRefreshCount, resolver.refreshCalled); - transportInfo.listener.transportShutdown(Status.UNAVAILABLE); - // When channel enters idle, new resolver is created but not started. - if (!isIdle) { - expectedRefreshCount++; - } - assertEquals(expectedRefreshCount, resolver.refreshCalled); - - timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS); - transportInfo = transports.poll(); - assertNotNull(transportInfo); - - transportInfo.listener.transportReady(); - - // Transport closed when ready - assertEquals(expectedRefreshCount, resolver.refreshCalled); - transportInfo.listener.transportShutdown(Status.UNAVAILABLE); - // When channel enters idle, new resolver is created but not started. - if (!isIdle) { - expectedRefreshCount++; - } - assertEquals(expectedRefreshCount, resolver.refreshCalled); - } - - @Test - public void uriPattern() { - assertTrue(ManagedChannelImpl2.URI_PATTERN.matcher("a:/").matches()); - assertTrue(ManagedChannelImpl2.URI_PATTERN.matcher("Z019+-.:/!@ #~ ").matches()); - assertFalse(ManagedChannelImpl2.URI_PATTERN.matcher("a/:").matches()); // "/:" not matched - assertFalse(ManagedChannelImpl2.URI_PATTERN.matcher("0a:/").matches()); // '0' not matched - assertFalse(ManagedChannelImpl2.URI_PATTERN.matcher("a,:/").matches()); // ',' not matched - assertFalse(ManagedChannelImpl2.URI_PATTERN.matcher(" a:/").matches()); // space not matched - } - - /** - * Test that information such as the Call's context, MethodDescriptor, authority, executor are - * propagated to newStream() and applyRequestMetadata(). - */ - @Test - public void informationPropagatedToNewStreamAndCallCredentials() { - createChannel(); - CallOptions callOptions = CallOptions.DEFAULT.withCallCredentials(creds); - final Context.Key testKey = Context.key("testing"); - Context ctx = Context.current().withValue(testKey, "testValue"); - final LinkedList credsApplyContexts = new LinkedList<>(); - final LinkedList newStreamContexts = new LinkedList<>(); - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock in) throws Throwable { - credsApplyContexts.add(Context.current()); - return null; - } - }).when(creds).applyRequestMetadata( - any(RequestInfo.class), any(Executor.class), any(CallCredentials.MetadataApplier.class)); - - // First call will be on delayed transport. Only newCall() is run within the expected context, - // so that we can verify that the context is explicitly attached before calling newStream() and - // applyRequestMetadata(), which happens after we detach the context from the thread. - Context origCtx = ctx.attach(); - assertEquals("testValue", testKey.get()); - ClientCall call = channel.newCall(method, callOptions); - ctx.detach(origCtx); - assertNull(testKey.get()); - call.start(mockCallListener, new Metadata()); - - // Simulate name resolution results - EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(socketAddress); - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - verify(mockTransportFactory) - .newClientTransport( - same(socketAddress), eq(clientTransportOptions), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo = transports.poll(); - final ConnectionClientTransport transport = transportInfo.transport; - when(transport.getAttributes()).thenReturn(Attributes.EMPTY); - doAnswer(new Answer() { - @Override - public ClientStream answer(InvocationOnMock in) throws Throwable { - newStreamContexts.add(Context.current()); - return mock(ClientStream.class); - } - }).when(transport).newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - - verify(creds, never()).applyRequestMetadata( - any(RequestInfo.class), any(Executor.class), any(CallCredentials.MetadataApplier.class)); - - // applyRequestMetadata() is called after the transport becomes ready. - transportInfo.listener.transportReady(); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - updateBalancingStateSafely(helper, READY, mockPicker); - executor.runDueTasks(); - ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null); - ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null); - verify(creds).applyRequestMetadata(infoCaptor.capture(), - same(executor.getScheduledExecutorService()), applierCaptor.capture()); - assertEquals("testValue", testKey.get(credsApplyContexts.poll())); - assertEquals(AUTHORITY, infoCaptor.getValue().getAuthority()); - assertEquals(SecurityLevel.NONE, infoCaptor.getValue().getSecurityLevel()); - verify(transport, never()).newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - - // newStream() is called after apply() is called - applierCaptor.getValue().apply(new Metadata()); - verify(transport).newStream(same(method), any(Metadata.class), same(callOptions)); - assertEquals("testValue", testKey.get(newStreamContexts.poll())); - // The context should not live beyond the scope of newStream() and applyRequestMetadata() - assertNull(testKey.get()); - - - // Second call will not be on delayed transport - origCtx = ctx.attach(); - call = channel.newCall(method, callOptions); - ctx.detach(origCtx); - call.start(mockCallListener, new Metadata()); - - verify(creds, times(2)).applyRequestMetadata(infoCaptor.capture(), - same(executor.getScheduledExecutorService()), applierCaptor.capture()); - assertEquals("testValue", testKey.get(credsApplyContexts.poll())); - assertEquals(AUTHORITY, infoCaptor.getValue().getAuthority()); - assertEquals(SecurityLevel.NONE, infoCaptor.getValue().getSecurityLevel()); - // This is from the first call - verify(transport).newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - - // Still, newStream() is called after apply() is called - applierCaptor.getValue().apply(new Metadata()); - verify(transport, times(2)).newStream(same(method), any(Metadata.class), same(callOptions)); - assertEquals("testValue", testKey.get(newStreamContexts.poll())); - - assertNull(testKey.get()); - } - - @Test - public void pickerReturnsStreamTracer_noDelay() { - ClientStream mockStream = mock(ClientStream.class); - ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class); - ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class); - createChannel(); - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo transportInfo = transports.poll(); - transportInfo.listener.transportReady(); - ClientTransport mockTransport = transportInfo.transport; - when(mockTransport.newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream); - - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn( - PickResult.withSubchannel(subchannel, factory2)); - updateBalancingStateSafely(helper, READY, mockPicker); - - CallOptions callOptions = CallOptions.DEFAULT.withStreamTracerFactory(factory1); - ClientCall call = channel.newCall(method, callOptions); - call.start(mockCallListener, new Metadata()); - - verify(mockPicker).pickSubchannel(any(PickSubchannelArgs.class)); - verify(mockTransport).newStream(same(method), any(Metadata.class), callOptionsCaptor.capture()); - assertEquals( - Arrays.asList(factory1, factory2), - callOptionsCaptor.getValue().getStreamTracerFactories()); - // The factories are safely not stubbed because we do not expect any usage of them. - verifyZeroInteractions(factory1); - verifyZeroInteractions(factory2); - } - - @Test - public void pickerReturnsStreamTracer_delayed() { - ClientStream mockStream = mock(ClientStream.class); - ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class); - ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class); - createChannel(); - - CallOptions callOptions = CallOptions.DEFAULT.withStreamTracerFactory(factory1); - ClientCall call = channel.newCall(method, callOptions); - call.start(mockCallListener, new Metadata()); - - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo transportInfo = transports.poll(); - transportInfo.listener.transportReady(); - ClientTransport mockTransport = transportInfo.transport; - when(mockTransport.newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn( - PickResult.withSubchannel(subchannel, factory2)); - - updateBalancingStateSafely(helper, READY, mockPicker); - assertEquals(1, executor.runDueTasks()); - - verify(mockPicker).pickSubchannel(any(PickSubchannelArgs.class)); - verify(mockTransport).newStream(same(method), any(Metadata.class), callOptionsCaptor.capture()); - assertEquals( - Arrays.asList(factory1, factory2), - callOptionsCaptor.getValue().getStreamTracerFactories()); - // The factories are safely not stubbed because we do not expect any usage of them. - verifyZeroInteractions(factory1); - verifyZeroInteractions(factory2); - } - - @Test - public void getState_loadBalancerSupportsChannelState() { - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build()); - createChannel(); - assertEquals(IDLE, channel.getState(false)); - - updateBalancingStateSafely(helper, TRANSIENT_FAILURE, mockPicker); - assertEquals(TRANSIENT_FAILURE, channel.getState(false)); - } - - @Test - public void getState_withRequestConnect() { - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build()); - requestConnection = false; - createChannel(); - - assertEquals(IDLE, channel.getState(false)); - verify(mockLoadBalancerProvider, never()).newLoadBalancer(any(Helper.class)); - - // call getState() with requestConnection = true - assertEquals(IDLE, channel.getState(true)); - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - helper = helperCaptor.getValue(); - - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - assertEquals(CONNECTING, channel.getState(false)); - assertEquals(CONNECTING, channel.getState(true)); - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - } - - @SuppressWarnings("deprecation") - @Test - public void getState_withRequestConnect_IdleWithLbRunning() { - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build()); - createChannel(); - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - - updateBalancingStateSafely(helper, IDLE, mockPicker); - - assertEquals(IDLE, channel.getState(true)); - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - verify(mockPicker).requestConnection(); - verify(mockLoadBalancer).requestConnection(); - } - - @Test - public void notifyWhenStateChanged() { - final AtomicBoolean stateChanged = new AtomicBoolean(); - Runnable onStateChanged = new Runnable() { - @Override - public void run() { - stateChanged.set(true); - } - }; - - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build()); - createChannel(); - assertEquals(IDLE, channel.getState(false)); - - channel.notifyWhenStateChanged(IDLE, onStateChanged); - executor.runDueTasks(); - assertFalse(stateChanged.get()); - - // state change from IDLE to CONNECTING - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - // onStateChanged callback should run - executor.runDueTasks(); - assertTrue(stateChanged.get()); - - // clear and test form CONNECTING - stateChanged.set(false); - channel.notifyWhenStateChanged(IDLE, onStateChanged); - // onStateChanged callback should run immediately - executor.runDueTasks(); - assertTrue(stateChanged.get()); - } - - @Test - public void channelStateWhenChannelShutdown() { - final AtomicBoolean stateChanged = new AtomicBoolean(); - Runnable onStateChanged = new Runnable() { - @Override - public void run() { - stateChanged.set(true); - } - }; - - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build()); - createChannel(); - assertEquals(IDLE, channel.getState(false)); - channel.notifyWhenStateChanged(IDLE, onStateChanged); - executor.runDueTasks(); - assertFalse(stateChanged.get()); - - channel.shutdown(); - assertEquals(SHUTDOWN, channel.getState(false)); - executor.runDueTasks(); - assertTrue(stateChanged.get()); - - stateChanged.set(false); - channel.notifyWhenStateChanged(SHUTDOWN, onStateChanged); - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - - assertEquals(SHUTDOWN, channel.getState(false)); - executor.runDueTasks(); - assertFalse(stateChanged.get()); - } - - @Test - public void stateIsIdleOnIdleTimeout() { - long idleTimeoutMillis = 2000L; - channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS); - createChannel(); - assertEquals(IDLE, channel.getState(false)); - - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - assertEquals(CONNECTING, channel.getState(false)); - - timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis)); - assertEquals(IDLE, channel.getState(false)); - } - - @Test - public void panic_whenIdle() { - subtestPanic(IDLE); - } - - @Test - public void panic_whenConnecting() { - subtestPanic(CONNECTING); - } - - @Test - public void panic_whenTransientFailure() { - subtestPanic(TRANSIENT_FAILURE); - } - - @Test - public void panic_whenReady() { - subtestPanic(READY); - } - - private void subtestPanic(ConnectivityState initialState) { - assertNotEquals("We don't test panic mode if it's already SHUTDOWN", SHUTDOWN, initialState); - long idleTimeoutMillis = 2000L; - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS); - createChannel(); - - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - assertThat(nameResolverFactory.resolvers).hasSize(1); - FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.remove(0); - - final Throwable panicReason = new Exception("Simulated uncaught exception"); - if (initialState == IDLE) { - timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis)); - } else { - updateBalancingStateSafely(helper, initialState, mockPicker); - } - assertEquals(initialState, channel.getState(false)); - - if (initialState == IDLE) { - // IDLE mode will shutdown resolver and balancer - verify(mockLoadBalancer).shutdown(); - assertTrue(resolver.shutdown); - // A new resolver is created - assertThat(nameResolverFactory.resolvers).hasSize(1); - resolver = nameResolverFactory.resolvers.remove(0); - assertFalse(resolver.shutdown); - } else { - verify(mockLoadBalancer, never()).shutdown(); - assertFalse(resolver.shutdown); - } - - // Make channel panic! - channel.syncContext.execute( - new Runnable() { - @Override - public void run() { - channel.panic(panicReason); - } - }); - - // Calls buffered in delayedTransport will fail - - // Resolver and balancer are shutdown - verify(mockLoadBalancer).shutdown(); - assertTrue(resolver.shutdown); - - // Channel will stay in TRANSIENT_FAILURE. getState(true) will not revive it. - assertEquals(TRANSIENT_FAILURE, channel.getState(true)); - assertEquals(TRANSIENT_FAILURE, channel.getState(true)); - verifyPanicMode(panicReason); - - // Besides the resolver created initially, no new resolver or balancer are created. - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - assertThat(nameResolverFactory.resolvers).isEmpty(); - - // A misbehaving balancer that calls updateBalancingState() after it's shut down will not be - // able to revive it. - updateBalancingStateSafely(helper, READY, mockPicker); - verifyPanicMode(panicReason); - - // Cannot be revived by exitIdleMode() - channel.syncContext.execute(new Runnable() { - @Override - public void run() { - channel.exitIdleMode(); - } - }); - verifyPanicMode(panicReason); - - // Can still shutdown normally - channel.shutdown(); - assertTrue(channel.isShutdown()); - assertTrue(channel.isTerminated()); - assertEquals(SHUTDOWN, channel.getState(false)); - - // We didn't stub mockPicker, because it should have never been called in this test. - verifyZeroInteractions(mockPicker); - } - - @Test - public void panic_bufferedCallsWillFail() { - createChannel(); - - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withNoResult()); - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - - // Start RPCs that will be buffered in delayedTransport - ClientCall call = - channel.newCall(method, CallOptions.DEFAULT.withoutWaitForReady()); - call.start(mockCallListener, new Metadata()); - - ClientCall call2 = - channel.newCall(method, CallOptions.DEFAULT.withWaitForReady()); - call2.start(mockCallListener2, new Metadata()); - - executor.runDueTasks(); - verifyZeroInteractions(mockCallListener, mockCallListener2); - - // Enter panic - final Throwable panicReason = new Exception("Simulated uncaught exception"); - channel.syncContext.execute( - new Runnable() { - @Override - public void run() { - channel.panic(panicReason); - } - }); - - // Buffered RPCs fail immediately - executor.runDueTasks(); - verifyCallListenerClosed(mockCallListener, Status.Code.INTERNAL, panicReason); - verifyCallListenerClosed(mockCallListener2, Status.Code.INTERNAL, panicReason); - panicExpected = true; - } - - private void verifyPanicMode(Throwable cause) { - panicExpected = true; - @SuppressWarnings("unchecked") - ClientCall.Listener mockListener = - (ClientCall.Listener) mock(ClientCall.Listener.class); - assertEquals(TRANSIENT_FAILURE, channel.getState(false)); - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockListener, new Metadata()); - executor.runDueTasks(); - verifyCallListenerClosed(mockListener, Status.Code.INTERNAL, cause); - - // Channel is dead. No more pending task to possibly revive it. - assertEquals(0, timer.numPendingTasks()); - assertEquals(0, executor.numPendingTasks()); - assertEquals(0, balancerRpcExecutor.numPendingTasks()); - } - - private void verifyCallListenerClosed( - ClientCall.Listener listener, Status.Code code, Throwable cause) { - ArgumentCaptor captor = ArgumentCaptor.forClass(null); - verify(listener).onClose(captor.capture(), any(Metadata.class)); - Status rpcStatus = captor.getValue(); - assertEquals(code, rpcStatus.getCode()); - assertSame(cause, rpcStatus.getCause()); - verifyNoMoreInteractions(listener); - } - - @Test - public void idleTimeoutAndReconnect() { - long idleTimeoutMillis = 2000L; - channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS); - createChannel(); - - timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis)); - assertEquals(IDLE, channel.getState(true /* request connection */)); - - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); - // Two times of requesting connection will create loadBalancer twice. - verify(mockLoadBalancerProvider, times(2)).newLoadBalancer(helperCaptor.capture()); - Helper helper2 = helperCaptor.getValue(); - - // Updating on the old helper (whose balancer has been shutdown) does not change the channel - // state. - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - assertEquals(IDLE, channel.getState(false)); - - updateBalancingStateSafely(helper2, CONNECTING, mockPicker); - assertEquals(CONNECTING, channel.getState(false)); - } - - @Test - public void idleMode_resetsDelayedTransportPicker() { - ClientStream mockStream = mock(ClientStream.class); - Status pickError = Status.UNAVAILABLE.withDescription("pick result error"); - long idleTimeoutMillis = 1000L; - channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS); - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build()); - createChannel(); - assertEquals(IDLE, channel.getState(false)); - - // This call will be buffered in delayedTransport - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - - // Move channel into TRANSIENT_FAILURE, which will fail the pending call - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withError(pickError)); - updateBalancingStateSafely(helper, TRANSIENT_FAILURE, mockPicker); - assertEquals(TRANSIENT_FAILURE, channel.getState(false)); - executor.runDueTasks(); - verify(mockCallListener).onClose(same(pickError), any(Metadata.class)); - - // Move channel to idle - timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis)); - assertEquals(IDLE, channel.getState(false)); - - // This call should be buffered, but will move the channel out of idle - ClientCall call2 = channel.newCall(method, CallOptions.DEFAULT); - call2.start(mockCallListener2, new Metadata()); - executor.runDueTasks(); - verifyNoMoreInteractions(mockCallListener2); - - // Get the helper created on exiting idle - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); - verify(mockLoadBalancerProvider, times(2)).newLoadBalancer(helperCaptor.capture()); - Helper helper2 = helperCaptor.getValue(); - - // Establish a connection - Subchannel subchannel = - createSubchannelSafely(helper2, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream); - transportListener.transportReady(); - - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - updateBalancingStateSafely(helper2, READY, mockPicker); - assertEquals(READY, channel.getState(false)); - executor.runDueTasks(); - - // Verify the buffered call was drained - verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class)); - verify(mockStream).start(any(ClientStreamListener.class)); - } - - @Test - public void enterIdleEntersIdle() { - createChannel(); - updateBalancingStateSafely(helper, READY, mockPicker); - assertEquals(READY, channel.getState(false)); - - channel.enterIdle(); - - assertEquals(IDLE, channel.getState(false)); - } - - @Test - public void enterIdleAfterIdleTimerIsNoOp() { - long idleTimeoutMillis = 2000L; - channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS); - createChannel(); - timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis)); - assertEquals(IDLE, channel.getState(false)); - - channel.enterIdle(); - - assertEquals(IDLE, channel.getState(false)); - } - - @Test - public void enterIdle_exitsIdleIfDelayedStreamPending() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - // Start a call that will be buffered in delayedTransport - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - - // enterIdle() will shut down the name resolver and lb policy used to get a pick for the delayed - // call - channel.enterIdle(); - assertEquals(IDLE, channel.getState(false)); - - // enterIdle() will restart the delayed call by exiting idle. This creates a new helper. - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); - verify(mockLoadBalancerProvider, times(2)).newLoadBalancer(helperCaptor.capture()); - Helper helper2 = helperCaptor.getValue(); - - // Establish a connection - Subchannel subchannel = - createSubchannelSafely(helper2, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - ClientStream mockStream = mock(ClientStream.class); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream); - transportListener.transportReady(); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - updateBalancingStateSafely(helper2, READY, mockPicker); - assertEquals(READY, channel.getState(false)); - - // Verify the original call was drained - executor.runDueTasks(); - verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class)); - verify(mockStream).start(any(ClientStreamListener.class)); - } - - @Test - public void updateBalancingStateDoesUpdatePicker() { - ClientStream mockStream = mock(ClientStream.class); - createChannel(); - - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - - // Make the transport available with subchannel2 - Subchannel subchannel1 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - Subchannel subchannel2 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel2); - - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream); - transportListener.transportReady(); - - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel1)); - updateBalancingStateSafely(helper, READY, mockPicker); - - executor.runDueTasks(); - verify(mockTransport, never()) - .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - verify(mockStream, never()).start(any(ClientStreamListener.class)); - - - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel2)); - updateBalancingStateSafely(helper, READY, mockPicker); - - executor.runDueTasks(); - verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class)); - verify(mockStream).start(any(ClientStreamListener.class)); - } - - @Test - public void updateBalancingState_withWrappedSubchannel() { - ClientStream mockStream = mock(ClientStream.class); - createChannel(); - - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - - final Subchannel subchannel1 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel1); - - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream); - transportListener.transportReady(); - - Subchannel wrappedSubchannel1 = new ForwardingSubchannel() { - @Override - protected Subchannel delegate() { - return subchannel1; - } - }; - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(wrappedSubchannel1)); - updateBalancingStateSafely(helper, READY, mockPicker); - - executor.runDueTasks(); - verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class)); - verify(mockStream).start(any(ClientStreamListener.class)); - } - - @Test - public void updateBalancingStateWithShutdownShouldBeIgnored() { - channelBuilder.nameResolverFactory( - new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build()); - createChannel(); - assertEquals(IDLE, channel.getState(false)); - - Runnable onStateChanged = mock(Runnable.class); - channel.notifyWhenStateChanged(IDLE, onStateChanged); - - updateBalancingStateSafely(helper, SHUTDOWN, mockPicker); - - assertEquals(IDLE, channel.getState(false)); - executor.runDueTasks(); - verify(onStateChanged, never()).run(); - } - - @Test - public void balancerRefreshNameResolution() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); - int initialRefreshCount = resolver.refreshCalled; - refreshNameResolutionSafely(helper); - assertEquals(initialRefreshCount + 1, resolver.refreshCalled); - } - - @Test - public void resetConnectBackoff() { - // Start with a name resolution failure to trigger backoff attempts - Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error")); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setError(error).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - // Name resolution is started as soon as channel is created. - createChannel(); - FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); - verify(mockLoadBalancer).handleNameResolutionError(same(error)); - - FakeClock.ScheduledTask nameResolverBackoff = getNameResolverRefresh(); - assertNotNull("There should be a name resolver backoff task", nameResolverBackoff); - assertEquals(0, resolver.refreshCalled); - - // Verify resetConnectBackoff() calls refresh and cancels the scheduled backoff - channel.resetConnectBackoff(); - assertEquals(1, resolver.refreshCalled); - assertTrue(nameResolverBackoff.isCancelled()); - - // Simulate a race between cancel and the task scheduler. Should be a no-op. - nameResolverBackoff.command.run(); - assertEquals(1, resolver.refreshCalled); - - // Verify that the reconnect policy was recreated and the backoff multiplier reset to 1 - timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS); - assertEquals(2, resolver.refreshCalled); - } - - @Test - public void resetConnectBackoff_noOpWithoutPendingResolverBackoff() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0); - assertEquals(0, nameResolver.refreshCalled); - - channel.resetConnectBackoff(); - - assertEquals(0, nameResolver.refreshCalled); - } - - @Test - public void resetConnectBackoff_noOpWhenChannelShutdown() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - channel.shutdown(); - assertTrue(channel.isShutdown()); - channel.resetConnectBackoff(); - - FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0); - assertEquals(0, nameResolver.refreshCalled); - } - - @Test - public void resetConnectBackoff_noOpWhenNameResolverNotStarted() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - requestConnection = false; - createChannel(); - - channel.resetConnectBackoff(); - - FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0); - assertEquals(0, nameResolver.refreshCalled); - } - - @Test - public void channelsAndSubchannels_instrumented_name() throws Exception { - createChannel(); - assertEquals(TARGET, getStats(channel).target); - - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - assertEquals(Collections.singletonList(addressGroup).toString(), - getStats((AbstractSubchannel) subchannel).target); - } - - @Test - public void channelTracing_channelCreationEvent() throws Exception { - timer.forwardNanos(1234); - channelBuilder.maxTraceEvents(10); - createChannel(); - assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Channel for 'fake://fake.example.com' created") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_subchannelCreationEvents() throws Exception { - channelBuilder.maxTraceEvents(10); - createChannel(); - timer.forwardNanos(1234); - AbstractSubchannel subchannel = - (AbstractSubchannel) createSubchannelSafely( - helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Child Subchannel started") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .setSubchannelRef(subchannel.getInstrumentedInternalSubchannel()) - .build()); - assertThat(getStats(subchannel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Subchannel for [[[test-addr]/{}]] created") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_nameResolvingErrorEvent() throws Exception { - timer.forwardNanos(1234); - channelBuilder.maxTraceEvents(10); - - Status error = Status.UNAVAILABLE.withDescription("simulated error"); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setError(error).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Failed to resolve name: " + error) - .setSeverity(ChannelTrace.Event.Severity.CT_WARNING) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_nameResolvedEvent() throws Exception { - timer.forwardNanos(1234); - channelBuilder.maxTraceEvents(10); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Address resolved: " - + Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_nameResolvedEvent_zeorAndNonzeroBackends() throws Exception { - timer.forwardNanos(1234); - channelBuilder.maxTraceEvents(10); - List servers = new ArrayList<>(); - servers.add(new EquivalentAddressGroup(socketAddress)); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - int prevSize = getStats(channel).channelTrace.events.size(); - ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .build(); - nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); - - prevSize = getStats(channel).channelTrace.events.size(); - nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - - prevSize = getStats(channel).channelTrace.events.size(); - nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); - - prevSize = getStats(channel).channelTrace.events.size(); - ResolutionResult resolutionResult2 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .build(); - nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - } - - @Test - public void channelTracing_serviceConfigChange() throws Exception { - timer.forwardNanos(1234); - channelBuilder.maxTraceEvents(10); - List servers = new ArrayList<>(); - servers.add(new EquivalentAddressGroup(socketAddress)); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - int prevSize = getStats(channel).channelTrace.events.size(); - Attributes attributes = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, new HashMap()) - .build(); - ManagedChannelServiceConfig2 mcsc1 = createManagedChannelServiceConfig( - ImmutableMap.of(), - new PolicySelection( - mockLoadBalancerProvider, ImmutableMap.of("foo", "bar"), null)); - ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setAttributes(attributes) - .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) - .build(); - nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - assertThat(getStats(channel).channelTrace.events.get(prevSize)) - .isEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Service config changed") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - - prevSize = getStats(channel).channelTrace.events.size(); - ResolutionResult resolutionResult2 = ResolutionResult.newBuilder().setAddresses( - Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setAttributes(attributes) - .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) - .build(); - nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); - - prevSize = getStats(channel).channelTrace.events.size(); - Map serviceConfig = new HashMap<>(); - serviceConfig.put("methodConfig", new HashMap()); - attributes = - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) - .build(); - timer.forwardNanos(1234); - ResolutionResult resolutionResult3 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setAttributes(attributes) - .setServiceConfig(ConfigOrError.fromConfig(ManagedChannelServiceConfig2.empty())) - .build(); - nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult3); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - assertThat(getStats(channel).channelTrace.events.get(prevSize)) - .isEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Service config changed") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_stateChangeEvent() throws Exception { - channelBuilder.maxTraceEvents(10); - createChannel(); - timer.forwardNanos(1234); - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Entering CONNECTING state") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_subchannelStateChangeEvent() throws Exception { - channelBuilder.maxTraceEvents(10); - createChannel(); - AbstractSubchannel subchannel = - (AbstractSubchannel) createSubchannelSafely( - helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - timer.forwardNanos(1234); - ((TransportProvider) subchannel.getInternalSubchannel()).obtainActiveTransport(); - assertThat(getStats(subchannel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("CONNECTING as requested") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_oobChannelStateChangeEvent() throws Exception { - channelBuilder.maxTraceEvents(10); - createChannel(); - OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "authority"); - timer.forwardNanos(1234); - oobChannel.handleSubchannelStateChange( - ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); - assertThat(getStats(oobChannel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Entering CONNECTING state") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelTracing_oobChannelCreationEvents() throws Exception { - channelBuilder.maxTraceEvents(10); - createChannel(); - timer.forwardNanos(1234); - OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "authority"); - assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("Child OobChannel created") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .setChannelRef(oobChannel) - .build()); - assertThat(getStats(oobChannel).channelTrace.events).contains(new ChannelTrace.Event.Builder() - .setDescription("OobChannel for [[test-addr]/{}] created") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - assertThat(getStats(oobChannel.getInternalSubchannel()).channelTrace.events).contains( - new ChannelTrace.Event.Builder() - .setDescription("Subchannel for [[test-addr]/{}] created") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void channelsAndSubchannels_instrumented_state() throws Exception { - createChannel(); - - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - helper = helperCaptor.getValue(); - - assertEquals(IDLE, getStats(channel).state); - updateBalancingStateSafely(helper, CONNECTING, mockPicker); - assertEquals(CONNECTING, getStats(channel).state); - - AbstractSubchannel subchannel = - (AbstractSubchannel) createSubchannelSafely( - helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - - assertEquals(IDLE, getStats(subchannel).state); - requestConnectionSafely(helper, subchannel); - assertEquals(CONNECTING, getStats(subchannel).state); - - MockClientTransportInfo transportInfo = transports.poll(); - - assertEquals(CONNECTING, getStats(subchannel).state); - transportInfo.listener.transportReady(); - assertEquals(READY, getStats(subchannel).state); - - assertEquals(CONNECTING, getStats(channel).state); - updateBalancingStateSafely(helper, READY, mockPicker); - assertEquals(READY, getStats(channel).state); - - channel.shutdownNow(); - assertEquals(SHUTDOWN, getStats(channel).state); - assertEquals(SHUTDOWN, getStats(subchannel).state); - } - - @Test - public void channelStat_callStarted() throws Exception { - createChannel(); - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - assertEquals(0, getStats(channel).callsStarted); - call.start(mockCallListener, new Metadata()); - assertEquals(1, getStats(channel).callsStarted); - assertEquals(executor.getTicker().read(), getStats(channel).lastCallStartedNanos); - } - - @Test - public void channelsAndSubChannels_instrumented_success() throws Exception { - channelsAndSubchannels_instrumented0(true); - } - - @Test - public void channelsAndSubChannels_instrumented_fail() throws Exception { - channelsAndSubchannels_instrumented0(false); - } - - private void channelsAndSubchannels_instrumented0(boolean success) throws Exception { - createChannel(); - - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - - // Channel stat bumped when ClientCall.start() called - assertEquals(0, getStats(channel).callsStarted); - call.start(mockCallListener, new Metadata()); - assertEquals(1, getStats(channel).callsStarted); - - ClientStream mockStream = mock(ClientStream.class); - ClientStreamTracer.Factory factory = mock(ClientStreamTracer.Factory.class); - AbstractSubchannel subchannel = - (AbstractSubchannel) createSubchannelSafely( - helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo transportInfo = transports.poll(); - transportInfo.listener.transportReady(); - ClientTransport mockTransport = transportInfo.transport; - when(mockTransport.newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn( - PickResult.withSubchannel(subchannel, factory)); - - // subchannel stat bumped when call gets assigned to it - assertEquals(0, getStats(subchannel).callsStarted); - updateBalancingStateSafely(helper, READY, mockPicker); - assertEquals(1, executor.runDueTasks()); - verify(mockStream).start(streamListenerCaptor.capture()); - assertEquals(1, getStats(subchannel).callsStarted); - - ClientStreamListener streamListener = streamListenerCaptor.getValue(); - call.halfClose(); - - // closing stream listener affects subchannel stats immediately - assertEquals(0, getStats(subchannel).callsSucceeded); - assertEquals(0, getStats(subchannel).callsFailed); - streamListener.closed(success ? Status.OK : Status.UNKNOWN, new Metadata()); - if (success) { - assertEquals(1, getStats(subchannel).callsSucceeded); - assertEquals(0, getStats(subchannel).callsFailed); - } else { - assertEquals(0, getStats(subchannel).callsSucceeded); - assertEquals(1, getStats(subchannel).callsFailed); - } - - // channel stats bumped when the ClientCall.Listener is notified - assertEquals(0, getStats(channel).callsSucceeded); - assertEquals(0, getStats(channel).callsFailed); - executor.runDueTasks(); - if (success) { - assertEquals(1, getStats(channel).callsSucceeded); - assertEquals(0, getStats(channel).callsFailed); - } else { - assertEquals(0, getStats(channel).callsSucceeded); - assertEquals(1, getStats(channel).callsFailed); - } - } - - @Test - public void channelsAndSubchannels_oob_instrumented_success() throws Exception { - channelsAndSubchannels_oob_instrumented0(true); - } - - @Test - public void channelsAndSubchannels_oob_instrumented_fail() throws Exception { - channelsAndSubchannels_oob_instrumented0(false); - } - - private void channelsAndSubchannels_oob_instrumented0(boolean success) throws Exception { - // set up - ClientStream mockStream = mock(ClientStream.class); - createChannel(); - - OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority"); - AbstractSubchannel oobSubchannel = (AbstractSubchannel) oobChannel.getSubchannel(); - FakeClock callExecutor = new FakeClock(); - CallOptions options = - CallOptions.DEFAULT.withExecutor(callExecutor.getScheduledExecutorService()); - ClientCall call = oobChannel.newCall(method, options); - Metadata headers = new Metadata(); - - // Channel stat bumped when ClientCall.start() called - assertEquals(0, getStats(oobChannel).callsStarted); - call.start(mockCallListener, headers); - assertEquals(1, getStats(oobChannel).callsStarted); - - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ManagedClientTransport.Listener transportListener = transportInfo.listener; - when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class))) - .thenReturn(mockStream); - - // subchannel stat bumped when call gets assigned to it - assertEquals(0, getStats(oobSubchannel).callsStarted); - transportListener.transportReady(); - callExecutor.runDueTasks(); - verify(mockStream).start(streamListenerCaptor.capture()); - assertEquals(1, getStats(oobSubchannel).callsStarted); - - ClientStreamListener streamListener = streamListenerCaptor.getValue(); - call.halfClose(); - - // closing stream listener affects subchannel stats immediately - assertEquals(0, getStats(oobSubchannel).callsSucceeded); - assertEquals(0, getStats(oobSubchannel).callsFailed); - streamListener.closed(success ? Status.OK : Status.UNKNOWN, new Metadata()); - if (success) { - assertEquals(1, getStats(oobSubchannel).callsSucceeded); - assertEquals(0, getStats(oobSubchannel).callsFailed); - } else { - assertEquals(0, getStats(oobSubchannel).callsSucceeded); - assertEquals(1, getStats(oobSubchannel).callsFailed); - } - - // channel stats bumped when the ClientCall.Listener is notified - assertEquals(0, getStats(oobChannel).callsSucceeded); - assertEquals(0, getStats(oobChannel).callsFailed); - callExecutor.runDueTasks(); - if (success) { - assertEquals(1, getStats(oobChannel).callsSucceeded); - assertEquals(0, getStats(oobChannel).callsFailed); - } else { - assertEquals(0, getStats(oobChannel).callsSucceeded); - assertEquals(1, getStats(oobChannel).callsFailed); - } - // oob channel is separate from the original channel - assertEquals(0, getStats(channel).callsSucceeded); - assertEquals(0, getStats(channel).callsFailed); - } - - @Test - public void channelsAndSubchannels_oob_instrumented_name() throws Exception { - createChannel(); - - String authority = "oobauthority"; - OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, authority); - assertEquals(authority, getStats(oobChannel).target); - } - - @Test - public void channelsAndSubchannels_oob_instrumented_state() throws Exception { - createChannel(); - - OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority"); - assertEquals(IDLE, getStats(oobChannel).state); - - oobChannel.getSubchannel().requestConnection(); - assertEquals(CONNECTING, getStats(oobChannel).state); - - MockClientTransportInfo transportInfo = transports.poll(); - ManagedClientTransport.Listener transportListener = transportInfo.listener; - - transportListener.transportReady(); - assertEquals(READY, getStats(oobChannel).state); - - // oobchannel state is separate from the ManagedChannel - assertEquals(IDLE, getStats(channel).state); - channel.shutdownNow(); - assertEquals(SHUTDOWN, getStats(channel).state); - assertEquals(SHUTDOWN, getStats(oobChannel).state); - } - - @Test - public void binaryLogInstalled() throws Exception { - final SettableFuture intercepted = SettableFuture.create(); - channelBuilder.binlog = new BinaryLog() { - @Override - public void close() throws IOException { - // noop - } - - @Override - public ServerMethodDefinition wrapMethodDefinition( - ServerMethodDefinition oMethodDef) { - return oMethodDef; - } - - @Override - public Channel wrapChannel(Channel channel) { - return ClientInterceptors.intercept(channel, - new ClientInterceptor() { - @Override - public ClientCall interceptCall( - MethodDescriptor method, - CallOptions callOptions, - Channel next) { - intercepted.set(true); - return next.newCall(method, callOptions); - } - }); - } - }; - - createChannel(); - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - assertTrue(intercepted.get()); - } - - @Test - public void retryBackoffThenChannelShutdown_retryShouldStillHappen_newCallShouldFail() { - Map retryPolicy = new HashMap<>(); - retryPolicy.put("maxAttempts", 3D); - retryPolicy.put("initialBackoff", "10s"); - retryPolicy.put("maxBackoff", "30s"); - retryPolicy.put("backoffMultiplier", 2D); - retryPolicy.put("retryableStatusCodes", Arrays.asList("UNAVAILABLE")); - Map methodConfig = new HashMap<>(); - Map name = new HashMap<>(); - name.put("service", "service"); - methodConfig.put("name", Arrays.asList(name)); - methodConfig.put("retryPolicy", retryPolicy); - Map rawServiceConfig = new HashMap<>(); - rawServiceConfig.put("methodConfig", Arrays.asList(methodConfig)); - Attributes attributesWithRetryPolicy = Attributes - .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig).build(); - - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - - channelBuilder.nameResolverFactory(nameResolverFactory); - channelBuilder.executor(MoreExecutors.directExecutor()); - channelBuilder.enableRetry(); - RetriableStream.setRandom( - // not random - new Random() { - @Override - public double nextDouble() { - return 1D; // fake random - } - }); - - requestConnection = false; - createChannel(); - - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - helper = helperCaptor.getValue(); - verify(mockLoadBalancer).handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(nameResolverFactory.servers) - .setAttributes(attributesWithRetryPolicy) - .build()); - - // simulating request connection and then transport ready after resolved address - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ClientStream mockStream = mock(ClientStream.class); - ClientStream mockStream2 = mock(ClientStream.class); - when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream).thenReturn(mockStream2); - transportInfo.listener.transportReady(); - updateBalancingStateSafely(helper, READY, mockPicker); - - ArgumentCaptor streamListenerCaptor = - ArgumentCaptor.forClass(ClientStreamListener.class); - verify(mockStream).start(streamListenerCaptor.capture()); - assertThat(timer.getPendingTasks()).isEmpty(); - - // trigger retry - streamListenerCaptor.getValue().closed(Status.UNAVAILABLE, new Metadata()); - - // in backoff - timer.forwardTime(5, TimeUnit.SECONDS); - assertThat(timer.getPendingTasks()).hasSize(1); - verify(mockStream2, never()).start(any(ClientStreamListener.class)); - - // shutdown during backoff period - channel.shutdown(); - - assertThat(timer.getPendingTasks()).hasSize(1); - verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class)); - - ClientCall call2 = channel.newCall(method, CallOptions.DEFAULT); - call2.start(mockCallListener2, new Metadata()); - - ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); - verify(mockCallListener2).onClose(statusCaptor.capture(), any(Metadata.class)); - assertSame(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode()); - assertEquals("Channel shutdown invoked", statusCaptor.getValue().getDescription()); - - // backoff ends - timer.forwardTime(5, TimeUnit.SECONDS); - assertThat(timer.getPendingTasks()).isEmpty(); - verify(mockStream2).start(streamListenerCaptor.capture()); - verify(mockLoadBalancer, never()).shutdown(); - assertFalse( - "channel.isTerminated() is expected to be false but was true", - channel.isTerminated()); - - streamListenerCaptor.getValue().closed(Status.INTERNAL, new Metadata()); - verify(mockLoadBalancer).shutdown(); - // simulating the shutdown of load balancer triggers the shutdown of subchannel - shutdownSafely(helper, subchannel); - transportInfo.listener.transportShutdown(Status.INTERNAL); - transportInfo.listener.transportTerminated(); // simulating transport terminated - assertTrue( - "channel.isTerminated() is expected to be true but was false", - channel.isTerminated()); - } - - @Test - public void hedgingScheduledThenChannelShutdown_hedgeShouldStillHappen_newCallShouldFail() { - Map hedgingPolicy = new HashMap<>(); - hedgingPolicy.put("maxAttempts", 3D); - hedgingPolicy.put("hedgingDelay", "10s"); - hedgingPolicy.put("nonFatalStatusCodes", Arrays.asList("UNAVAILABLE")); - Map methodConfig = new HashMap<>(); - Map name = new HashMap<>(); - name.put("service", "service"); - methodConfig.put("name", Arrays.asList(name)); - methodConfig.put("hedgingPolicy", hedgingPolicy); - Map rawServiceConfig = new HashMap<>(); - rawServiceConfig.put("methodConfig", Arrays.asList(methodConfig)); - Attributes attributesWithRetryPolicy = Attributes - .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig).build(); - - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - - channelBuilder.nameResolverFactory(nameResolverFactory); - channelBuilder.executor(MoreExecutors.directExecutor()); - channelBuilder.enableRetry(); - - requestConnection = false; - createChannel(); - - ClientCall call = channel.newCall(method, CallOptions.DEFAULT); - call.start(mockCallListener, new Metadata()); - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - helper = helperCaptor.getValue(); - verify(mockLoadBalancer).handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(nameResolverFactory.servers) - .setAttributes(attributesWithRetryPolicy) - .build()); - - // simulating request connection and then transport ready after resolved address - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) - .thenReturn(PickResult.withSubchannel(subchannel)); - requestConnectionSafely(helper, subchannel); - MockClientTransportInfo transportInfo = transports.poll(); - ConnectionClientTransport mockTransport = transportInfo.transport; - ClientStream mockStream = mock(ClientStream.class); - ClientStream mockStream2 = mock(ClientStream.class); - when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class))) - .thenReturn(mockStream).thenReturn(mockStream2); - transportInfo.listener.transportReady(); - updateBalancingStateSafely(helper, READY, mockPicker); - - ArgumentCaptor streamListenerCaptor = - ArgumentCaptor.forClass(ClientStreamListener.class); - verify(mockStream).start(streamListenerCaptor.capture()); - - // in hedging delay backoff - timer.forwardTime(5, TimeUnit.SECONDS); - assertThat(timer.numPendingTasks()).isEqualTo(1); - // first hedge fails - streamListenerCaptor.getValue().closed(Status.UNAVAILABLE, new Metadata()); - verify(mockStream2, never()).start(any(ClientStreamListener.class)); - - // shutdown during backoff period - channel.shutdown(); - - assertThat(timer.numPendingTasks()).isEqualTo(1); - verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class)); - - ClientCall call2 = channel.newCall(method, CallOptions.DEFAULT); - call2.start(mockCallListener2, new Metadata()); - - ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); - verify(mockCallListener2).onClose(statusCaptor.capture(), any(Metadata.class)); - assertSame(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode()); - assertEquals("Channel shutdown invoked", statusCaptor.getValue().getDescription()); - - // backoff ends - timer.forwardTime(5, TimeUnit.SECONDS); - assertThat(timer.numPendingTasks()).isEqualTo(1); - verify(mockStream2).start(streamListenerCaptor.capture()); - verify(mockLoadBalancer, never()).shutdown(); - assertFalse( - "channel.isTerminated() is expected to be false but was true", - channel.isTerminated()); - - streamListenerCaptor.getValue().closed(Status.INTERNAL, new Metadata()); - assertThat(timer.numPendingTasks()).isEqualTo(0); - verify(mockLoadBalancer).shutdown(); - // simulating the shutdown of load balancer triggers the shutdown of subchannel - shutdownSafely(helper, subchannel); - // simulating transport shutdown & terminated - transportInfo.listener.transportShutdown(Status.INTERNAL); - transportInfo.listener.transportTerminated(); - assertTrue( - "channel.isTerminated() is expected to be true but was false", - channel.isTerminated()); - } - - @Test - public void badServiceConfigIsRecoverable() throws Exception { - final Map invalidServiceConfig = - parseConfig("{\"loadBalancingConfig\": [{\"kaboom\": {}}]}"); - final List addresses = - ImmutableList.of(new EquivalentAddressGroup(new SocketAddress() {})); - final class FakeNameResolver extends NameResolver { - Listener2 listener; - - @Override - public String getServiceAuthority() { - return "also fake"; - } - - @Override - public void start(Listener2 listener) { - this.listener = listener; - listener.onResult( - ResolutionResult.newBuilder() - .setAddresses(addresses) - .setAttributes( - Attributes.newBuilder() - .set( - GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, invalidServiceConfig) - .build()) - .setServiceConfig( - ConfigOrError.fromError( - Status.INTERNAL.withDescription("kaboom is invalid"))) - .build()); - } - - @Override - public void shutdown() {} - } - - final class FakeNameResolverFactory2 extends NameResolver.Factory { - FakeNameResolver resolver; - - @Nullable - @Override - public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { - return (resolver = new FakeNameResolver()); - } - - @Override - public String getDefaultScheme() { - return "fake"; - } - } - - FakeNameResolverFactory2 factory = new FakeNameResolverFactory2(); - final class CustomBuilder extends AbstractManagedChannelImplBuilder { - - CustomBuilder() { - super(TARGET); - this.executorPool = ManagedChannelImplTest2.this.executorPool; - this.channelz = ManagedChannelImplTest2.this.channelz; - } - - @Override - protected ClientTransportFactory buildTransportFactory() { - return mockTransportFactory; - } - } - - ManagedChannel mychannel = new CustomBuilder().nameResolverFactory(factory).build(); - - ClientCall call1 = - mychannel.newCall(TestMethodDescriptors.voidMethod(), CallOptions.DEFAULT); - ListenableFuture future1 = ClientCalls.futureUnaryCall(call1, null); - executor.runDueTasks(); - try { - future1.get(1, TimeUnit.SECONDS); - Assert.fail(); - } catch (ExecutionException e) { - assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("kaboom"); - } - - // ok the service config is bad, let's fix it. - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); - Object fakeLbConfig = new Object(); - PolicySelection lbConfigs = - new PolicySelection( - mockLoadBalancerProvider, rawServiceConfig, fakeLbConfig); - mockLoadBalancerProvider.parseLoadBalancingPolicyConfig(rawServiceConfig); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, lbConfigs); - factory.resolver.listener.onResult( - ResolutionResult.newBuilder() - .setAddresses(addresses) - .setAttributes( - Attributes.newBuilder() - .set( - GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig) - .build()) - .setServiceConfig(ConfigOrError.fromConfig(managedChannelServiceConfig)) - .build()); - - ClientCall call2 = mychannel.newCall( - TestMethodDescriptors.voidMethod(), - CallOptions.DEFAULT.withDeadlineAfter(5, TimeUnit.SECONDS)); - ListenableFuture future2 = ClientCalls.futureUnaryCall(call2, null); - - timer.forwardTime(5, TimeUnit.SECONDS); - - executor.runDueTasks(); - try { - future2.get(); - Assert.fail(); - } catch (ExecutionException e) { - assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("deadline"); - } - - mychannel.shutdownNow(); - // Now for Deadline_exceeded, stream shutdown is delayed, calling shutdownNow() on a open stream - // will add a task to executor. Cleaning that task here. - executor.runDueTasks(); - } - - @Deprecated - @Test - public void nameResolver_forwardingStartOldApi() { - final AtomicReference listenerCapture = new AtomicReference<>(); - final NameResolver noopResolver = new NameResolver() { - @Override - public String getServiceAuthority() { - return "fake-authority"; - } - - @Override - public void start(Listener2 listener) { - listenerCapture.set(listener); - } - - @Override - public void shutdown() {} - }; - - // This forwarding resolver is still on the old start() API. Despite that, the delegate - // resolver which is on the new API should get the new Listener2. - final NameResolver oldApiForwardingResolver = new NameResolver() { - @Override - public String getServiceAuthority() { - return noopResolver.getServiceAuthority(); - } - - @Override - public void start(Listener listener) { - noopResolver.start(listener); - } - - @Override - public void shutdown() { - noopResolver.shutdown(); - } - }; - - NameResolver.Factory oldApiResolverFactory = new NameResolver.Factory() { - @Override - public NameResolver newNameResolver(URI targetUri, NameResolver.Helper helper) { - return oldApiForwardingResolver; - } - - @Override - public String getDefaultScheme() { - return "fakescheme"; - } - }; - channelBuilder.nameResolverFactory(oldApiResolverFactory); - createChannel(); - - assertThat(listenerCapture.get()).isNotNull(); - } - - @Test - public void nameResolverArgsPropagation() { - final AtomicReference capturedArgs = new AtomicReference<>(); - final NameResolver noopResolver = new NameResolver() { - @Override - public String getServiceAuthority() { - return "fake-authority"; - } - - @Override - public void start(Listener2 listener) { - } - - @Override - public void shutdown() {} - }; - ProxyDetector neverProxy = new ProxyDetector() { - @Override - public ProxiedSocketAddress proxyFor(SocketAddress targetAddress) { - return null; - } - }; - NameResolver.Factory factory = new NameResolver.Factory() { - @Override - public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { - capturedArgs.set(args); - return noopResolver; - } - - @Override - public String getDefaultScheme() { - return "fakescheme"; - } - }; - channelBuilder.nameResolverFactory(factory).proxyDetector(neverProxy); - createChannel(); - - NameResolver.Args args = capturedArgs.get(); - assertThat(args).isNotNull(); - assertThat(args.getDefaultPort()).isEqualTo(DEFAULT_PORT); - assertThat(args.getProxyDetector()).isSameInstanceAs(neverProxy); - - verify(offloadExecutor, never()).execute(any(Runnable.class)); - args.getOffloadExecutor() - .execute( - new Runnable() { - @Override - public void run() {} - }); - verify(offloadExecutor, times(1)).execute(any(Runnable.class)); - } - - @Test - public void getAuthorityAfterShutdown() throws Exception { - createChannel(); - assertEquals(SERVICE_NAME, channel.authority()); - channel.shutdownNow().awaitTermination(1, TimeUnit.SECONDS); - assertEquals(SERVICE_NAME, channel.authority()); - } - - @Test - public void nameResolverHelper_emptyConfigSucceeds() { - boolean retryEnabled = false; - int maxRetryAttemptsLimit = 2; - int maxHedgedAttemptsLimit = 3; - AutoConfiguredLoadBalancerFactory2 autoConfiguredLoadBalancerFactory = - new AutoConfiguredLoadBalancerFactory2("pick_first"); - - ScParser parser = new ScParser( - retryEnabled, - maxRetryAttemptsLimit, - maxHedgedAttemptsLimit, - autoConfiguredLoadBalancerFactory, - mock(ChannelLogger.class)); - - ConfigOrError coe = parser.parseServiceConfig(ImmutableMap.of()); - - assertThat(coe.getError()).isNull(); - ManagedChannelServiceConfig2 cfg = (ManagedChannelServiceConfig2) coe.getConfig(); - assertThat(cfg.getServiceMap()).isEmpty(); - assertThat(cfg.getServiceMethodMap()).isEmpty(); - } - - @Test - public void nameResolverHelper_badConfigFails() { - boolean retryEnabled = false; - int maxRetryAttemptsLimit = 2; - int maxHedgedAttemptsLimit = 3; - AutoConfiguredLoadBalancerFactory2 autoConfiguredLoadBalancerFactory = - new AutoConfiguredLoadBalancerFactory2("pick_first"); - - ScParser parser = new ScParser( - retryEnabled, - maxRetryAttemptsLimit, - maxHedgedAttemptsLimit, - autoConfiguredLoadBalancerFactory, - mock(ChannelLogger.class)); - - ConfigOrError coe = - parser.parseServiceConfig(ImmutableMap.of("methodConfig", "bogus")); - - assertThat(coe.getError()).isNotNull(); - assertThat(coe.getError().getCode()).isEqualTo(Code.UNKNOWN); - assertThat(coe.getError().getDescription()).contains("failed to parse service config"); - assertThat(coe.getError().getCause()).isInstanceOf(ClassCastException.class); - } - - @Test - public void nameResolverHelper_noConfigChosen() { - boolean retryEnabled = false; - int maxRetryAttemptsLimit = 2; - int maxHedgedAttemptsLimit = 3; - AutoConfiguredLoadBalancerFactory2 autoConfiguredLoadBalancerFactory = - new AutoConfiguredLoadBalancerFactory2("pick_first"); - - ScParser parser = new ScParser( - retryEnabled, - maxRetryAttemptsLimit, - maxHedgedAttemptsLimit, - autoConfiguredLoadBalancerFactory, - mock(ChannelLogger.class)); - - ConfigOrError coe = - parser.parseServiceConfig(ImmutableMap.of("loadBalancingConfig", ImmutableList.of())); - - assertThat(coe.getError()).isNull(); - ManagedChannelServiceConfig2 cfg = (ManagedChannelServiceConfig2) coe.getConfig(); - assertThat(cfg.getLoadBalancingConfig()).isNull(); - } - - @Test - public void disableServiceConfigLookUp_noDefaultConfig() throws Exception { - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - try { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - channelBuilder.disableServiceConfigLookUp(); - - Map rawServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":true}]}"); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - - createChannel(); - - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); - Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)).isEmpty(); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); - } finally { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - } - - @Test - public void disableServiceConfigLookUp_withDefaultConfig() throws Exception { - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - try { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - channelBuilder.disableServiceConfigLookUp(); - Map defaultServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":true}]}"); - channelBuilder.defaultServiceConfig(defaultServiceConfig); - - Map rawServiceConfig = new HashMap<>(); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - - createChannel(); - - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); - Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(defaultServiceConfig); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); - } finally { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - } - - @Test - public void enableServiceConfigLookUp_noDefaultConfig() throws Exception { - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - try { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - - Map rawServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":true}]}"); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - - createChannel(); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); - Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(rawServiceConfig); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); - - // new config - rawServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":false}]}"); - managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - nameResolverFactory.allResolved(); - - resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); - actualAttrs = resultCaptor.getValue().getAttributes(); - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(rawServiceConfig); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); - } finally { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - } - - @Test - public void enableServiceConfigLookUp_withDefaultConfig() throws Exception { - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - try { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - Map defaultServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":true}]}"); - channelBuilder.defaultServiceConfig(defaultServiceConfig); - - Map rawServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService2\"}]," - + "\"waitForReady\":false}]}"); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - - createChannel(); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); - Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(rawServiceConfig); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); - } finally { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - } - - @Test - public void enableServiceConfigLookUp_resolverReturnsNoConfig_withDefaultConfig() - throws Exception { - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - try { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - Map defaultServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":true}]}"); - channelBuilder.defaultServiceConfig(defaultServiceConfig); - - nameResolverFactory.nextRawServiceConfig.set(null); - nameResolverFactory.nextConfigOrError.set(null); - - createChannel(); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); - Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) - .isEqualTo(defaultServiceConfig); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); - } finally { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - } - - @Test - public void enableServiceConfigLookUp_resolverReturnsNoConfig_noDefaultConfig() { - LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); - try { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - - Map rawServiceConfig = Collections.emptyMap(); - ManagedChannelServiceConfig2 managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, null); - nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); - nameResolverFactory.nextConfigOrError.set( - ConfigOrError.fromConfig(managedChannelServiceConfig)); - - createChannel(); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); - assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); - Attributes actualAttrs = resultCaptor.getValue().getAttributes(); - assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)).isEmpty(); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); - } finally { - LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); - } - } - - @Test - public void useDefaultImmediatelyIfDisableLookUp() throws Exception { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - channelBuilder.disableServiceConfigLookUp(); - Map defaultServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":true}]}"); - channelBuilder.defaultServiceConfig(defaultServiceConfig); - requestConnection = false; - channelBuilder.maxTraceEvents(10); - - createChannel(); - - int size = getStats(channel).channelTrace.events.size(); - assertThat(getStats(channel).channelTrace.events.get(size - 1)) - .isEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Service config look-up disabled, using default service config") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - @Test - public void notUseDefaultImmediatelyIfEnableLookUp() throws Exception { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(ImmutableList.of(addressGroup)).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - Map defaultServiceConfig = - parseConfig("{\"methodConfig\":[{" - + "\"name\":[{\"service\":\"SimpleService1\"}]," - + "\"waitForReady\":true}]}"); - channelBuilder.defaultServiceConfig(defaultServiceConfig); - requestConnection = false; - channelBuilder.maxTraceEvents(10); - - createChannel(); - - int size = getStats(channel).channelTrace.events.size(); - assertThat(getStats(channel).channelTrace.events.get(size - 1)) - .isNotEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Using default service config") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - - private static final class ChannelBuilder - extends AbstractManagedChannelImplBuilder { - - ChannelBuilder() { - super(TARGET); - } - - @Override protected ClientTransportFactory buildTransportFactory() { - throw new UnsupportedOperationException(); - } - - @Override protected int getDefaultPort() { - return DEFAULT_PORT; - } - } - - private static final class FakeBackoffPolicyProvider implements BackoffPolicy.Provider { - @Override - public BackoffPolicy get() { - return new BackoffPolicy() { - int multiplier = 1; - - @Override - public long nextBackoffNanos() { - return RECONNECT_BACKOFF_INTERVAL_NANOS * multiplier++; - } - }; - } - } - - private static final class FakeNameResolverFactory extends NameResolver.Factory { - final URI expectedUri; - final List servers; - final boolean resolvedAtStart; - final Status error; - final ArrayList resolvers = new ArrayList<>(); - final AtomicReference nextConfigOrError = new AtomicReference<>(); - final AtomicReference> nextRawServiceConfig = new AtomicReference<>(); - - FakeNameResolverFactory( - URI expectedUri, - List servers, - boolean resolvedAtStart, - Status error) { - this.expectedUri = expectedUri; - this.servers = servers; - this.resolvedAtStart = resolvedAtStart; - this.error = error; - } - - @Override - public NameResolver newNameResolver(final URI targetUri, NameResolver.Args args) { - if (!expectedUri.equals(targetUri)) { - return null; - } - assertEquals(DEFAULT_PORT, args.getDefaultPort()); - FakeNameResolverFactory.FakeNameResolver resolver = - new FakeNameResolverFactory.FakeNameResolver(error); - resolvers.add(resolver); - return resolver; - } - - @Override - public String getDefaultScheme() { - return "fake"; - } - - void allResolved() { - for (FakeNameResolverFactory.FakeNameResolver resolver : resolvers) { - resolver.resolved(); - } - } - - final class FakeNameResolver extends NameResolver { - Listener2 listener; - boolean shutdown; - int refreshCalled; - Status error; - - FakeNameResolver(Status error) { - this.error = error; - } - - @Override public String getServiceAuthority() { - return expectedUri.getAuthority(); - } - - @Override public void start(Listener2 listener) { - this.listener = listener; - if (resolvedAtStart) { - resolved(); - } - } - - @Override public void refresh() { - refreshCalled++; - resolved(); - } - - void resolved() { - if (error != null) { - listener.onError(error); - return; - } - ResolutionResult.Builder builder = - ResolutionResult.newBuilder() - .setAddresses(servers); - ConfigOrError configOrError = nextConfigOrError.get(); - Map rawServiceConfig = nextRawServiceConfig.get(); - if (configOrError != null) { - builder.setServiceConfig(configOrError); - } - if (rawServiceConfig != null) { - builder.setAttributes( - Attributes.newBuilder() - .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig) - .build()); - } - - listener.onResult(builder.build()); - } - - @Override public void shutdown() { - shutdown = true; - } - - @Override - public String toString() { - return "FakeNameResolver"; - } - } - - static final class Builder { - final URI expectedUri; - List servers = ImmutableList.of(); - boolean resolvedAtStart = true; - Status error = null; - - Builder(URI expectedUri) { - this.expectedUri = expectedUri; - } - - FakeNameResolverFactory.Builder setServers(List servers) { - this.servers = servers; - return this; - } - - FakeNameResolverFactory.Builder setResolvedAtStart(boolean resolvedAtStart) { - this.resolvedAtStart = resolvedAtStart; - return this; - } - - FakeNameResolverFactory.Builder setError(Status error) { - this.error = error; - return this; - } - - FakeNameResolverFactory build() { - return new FakeNameResolverFactory(expectedUri, servers, resolvedAtStart, error); - } - } - } - - private static ChannelStats getStats(AbstractSubchannel subchannel) throws Exception { - return subchannel.getInstrumentedInternalSubchannel().getStats().get(); - } - - private static ChannelStats getStats( - InternalInstrumented instrumented) throws Exception { - return instrumented.getStats().get(); - } - - private FakeClock.ScheduledTask getNameResolverRefresh() { - return Iterables.getOnlyElement(timer.getPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER), null); - } - - // Helper methods to call methods from SynchronizationContext - private static Subchannel createSubchannelSafely( - final Helper helper, final EquivalentAddressGroup addressGroup, final Attributes attrs, - final SubchannelStateListener stateListener) { - final AtomicReference resultCapture = new AtomicReference<>(); - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - Subchannel s = helper.createSubchannel(CreateSubchannelArgs.newBuilder() - .setAddresses(addressGroup) - .setAttributes(attrs) - .build()); - s.start(stateListener); - resultCapture.set(s); - } - }); - return resultCapture.get(); - } - - private static Subchannel createUnstartedSubchannel( - final Helper helper, final EquivalentAddressGroup addressGroup, final Attributes attrs) { - final AtomicReference resultCapture = new AtomicReference<>(); - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - Subchannel s = helper.createSubchannel(CreateSubchannelArgs.newBuilder() - .setAddresses(addressGroup) - .setAttributes(attrs) - .build()); - resultCapture.set(s); - } - }); - return resultCapture.get(); - } - - private static void requestConnectionSafely(Helper helper, final Subchannel subchannel) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - subchannel.requestConnection(); - } - }); - } - - private static void updateBalancingStateSafely( - final Helper helper, final ConnectivityState state, final SubchannelPicker picker) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - helper.updateBalancingState(state, picker); - } - }); - } - - private static void refreshNameResolutionSafely(final Helper helper) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - helper.refreshNameResolution(); - } - }); - } - - private static void updateAddressesSafely( - Helper helper, final Subchannel subchannel, final List addrs) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - subchannel.updateAddresses(addrs); - } - }); - } - - private static void shutdownSafely( - final Helper helper, final Subchannel subchannel) { - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - subchannel.shutdown(); - } - }); - } - - @SuppressWarnings("unchecked") - private static Map parseConfig(String json) throws Exception { - return (Map) JsonParser.parse(json); - } - - private static ManagedChannelServiceConfig2 createManagedChannelServiceConfig( - Map rawServiceConfig, PolicySelection policySelection) { - // Provides dummy variable for retry related params (not used in this test class) - return ManagedChannelServiceConfig2 - .fromServiceConfig(rawServiceConfig, true, 3, 4, policySelection); - } -} diff --git a/core/src/test/java/io/grpc/internal/RetryPolicyTest.java b/core/src/test/java/io/grpc/internal/RetryPolicyTest.java index fc13607107..2e02078698 100644 --- a/core/src/test/java/io/grpc/internal/RetryPolicyTest.java +++ b/core/src/test/java/io/grpc/internal/RetryPolicyTest.java @@ -62,10 +62,17 @@ public class RetryPolicyTest { @SuppressWarnings("unchecked") Map serviceConfig = (Map) serviceConfigObj; - ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( - /* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 4, - /* maxHedgedAttemptsLimit = */ 3); - serviceConfigInterceptor.handleUpdate(serviceConfig); + ServiceConfigInterceptor serviceConfigInterceptor = + new ServiceConfigInterceptor(/* retryEnabled= */ true); + serviceConfigInterceptor + .handleUpdate( + ManagedChannelServiceConfig + .fromServiceConfig( + serviceConfig, + /* retryEnabled= */ true, + /* maxRetryAttemptsLimit= */ 4, + /* maxHedgedAttemptsLimit= */ 3, + /* loadBalancingConfig= */ null)); MethodDescriptor.Builder builder = TestMethodDescriptors.voidMethod().toBuilder(); @@ -140,10 +147,17 @@ public class RetryPolicyTest { @SuppressWarnings("unchecked") Map serviceConfig = (Map) serviceConfigObj; - ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( - /* retryEnabled = */ false, /* maxRetryAttemptsLimit = */ 4, - /* maxHedgedAttemptsLimit = */ 3); - serviceConfigInterceptor.handleUpdate(serviceConfig); + ServiceConfigInterceptor serviceConfigInterceptor = + new ServiceConfigInterceptor(/* retryEnabled= */ false); + serviceConfigInterceptor + .handleUpdate( + ManagedChannelServiceConfig + .fromServiceConfig( + serviceConfig, + /* retryEnabled= */ false, + /* maxRetryAttemptsLimit= */ 4, + /* maxHedgedAttemptsLimit= */ 3, + /* loadBalancingConfig= */ null)); MethodDescriptor.Builder builder = TestMethodDescriptors.voidMethod().toBuilder(); diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java index 24cbd589a2..75b11771ce 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java @@ -95,7 +95,7 @@ public class ServiceConfigErrorHandlingTest { @Override public boolean shouldAccept(Runnable command) { return command.toString().contains( - ManagedChannelImpl2.DelayedNameResolverRefresh.class.getName()); + ManagedChannelImpl.DelayedNameResolverRefresh.class.getName()); } }; @@ -104,7 +104,7 @@ public class ServiceConfigErrorHandlingTest { @Rule public final ExpectedException thrown = ExpectedException.none(); @Rule public final MockitoRule mocks = MockitoJUnit.rule(); - private ManagedChannelImpl2 channel; + private ManagedChannelImpl channel; private final AtomicReference nextLbPolicyConfigError = new AtomicReference<>(); private FakeLoadBalancer mockLoadBalancer = @@ -157,7 +157,7 @@ public class ServiceConfigErrorHandlingTest { checkState(channel == null); channel = - new ManagedChannelImpl2( + new ManagedChannelImpl( channelBuilder, mockTransportFactory, new FakeBackoffPolicyProvider(), @@ -175,7 +175,7 @@ public class ServiceConfigErrorHandlingTest { channel.exitIdleMode(); } }); - if (channelBuilder.idleTimeoutMillis != ManagedChannelImpl2.IDLE_TIMEOUT_MILLIS_DISABLE) { + if (channelBuilder.idleTimeoutMillis != ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE) { numExpectedTasks += 1; } diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java index 31bede998a..d339e4423d 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java @@ -47,7 +47,6 @@ import org.mockito.MockitoAnnotations; /** * Unit tests for {@link ServiceConfigInterceptor}. */ -@Deprecated // migrate to ServiceConfigInterceptor(Test)?2 @RunWith(JUnit4.class) public class ServiceConfigInterceptorTest { @@ -61,8 +60,8 @@ public class ServiceConfigInterceptorTest { MockitoAnnotations.initMocks(this); } - private final ServiceConfigInterceptor interceptor = new ServiceConfigInterceptor( - /* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 5, /* maxHedgedAttemptsLimit = */ 6); + private final ServiceConfigInterceptor interceptor = + new ServiceConfigInterceptor(/* retryEnabled = */ true); private final String fullMethodName = MethodDescriptor.generateFullMethodName("service", "method"); @@ -72,8 +71,6 @@ public class ServiceConfigInterceptorTest { .setFullMethodName(fullMethodName) .build(); - - private static final class JsonObj extends HashMap { private JsonObj(Object ... kv) { for (int i = 0; i < kv.length; i += 2) { @@ -93,8 +90,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel); @@ -108,7 +107,7 @@ public class ServiceConfigInterceptorTest { JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(createManagedChannelServiceConfig(serviceConfig)); interceptor.handleUpdate(null); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel); @@ -134,8 +133,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 1d); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); @@ -148,8 +149,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 10d); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall( methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(5), channel); @@ -163,8 +166,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 5d); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall( methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(10), channel); @@ -178,8 +183,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 1d); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); @@ -192,8 +199,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 5d); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall( methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(10), channel); @@ -207,8 +216,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 10d); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall( methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(5), channel); @@ -222,8 +233,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", false); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withWaitForReady(), channel); @@ -241,8 +254,10 @@ public class ServiceConfigInterceptorTest { JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2), "timeout", "1s"); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); @@ -255,8 +270,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "100000s"); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); Deadline existingDeadline = Deadline.after(1000, TimeUnit.NANOSECONDS); interceptor.interceptCall( @@ -273,8 +290,10 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "1s"); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); Deadline existingDeadline = Deadline.after(1234567890, TimeUnit.NANOSECONDS); interceptor.interceptCall( @@ -284,7 +303,6 @@ public class ServiceConfigInterceptorTest { assertThat(callOptionsCap.getValue().getDeadline()).isNotEqualTo(existingDeadline); } - @Test public void handleUpdate_failsOnMissingServiceName() { JsonObj name = new JsonObj("method", "method"); @@ -294,9 +312,11 @@ public class ServiceConfigInterceptorTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("missing service"); - interceptor.handleUpdate(serviceConfig); - } + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); + } @Test public void handleUpdate_failsOnDuplicateMethod() { @@ -308,7 +328,10 @@ public class ServiceConfigInterceptorTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Duplicate method"); - interceptor.handleUpdate(serviceConfig); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); + + interceptor.handleUpdate(parsedServiceConfig); } @Test @@ -319,7 +342,10 @@ public class ServiceConfigInterceptorTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("no names in method config"); - interceptor.handleUpdate(serviceConfig); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); + + interceptor.handleUpdate(parsedServiceConfig); } @Test @@ -332,7 +358,10 @@ public class ServiceConfigInterceptorTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Duplicate service"); - interceptor.handleUpdate(serviceConfig); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); + + interceptor.handleUpdate(parsedServiceConfig); } @Test @@ -346,7 +375,10 @@ public class ServiceConfigInterceptorTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Duplicate service"); - interceptor.handleUpdate(serviceConfig); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); + + interceptor.handleUpdate(parsedServiceConfig); } @Test @@ -358,13 +390,17 @@ public class ServiceConfigInterceptorTest { JsonObj name2 = new JsonObj("service", "service", "method", "method"); JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2)); JsonObj serviceConfig2 = new JsonObj("methodConfig", new JsonList(methodConfig2)); + ManagedChannelServiceConfig parsedServiceConfig1 = + createManagedChannelServiceConfig(serviceConfig1); + ManagedChannelServiceConfig parsedServiceConfig2 = + createManagedChannelServiceConfig(serviceConfig2); - interceptor.handleUpdate(serviceConfig1); + interceptor.handleUpdate(parsedServiceConfig1); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isNotEmpty(); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); - interceptor.handleUpdate(serviceConfig2); + interceptor.handleUpdate(parsedServiceConfig2); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isNotEmpty(); @@ -376,8 +412,10 @@ public class ServiceConfigInterceptorTest { JsonObj name2 = new JsonObj("service", "service", "method", "method"); JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()) .containsExactly( @@ -387,7 +425,6 @@ public class ServiceConfigInterceptorTest { "service2", new MethodInfo(methodConfig, false, 1, 1)); } - @Test public void methodInfo_validateDeadline() { JsonObj name = new JsonObj("service", "service"); @@ -408,7 +445,6 @@ public class ServiceConfigInterceptorTest { assertThat(info.timeoutNanos).isEqualTo(Long.MAX_VALUE); } - @Test public void methodInfo_badMaxRequestSize() { JsonObj name = new JsonObj("service", "service"); @@ -431,6 +467,17 @@ public class ServiceConfigInterceptorTest { new MethodInfo(methodConfig, false, 1, 1); } + private static ManagedChannelServiceConfig createManagedChannelServiceConfig( + JsonObj rawServiceConfig) { + // current tests doesn't use any other values except rawServiceConfig, so provide dummy values. + return ManagedChannelServiceConfig.fromServiceConfig( + rawServiceConfig, + /* retryEnabled= */ true, + /* maxRetryAttemptsLimit= */ 3, + /* maxHedgedAttemptsLimit= */ 4, + /* loadBalancingConfig= */ null); + } + private static final class NoopMarshaller implements MethodDescriptor.Marshaller { @Override diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest2.java b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest2.java deleted file mode 100644 index 12edbf23d6..0000000000 --- a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest2.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright 2018 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.internal; - -import static com.google.common.truth.Truth.assertThat; -import static io.grpc.internal.ServiceConfigInterceptor2.HEDGING_POLICY_KEY; -import static io.grpc.internal.ServiceConfigInterceptor2.RETRY_POLICY_KEY; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; - -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.Deadline; -import io.grpc.MethodDescriptor; -import io.grpc.MethodDescriptor.MethodType; -import io.grpc.internal.ManagedChannelServiceConfig2.MethodInfo; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.concurrent.TimeUnit; -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.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Unit tests for {@link ServiceConfigInterceptor2}. - */ -@RunWith(JUnit4.class) -public class ServiceConfigInterceptorTest2 { - - @Rule public final ExpectedException thrown = ExpectedException.none(); - - @Mock private Channel channel; - @Captor private ArgumentCaptor callOptionsCap; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - private final ServiceConfigInterceptor2 interceptor = - new ServiceConfigInterceptor2(/* retryEnabled = */ true); - - private final String fullMethodName = - MethodDescriptor.generateFullMethodName("service", "method"); - private final MethodDescriptor methodDescriptor = - MethodDescriptor.newBuilder(new NoopMarshaller(), new NoopMarshaller()) - .setType(MethodType.UNARY) - .setFullMethodName(fullMethodName) - .build(); - - private static final class JsonObj extends HashMap { - private JsonObj(Object ... kv) { - for (int i = 0; i < kv.length; i += 2) { - put((String) kv[i], kv[i + 1]); - } - } - } - - private static final class JsonList extends ArrayList { - private JsonList(Object ... values) { - addAll(Arrays.asList(values)); - } - } - - @Test - public void withWaitForReady() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().isWaitForReady()).isTrue(); - } - - @Test - public void handleNullConfig() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - interceptor.handleUpdate(createManagedChannelServiceConfig(serviceConfig)); - interceptor.handleUpdate(null); - - interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse(); - } - - @Test - public void handleUpdateNotCalledBeforeInterceptCall() { - interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse(); - assertThat(callOptionsCap.getValue().getOption(RETRY_POLICY_KEY).get()) - .isEqualTo(RetryPolicy.DEFAULT); - assertThat(callOptionsCap.getValue().getOption(HEDGING_POLICY_KEY).get()) - .isEqualTo(HedgingPolicy.DEFAULT); - } - - @Test - public void withMaxRequestSize() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 1d); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(1); - } - - @Test - public void withMaxRequestSize_pickSmallerExisting() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 10d); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall( - methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(5), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(5); - } - - @Test - public void withMaxRequestSize_pickSmallerNew() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 5d); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall( - methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(10), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(5); - } - - @Test - public void withMaxResponseSize() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 1d); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(1); - } - - @Test - public void withMaxResponseSize_pickSmallerExisting() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 5d); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall( - methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(10), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(5); - } - - @Test - public void withMaxResponseSize_pickSmallerNew() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 10d); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall( - methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(5), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(5); - } - - @Test - public void withoutWaitForReady() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", false); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withWaitForReady(), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse(); - } - - @Test - public void fullMethodMatched() { - // Put in service that matches, but has no deadline. It should be lower priority - JsonObj name1 = new JsonObj("service", "service"); - JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1)); - - JsonObj name2 = new JsonObj("service", "service", "method", "method"); - JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2), "timeout", "1s"); - - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getDeadline()).isNotNull(); - } - - @Test - public void nearerDeadlineKept_existing() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "100000s"); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - Deadline existingDeadline = Deadline.after(1000, TimeUnit.NANOSECONDS); - interceptor.interceptCall( - methodDescriptor, CallOptions.DEFAULT.withDeadline(existingDeadline), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getDeadline()).isEqualTo(existingDeadline); - } - - @Test - public void nearerDeadlineKept_new() { - // TODO(carl-mastrangelo): the deadlines are very large because they change over time. - // This should be fixed, and is tracked in https://github.com/grpc/grpc-java/issues/2531 - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "1s"); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - Deadline existingDeadline = Deadline.after(1234567890, TimeUnit.NANOSECONDS); - interceptor.interceptCall( - methodDescriptor, CallOptions.DEFAULT.withDeadline(existingDeadline), channel); - - verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); - assertThat(callOptionsCap.getValue().getDeadline()).isNotEqualTo(existingDeadline); - } - - @Test - public void handleUpdate_failsOnMissingServiceName() { - JsonObj name = new JsonObj("method", "method"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("missing service"); - - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - } - - @Test - public void handleUpdate_failsOnDuplicateMethod() { - JsonObj name1 = new JsonObj("service", "service", "method", "method"); - JsonObj name2 = new JsonObj("service", "service", "method", "method"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Duplicate method"); - - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - } - - @Test - public void handleUpdate_failsOnEmptyName() { - JsonObj methodConfig = new JsonObj(); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("no names in method config"); - - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - } - - @Test - public void handleUpdate_failsOnDuplicateService() { - JsonObj name1 = new JsonObj("service", "service"); - JsonObj name2 = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Duplicate service"); - - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - } - - @Test - public void handleUpdate_failsOnDuplicateServiceMultipleConfig() { - JsonObj name1 = new JsonObj("service", "service"); - JsonObj name2 = new JsonObj("service", "service"); - JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1)); - JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Duplicate service"); - - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - } - - @Test - public void handleUpdate_replaceExistingConfig() { - JsonObj name1 = new JsonObj("service", "service"); - JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1)); - JsonObj serviceConfig1 = new JsonObj("methodConfig", new JsonList(methodConfig1)); - - JsonObj name2 = new JsonObj("service", "service", "method", "method"); - JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2)); - JsonObj serviceConfig2 = new JsonObj("methodConfig", new JsonList(methodConfig2)); - ManagedChannelServiceConfig2 parsedServiceConfig1 = - createManagedChannelServiceConfig(serviceConfig1); - ManagedChannelServiceConfig2 parsedServiceConfig2 = - createManagedChannelServiceConfig(serviceConfig2); - - interceptor.handleUpdate(parsedServiceConfig1); - - assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isNotEmpty(); - assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); - - interceptor.handleUpdate(parsedServiceConfig2); - - assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); - assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isNotEmpty(); - } - - @Test - public void handleUpdate_matchNames() { - JsonObj name1 = new JsonObj("service", "service2"); - JsonObj name2 = new JsonObj("service", "service", "method", "method"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - ManagedChannelServiceConfig2 parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); - - assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()) - .containsExactly( - methodDescriptor.getFullMethodName(), - new MethodInfo(methodConfig, false, 1, 1)); - assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).containsExactly( - "service2", new MethodInfo(methodConfig, false, 1, 1)); - } - - @Test - public void methodInfo_validateDeadline() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "10000000000000000s"); - - thrown.expectMessage("Duration value is out of range"); - - new MethodInfo(methodConfig, false, 1, 1); - } - - @Test - public void methodInfo_saturateDeadline() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "315576000000s"); - - MethodInfo info = new MethodInfo(methodConfig, false, 1, 1); - - assertThat(info.timeoutNanos).isEqualTo(Long.MAX_VALUE); - } - - @Test - public void methodInfo_badMaxRequestSize() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", -1d); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("exceeds bounds"); - - new MethodInfo(methodConfig, false, 1, 1); - } - - @Test - public void methodInfo_badMaxResponseSize() { - JsonObj name = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", -1d); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("exceeds bounds"); - - new MethodInfo(methodConfig, false, 1, 1); - } - - private static ManagedChannelServiceConfig2 createManagedChannelServiceConfig( - JsonObj rawServiceConfig) { - // current tests doesn't use any other values except rawServiceConfig, so provide dummy values. - return ManagedChannelServiceConfig2.fromServiceConfig( - rawServiceConfig, - /* retryEnabled= */ true, - /* maxRetryAttemptsLimit= */ 3, - /* maxHedgedAttemptsLimit= */ 4, - /* loadBalancingConfig= */ null); - } - - private static final class NoopMarshaller implements MethodDescriptor.Marshaller { - - @Override - public InputStream stream(Void value) { - return null; - } - - @Override - public Void parse(InputStream stream) { - return null; - } - } -}