core: promote ServiceConfigErrorHandling (#6633)

This commit is contained in:
Jihun Cho 2020-01-23 17:59:27 -08:00 committed by GitHub
parent 5b7f5b8c3b
commit 751faa6faa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 899 additions and 9838 deletions

View File

@ -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_RETRY_BUFFER_SIZE_IN_BYTES = 1L << 24; // 16M
private static final long DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES = 1L << 20; // 1M 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<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL; ObjectPool<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL;
ObjectPool<? extends Executor> offloadExecutorPool = DEFAULT_EXECUTOR_POOL; ObjectPool<? extends Executor> offloadExecutorPool = DEFAULT_EXECUTOR_POOL;
@ -165,8 +158,6 @@ public abstract class AbstractManagedChannelImplBuilder
@Nullable @Nullable
ProxyDetector proxyDetector; 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 * 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 * 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(); 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. * Disable or enable stats features. Enabled by default.
* *
@ -527,17 +508,6 @@ public abstract class AbstractManagedChannelImplBuilder
@Override @Override
public ManagedChannel build() { 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( return new ManagedChannelOrphanWrapper(new ManagedChannelImpl(
this, this,
buildTransportFactory(), buildTransportFactory(),

View File

@ -20,7 +20,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.LoadBalancer.ATTR_LOAD_BALANCING_CONFIG; import static io.grpc.LoadBalancer.ATTR_LOAD_BALANCING_CONFIG;
import com.google.common.annotations.VisibleForTesting; 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.Attributes;
import io.grpc.ChannelLogger;
import io.grpc.ChannelLogger.ChannelLogLevel; import io.grpc.ChannelLogger.ChannelLogLevel;
import io.grpc.ConnectivityState; import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo; import io.grpc.ConnectivityStateInfo;
@ -39,13 +42,13 @@ import io.grpc.Status;
import io.grpc.internal.ServiceConfigUtil.LbConfig; import io.grpc.internal.ServiceConfigUtil.LbConfig;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; 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 { public final class AutoConfiguredLoadBalancerFactory {
private static final Logger logger = private static final Logger logger =
Logger.getLogger(AutoConfiguredLoadBalancerFactory.class.getName()); 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 * Returns non-OK status if resolvedAddresses is empty and delegate lb requires address ({@link
* name-resolution error. * LoadBalancer#canHandleEmptyAddressListFromNameResolution()} returns {@code false}). {@code
* AutoConfiguredLoadBalancer} doesn't expose {@code
* canHandleEmptyAddressListFromNameResolution} because it depends on the delegated LB.
*/ */
Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) { Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
List<EquivalentAddressGroup> servers = resolvedAddresses.getAddresses(); List<EquivalentAddressGroup> servers = resolvedAddresses.getAddresses();
@ -118,10 +123,12 @@ public final class AutoConfiguredLoadBalancerFactory {
"Unexpected ATTR_LOAD_BALANCING_CONFIG from upstream: " "Unexpected ATTR_LOAD_BALANCING_CONFIG from upstream: "
+ attributes.get(ATTR_LOAD_BALANCING_CONFIG)); + attributes.get(ATTR_LOAD_BALANCING_CONFIG));
} }
Map<String, ?> configMap = attributes.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG); PolicySelection policySelection =
PolicySelection selection; (PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig();
ResolvedPolicySelection resolvedSelection;
try { try {
selection = decideLoadBalancerProvider(servers, configMap); resolvedSelection = resolveLoadBalancerProvider(servers, policySelection);
} catch (PolicyException e) { } catch (PolicyException e) {
Status s = Status.INTERNAL.withDescription(e.getMessage()); Status s = Status.INTERNAL.withDescription(e.getMessage());
helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s)); helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s));
@ -130,6 +137,7 @@ public final class AutoConfiguredLoadBalancerFactory {
delegate = new NoopLoadBalancer(); delegate = new NoopLoadBalancer();
return Status.OK; return Status.OK;
} }
PolicySelection selection = resolvedSelection.policySelection;
if (delegateProvider == null if (delegateProvider == null
|| !selection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) { || !selection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) {
@ -142,24 +150,25 @@ public final class AutoConfiguredLoadBalancerFactory {
ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}", ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}",
old.getClass().getSimpleName(), delegate.getClass().getSimpleName()); old.getClass().getSimpleName(), delegate.getClass().getSimpleName());
} }
Object lbConfig = selection.config;
if (selection.config != null) { if (lbConfig != null) {
helper.getChannelLogger().log( helper.getChannelLogger().log(
ChannelLogLevel.DEBUG, "Load-balancing config: {0}", selection.config); ChannelLogLevel.DEBUG, "Load-balancing config: {0}", selection.config);
attributes = attributes =
attributes.toBuilder().set(ATTR_LOAD_BALANCING_CONFIG, selection.config).build(); attributes.toBuilder().set(ATTR_LOAD_BALANCING_CONFIG, selection.rawConfig).build();
} }
LoadBalancer delegate = getDelegate(); LoadBalancer delegate = getDelegate();
if (selection.serverList.isEmpty() if (resolvedSelection.serverList.isEmpty()
&& !delegate.canHandleEmptyAddressListFromNameResolution()) { && !delegate.canHandleEmptyAddressListFromNameResolution()) {
return Status.UNAVAILABLE.withDescription( return Status.UNAVAILABLE.withDescription(
"NameResolver returned no usable address. addrs=" + servers + ", attrs=" + attributes); "NameResolver returned no usable address. addrs=" + servers + ", attrs=" + attributes);
} else { } else {
delegate.handleResolvedAddresses( delegate.handleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(selection.serverList) .setAddresses(resolvedSelection.serverList)
.setAttributes(attributes) .setAttributes(attributes)
.setLoadBalancingPolicyConfig(lbConfig)
.build()); .build());
return Status.OK; return Status.OK;
} }
@ -199,24 +208,17 @@ public final class AutoConfiguredLoadBalancerFactory {
} }
/** /**
* Picks a load balancer based on given criteria. In order of preference: * 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
* <ol> * addresses are not present, it will fall back to {@link #defaultPolicy}.
* <li>User provided lb on the channel. This is a degenerate case and not handled here.
* This options is deprecated.</li>
* <li>Policy from "loadBalancingConfig" if present. This is not covered here.</li>
* <li>"grpclb" if any gRPC LB balancer addresses are present</li>
* <li>The policy from "loadBalancingPolicy" if present</li>
* <li>"pick_first" if the service config choice does not specify</li>
* </ol>
* *
* @param servers The list of servers reported * @param servers The list of servers reported
* @param config the service config object * @param policySelection the selected policy from raw service config
* @return the new load balancer factory, never null * @return the resolved policy selection
*/ */
@VisibleForTesting @VisibleForTesting
PolicySelection decideLoadBalancerProvider( ResolvedPolicySelection resolveLoadBalancerProvider(
List<EquivalentAddressGroup> servers, @Nullable Map<String, ?> config) List<EquivalentAddressGroup> servers, @Nullable PolicySelection policySelection)
throws PolicyException { throws PolicyException {
// Check for balancer addresses // Check for balancer addresses
boolean haveBalancerAddress = false; boolean haveBalancerAddress = false;
@ -229,36 +231,10 @@ public final class AutoConfiguredLoadBalancerFactory {
} }
} }
List<LbConfig> lbConfigs = null; if (policySelection != null) {
if (config != null) { String policyName = policySelection.provider.getPolicyName();
List<Map<String, ?>> rawLbConfigs = return new ResolvedPolicySelection(
ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(config); policySelection, policyName.equals(GRPCLB_POLICY_NAME) ? servers : backendAddrs);
lbConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(rawLbConfigs);
}
if (lbConfigs != null && !lbConfigs.isEmpty()) {
LinkedHashSet<String> 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 (haveBalancerAddress) { if (haveBalancerAddress) {
@ -278,20 +254,29 @@ public final class AutoConfiguredLoadBalancerFactory {
helper.getChannelLogger().log(ChannelLogLevel.ERROR, errorMsg); helper.getChannelLogger().log(ChannelLogLevel.ERROR, errorMsg);
logger.warning(errorMsg); logger.warning(errorMsg);
} }
return new PolicySelection( return new ResolvedPolicySelection(
new PolicySelection(
getProviderOrThrow( getProviderOrThrow(
"round_robin", "received balancer addresses but grpclb runtime is missing"), "round_robin", "received balancer addresses but grpclb runtime is missing"),
backendAddrs, null); /* 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 // No balancer address this time. If balancer address shows up later, we want to make sure
// the warning is logged one more time. // the warning is logged one more time.
roundRobinDueToGrpclbDepMissing = false; roundRobinDueToGrpclbDepMissing = false;
// No config nor balancer address. Use default. // No config nor balancer address. Use default.
return new PolicySelection( return new ResolvedPolicySelection(
getProviderOrThrow(defaultPolicy, "using default policy"), servers, null); 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.
*
* <p>Order of policy preference:
*
* <ol>
* <li>Policy from "loadBalancingConfig" if present</li>
* <li>The policy from deprecated "loadBalancingPolicy" if present</li>
* </ol>
* </p>
*
* <p>Unlike a normal {@link LoadBalancer.Factory}, this accepts a full service config rather than
* the LoadBalancingConfig. * 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 @Nullable
ConfigOrError selectLoadBalancerPolicy(Map<String, ?> serviceConfig) { ConfigOrError parseLoadBalancerPolicy(Map<String, ?> serviceConfig, ChannelLogger channelLogger) {
try { try {
List<LbConfig> loadBalancerConfigs = null; List<LbConfig> loadBalancerConfigs = null;
if (serviceConfig != null) { if (serviceConfig != null) {
@ -328,10 +327,18 @@ public final class AutoConfiguredLoadBalancerFactory {
if (provider == null) { if (provider == null) {
policiesTried.add(policy); policiesTried.add(policy);
} else { } else {
return ConfigOrError.fromConfig(new PolicySelection( if (!policiesTried.isEmpty()) {
provider, channelLogger.log(
/* serverList= */ null, ChannelLogLevel.DEBUG,
lbConfig.getRawConfigValue())); "{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( return ConfigOrError.fromError(
@ -357,25 +364,65 @@ public final class AutoConfiguredLoadBalancerFactory {
@VisibleForTesting @VisibleForTesting
static final class PolicySelection { static final class PolicySelection {
final LoadBalancerProvider provider; final LoadBalancerProvider provider;
@Nullable final List<EquivalentAddressGroup> serverList; @Nullable final Map<String, ?> rawConfig;
// TODO(carl-mastrangelo): make this the non-raw service config object. @Nullable final Object config;
@Nullable final Map<String, ?> config;
PolicySelection(
LoadBalancerProvider provider, List<EquivalentAddressGroup> serverList,
@Nullable Map<String, ?> config) {
this.provider = checkNotNull(provider, "provider");
this.serverList = Collections.unmodifiableList(checkNotNull(serverList, "serverList"));
this.config = config;
}
PolicySelection( PolicySelection(
LoadBalancerProvider provider, LoadBalancerProvider provider,
@Nullable Map<String, ?> config) { @Nullable Map<String, ?> rawConfig,
@Nullable Object config) {
this.provider = checkNotNull(provider, "provider"); this.provider = checkNotNull(provider, "provider");
this.serverList = null; this.rawConfig = rawConfig;
this.config = config; 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<EquivalentAddressGroup> serverList;
ResolvedPolicySelection(
PolicySelection policySelection, List<EquivalentAddressGroup> 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 { private static final class EmptyPicker extends SubchannelPicker {

View File

@ -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<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<EquivalentAddressGroup> servers, @Nullable PolicySelection policySelection)
throws PolicyException {
// Check for balancer addresses
boolean haveBalancerAddress = false;
List<EquivalentAddressGroup> 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.
*
* <p>Order of policy preference:
*
* <ol>
* <li>Policy from "loadBalancingConfig" if present</li>
* <li>The policy from deprecated "loadBalancingPolicy" if present</li>
* </ol>
* </p>
*
* <p>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<String, ?> serviceConfig, ChannelLogger channelLogger) {
try {
List<LbConfig> loadBalancerConfigs = null;
if (serviceConfig != null) {
List<Map<String, ?>> rawLbConfigs =
ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(serviceConfig);
loadBalancerConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(rawLbConfigs);
}
if (loadBalancerConfigs != null && !loadBalancerConfigs.isEmpty()) {
List<String> 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<String, ?> rawConfig;
@Nullable final Object config;
PolicySelection(
LoadBalancerProvider provider,
@Nullable Map<String, ?> 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<EquivalentAddressGroup> serverList;
ResolvedPolicySelection(
PolicySelection policySelection, List<EquivalentAddressGroup> 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);
}
}
}

View File

@ -27,6 +27,7 @@ import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.util.concurrent.ListenableFuture; 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.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@ -130,6 +130,11 @@ final class ManagedChannelImpl extends ManagedChannel implements
static final Status SUBCHANNEL_SHUTDOWN_STATUS = static final Status SUBCHANNEL_SHUTDOWN_STATUS =
Status.UNAVAILABLE.withDescription("Subchannel shutdown invoked"); Status.UNAVAILABLE.withDescription("Subchannel shutdown invoked");
private static final ServiceConfigHolder EMPTY_SERVICE_CONFIG =
new ServiceConfigHolder(
Collections.<String, Object>emptyMap(),
ManagedChannelServiceConfig.empty());
private final InternalLogId logId; private final InternalLogId logId;
private final String target; private final String target;
private final NameResolverRegistry nameResolverRegistry; private final NameResolverRegistry nameResolverRegistry;
@ -243,28 +248,22 @@ final class ManagedChannelImpl extends ManagedChannel implements
private final ChannelTracer channelTracer; private final ChannelTracer channelTracer;
private final ChannelLogger channelLogger; private final ChannelLogger channelLogger;
private final InternalChannelz channelz; private final InternalChannelz channelz;
// Must be mutated and read from syncContext // Must be mutated and read from syncContext
@CheckForNull // a flag for doing channel tracing when flipped
private Boolean haveBackends; // a flag for doing channel tracing when flipped private ResolutionState lastResolutionState = ResolutionState.NO_RESOLUTION;
// Must be mutated and read from constructor or syncContext // 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 @Nullable
private Map<String, ?> lastServiceConfig; // used for channel tracing when value changed private final ServiceConfigHolder defaultServiceConfig;
@Nullable
private final Map<String, ?> defaultServiceConfig;
// Must be mutated and read from constructor or syncContext // Must be mutated and read from constructor or syncContext
// See service config error handling spec for reference. private boolean serviceConfigUpdated = false;
// TODO(notcarl): check this value when error in service config resolution
@SuppressWarnings("UnusedVariable")
private boolean waitingForServiceConfig = true;
private final boolean lookUpServiceConfig; private final boolean lookUpServiceConfig;
// One instance per channel. // One instance per channel.
private final ChannelBufferMeter channelBufferUsed = new ChannelBufferMeter(); private final ChannelBufferMeter channelBufferUsed = new ChannelBufferMeter();
@Nullable
private Throttle throttle;
private final long perRpcBufferLimit; private final long perRpcBufferLimit;
private final long channelBufferLimit; private final long channelBufferLimit;
@ -504,6 +503,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
final Metadata headers, final Metadata headers,
final Context context) { final Context context) {
checkState(retryEnabled, "retry should be enabled"); checkState(retryEnabled, "retry should be enabled");
final Throttle throttle = lastServiceConfig.managedChannelServiceConfig.getRetryThrottling();
final class RetryStream extends RetriableStream<ReqT> { final class RetryStream extends RetriableStream<ReqT> {
RetryStream() { RetryStream() {
super( super(
@ -582,18 +582,20 @@ final class ManagedChannelImpl extends ManagedChannel implements
new ExecutorHolder( new ExecutorHolder(
checkNotNull(builder.offloadExecutorPool, "offloadExecutorPool")); checkNotNull(builder.offloadExecutorPool, "offloadExecutorPool"));
this.nameResolverRegistry = builder.nameResolverRegistry; this.nameResolverRegistry = builder.nameResolverRegistry;
ScParser serviceConfigParser =
new ScParser(
retryEnabled,
builder.maxRetryAttempts,
builder.maxHedgedAttempts,
loadBalancerFactory,
channelLogger);
this.nameResolverArgs = this.nameResolverArgs =
NameResolver.Args.newBuilder() NameResolver.Args.newBuilder()
.setDefaultPort(builder.getDefaultPort()) .setDefaultPort(builder.getDefaultPort())
.setProxyDetector(proxyDetector) .setProxyDetector(proxyDetector)
.setSynchronizationContext(syncContext) .setSynchronizationContext(syncContext)
.setScheduledExecutorService(scheduledExecutor) .setScheduledExecutorService(scheduledExecutor)
.setServiceConfigParser( .setServiceConfigParser(serviceConfigParser)
new ScParser(
retryEnabled,
builder.maxRetryAttempts,
builder.maxHedgedAttempts,
loadBalancerFactory))
.setChannelLogger(channelLogger) .setChannelLogger(channelLogger)
.setOffloadExecutor( .setOffloadExecutor(
// Avoid creating the offloadExecutor until it is first used // 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 = new DelayedClientTransport(this.executor, this.syncContext);
this.delayedTransport.start(delayedTransportListener); this.delayedTransport.start(delayedTransportListener);
this.backoffPolicyProvider = backoffPolicyProvider; this.backoffPolicyProvider = backoffPolicyProvider;
serviceConfigInterceptor = new ServiceConfigInterceptor(
retryEnabled, builder.maxRetryAttempts, builder.maxHedgedAttempts); serviceConfigInterceptor = new ServiceConfigInterceptor(retryEnabled);
this.defaultServiceConfig = builder.defaultServiceConfig; if (builder.defaultServiceConfig != null) {
this.lastServiceConfig = defaultServiceConfig; 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; this.lookUpServiceConfig = builder.lookUpServiceConfig;
Channel channel = new RealChannel(nameResolver.getServiceAuthority()); Channel channel = new RealChannel(nameResolver.getServiceAuthority());
channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor); channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor);
@ -667,11 +682,8 @@ final class ManagedChannelImpl extends ManagedChannel implements
// May only be called in constructor or syncContext // May only be called in constructor or syncContext
private void handleServiceConfigUpdate() { private void handleServiceConfigUpdate() {
waitingForServiceConfig = false; serviceConfigUpdated = true;
serviceConfigInterceptor.handleUpdate(lastServiceConfig); serviceConfigInterceptor.handleUpdate(lastServiceConfig.managedChannelServiceConfig);
if (retryEnabled) {
throttle = ServiceConfigUtil.getThrottlePolicy(lastServiceConfig);
}
} }
@VisibleForTesting @VisibleForTesting
@ -1309,48 +1321,73 @@ final class ManagedChannelImpl extends ManagedChannel implements
Attributes attrs = resolutionResult.getAttributes(); Attributes attrs = resolutionResult.getAttributes();
channelLogger.log( channelLogger.log(
ChannelLogLevel.DEBUG, "Resolved address: {0}, config={1}", servers, attrs); 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); channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers);
haveBackends = true; lastResolutionState = ResolutionState.SUCCESS;
} }
nameResolverBackoffPolicy = null; nameResolverBackoffPolicy = null;
ConfigOrError configOrError = resolutionResult.getServiceConfig();
ServiceConfigHolder validServiceConfig = null;
Status serviceConfigError = null;
if (configOrError != null) {
Map<String, ?> 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. ServiceConfigHolder effectiveServiceConfig;
final Map<String, ?> serviceConfig =
attrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
Map<String, ?> effectiveServiceConfig;
if (!lookUpServiceConfig) { if (!lookUpServiceConfig) {
if (serviceConfig != null) { if (validServiceConfig != null) {
channelLogger.log( channelLogger.log(
ChannelLogLevel.INFO, ChannelLogLevel.INFO,
"Service config from name resolver discarded by channel settings"); "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 { } else {
// Try to use config if returned from name resolver // Try to use config if returned from name resolver
// Otherwise, try to use the default config if available // Otherwise, try to use the default config if available
if (serviceConfig != null) { if (validServiceConfig != null) {
effectiveServiceConfig = serviceConfig; effectiveServiceConfig = validServiceConfig;
} else { } else if (defaultServiceConfig != null) {
effectiveServiceConfig = defaultServiceConfig; effectiveServiceConfig = defaultServiceConfig;
if (defaultServiceConfig != null) {
channelLogger.log( channelLogger.log(
ChannelLogLevel.INFO, ChannelLogLevel.INFO,
"Received no service config, using default service config"); "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)) {
// FIXME(notcarl): reference equality is not right (although not harmful) right now. channelLogger.log(
// Name resolver should return the same config if txt record is the same ChannelLogLevel.INFO,
if (effectiveServiceConfig != lastServiceConfig) { "Service config changed{0}",
channelLogger.log(ChannelLogLevel.INFO, effectiveServiceConfig == EMPTY_SERVICE_CONFIG ? " to empty" : "");
"Service config changed{0}", effectiveServiceConfig == null ? " to null" : "");
lastServiceConfig = effectiveServiceConfig; lastServiceConfig = effectiveServiceConfig;
} }
try { try {
// TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS
// and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But,
// lbNeedAddress is not deterministic
handleServiceConfigUpdate(); handleServiceConfigUpdate();
} catch (RuntimeException re) { } catch (RuntimeException re) {
logger.log( logger.log(
@ -1363,22 +1400,35 @@ final class ManagedChannelImpl extends ManagedChannel implements
// Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) { if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) {
Attributes effectiveAttrs = attrs; Attributes effectiveAttrs = attrs;
if (effectiveServiceConfig != serviceConfig) { if (effectiveServiceConfig != validServiceConfig) {
effectiveAttrs = attrs.toBuilder() effectiveAttrs = attrs.toBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, effectiveServiceConfig) .set(
GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG,
effectiveServiceConfig.rawServiceConfig)
.build(); .build();
} }
Status handleResult = helper.lb.tryHandleResolvedAddresses( Status handleResult = helper.lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(servers) .setAddresses(servers)
.setAttributes(effectiveAttrs) .setAttributes(effectiveAttrs)
.setLoadBalancingPolicyConfig(
effectiveServiceConfig.managedChannelServiceConfig.getLoadBalancingConfig())
.build()); .build());
if (!handleResult.isOk()) { 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")); handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used"));
} }
} }
} }
} }
}
syncContext.execute(new NamesResolved()); syncContext.execute(new NamesResolved());
} }
@ -1399,15 +1449,21 @@ final class ManagedChannelImpl extends ManagedChannel implements
private void handleErrorInSyncContext(Status error) { private void handleErrorInSyncContext(Status error) {
logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}", logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}",
new Object[] {getLogId(), error}); new Object[] {getLogId(), error});
if (haveBackends == null || haveBackends) { if (lastResolutionState != ResolutionState.ERROR) {
channelLogger.log(ChannelLogLevel.WARNING, "Failed to resolve name: {0}", 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. // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
if (NameResolverListener.this.helper != ManagedChannelImpl.this.lbHelper) { if (NameResolverListener.this.helper != ManagedChannelImpl.this.lbHelper) {
return; return;
} }
helper.lb.handleNameResolutionError(error); helper.lb.handleNameResolutionError(error);
scheduleExponentialBackOffInSyncContext();
}
private void scheduleExponentialBackOffInSyncContext() {
if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh.isPending()) { if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh.isPending()) {
// The name resolver may invoke onError multiple times, but we only want to // The name resolver may invoke onError multiple times, but we only want to
// schedule one backoff attempt // schedule one backoff attempt
@ -1845,17 +1901,20 @@ final class ManagedChannelImpl extends ManagedChannel implements
private final int maxRetryAttemptsLimit; private final int maxRetryAttemptsLimit;
private final int maxHedgedAttemptsLimit; private final int maxHedgedAttemptsLimit;
private final AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory; private final AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory;
private final ChannelLogger channelLogger;
ScParser( ScParser(
boolean retryEnabled, boolean retryEnabled,
int maxRetryAttemptsLimit, int maxRetryAttemptsLimit,
int maxHedgedAttemptsLimit, int maxHedgedAttemptsLimit,
AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory) { AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory,
ChannelLogger channelLogger) {
this.retryEnabled = retryEnabled; this.retryEnabled = retryEnabled;
this.maxRetryAttemptsLimit = maxRetryAttemptsLimit; this.maxRetryAttemptsLimit = maxRetryAttemptsLimit;
this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit; this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit;
this.autoLoadBalancerFactory = this.autoLoadBalancerFactory =
checkNotNull(autoLoadBalancerFactory, "autoLoadBalancerFactory"); checkNotNull(autoLoadBalancerFactory, "autoLoadBalancerFactory");
this.channelLogger = checkNotNull(channelLogger, "channelLogger");
} }
@Override @Override
@ -1863,7 +1922,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
try { try {
Object loadBalancingPolicySelection; Object loadBalancingPolicySelection;
ConfigOrError choiceFromLoadBalancer = ConfigOrError choiceFromLoadBalancer =
autoLoadBalancerFactory.selectLoadBalancerPolicy(rawServiceConfig); autoLoadBalancerFactory.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
if (choiceFromLoadBalancer == null) { if (choiceFromLoadBalancer == null) {
loadBalancingPolicySelection = null; loadBalancingPolicySelection = null;
} else if (choiceFromLoadBalancer.getError() != 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); + "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<String, ?> rawServiceConfig;
ManagedChannelServiceConfig managedChannelServiceConfig;
ServiceConfigHolder(
Map<String, ?> 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();
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -39,9 +39,7 @@ final class ManagedChannelServiceConfig {
private final Map<String, MethodInfo> serviceMethodMap; private final Map<String, MethodInfo> serviceMethodMap;
private final Map<String, MethodInfo> serviceMap; private final Map<String, MethodInfo> serviceMap;
// TODO(notcarl/zdapeng): use retryThrottling here
@Nullable @Nullable
@SuppressWarnings("unused")
private final Throttle retryThrottling; private final Throttle retryThrottling;
@Nullable @Nullable
private final Object loadBalancingConfig; private final Object loadBalancingConfig;
@ -57,6 +55,16 @@ final class ManagedChannelServiceConfig {
this.loadBalancingConfig = loadBalancingConfig; this.loadBalancingConfig = loadBalancingConfig;
} }
/** Returns an empty {@link ManagedChannelServiceConfig}. */
static ManagedChannelServiceConfig empty() {
return
new ManagedChannelServiceConfig(
new HashMap<String, MethodInfo>(),
new HashMap<String, MethodInfo>(),
/* retryThrottling= */ null,
/* loadBalancingConfig= */ null);
}
/** /**
* Parses the Channel level config values (e.g. excludes load balancing) * Parses the Channel level config values (e.g. excludes load balancing)
*/ */
@ -138,6 +146,41 @@ final class ManagedChannelServiceConfig {
return loadBalancingConfig; 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. * Equivalent of MethodConfig from a ServiceConfig with restrictions from Channel setting.
*/ */

View File

@ -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<String, MethodInfo> serviceMethodMap;
private final Map<String, MethodInfo> serviceMap;
@Nullable
private final Throttle retryThrottling;
@Nullable
private final Object loadBalancingConfig;
ManagedChannelServiceConfig2(
Map<String, MethodInfo> serviceMethodMap,
Map<String, MethodInfo> 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<String, MethodInfo>(),
new HashMap<String, MethodInfo>(),
/* retryThrottling= */ null,
/* loadBalancingConfig= */ null);
}
/**
* Parses the Channel level config values (e.g. excludes load balancing)
*/
static ManagedChannelServiceConfig2 fromServiceConfig(
Map<String, ?> serviceConfig,
boolean retryEnabled,
int maxRetryAttemptsLimit,
int maxHedgedAttemptsLimit,
@Nullable Object loadBalancingConfig) {
Throttle retryThrottling = null;
if (retryEnabled) {
retryThrottling = ServiceConfigUtil.getThrottlePolicy(serviceConfig);
}
Map<String, MethodInfo> serviceMethodMap = new HashMap<>();
Map<String, MethodInfo> 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<Map<String, ?>> methodConfigs =
ServiceConfigUtil.getMethodConfigFromServiceConfig(serviceConfig);
if (methodConfigs == null) {
// this is surprising, but possible.
return new ManagedChannelServiceConfig2(
serviceMethodMap, serviceMap, retryThrottling, loadBalancingConfig);
}
for (Map<String, ?> methodConfig : methodConfigs) {
MethodInfo info = new MethodInfo(
methodConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit);
List<Map<String, ?>> nameList =
ServiceConfigUtil.getNameListFromMethodConfig(methodConfig);
checkArgument(
nameList != null && !nameList.isEmpty(), "no names in method config %s", methodConfig);
for (Map<String, ?> 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<String, MethodInfo> getServiceMap() {
return serviceMap;
}
/**
* Returns the per-method configuration for the channel.
*/
Map<String, MethodInfo> 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<String, ?> 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<String, ?> retryPolicyMap =
retryEnabled ? ServiceConfigUtil.getRetryPolicyFromMethodConfig(methodConfig) : null;
retryPolicy = retryPolicyMap == null
? RetryPolicy.DEFAULT : retryPolicy(retryPolicyMap, maxRetryAttemptsLimit);
Map<String, ?> 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<String, ?> 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<String, ?> 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));
}
}
}

View File

@ -26,8 +26,6 @@ import io.grpc.ClientInterceptor;
import io.grpc.Deadline; import io.grpc.Deadline;
import io.grpc.MethodDescriptor; import io.grpc.MethodDescriptor;
import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo; import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
@ -40,34 +38,20 @@ final class ServiceConfigInterceptor implements ClientInterceptor {
// Map from method name to MethodInfo // Map from method name to MethodInfo
@VisibleForTesting @VisibleForTesting
final AtomicReference<ManagedChannelServiceConfig> managedChannelServiceConfig final AtomicReference<ManagedChannelServiceConfig> managedChannelServiceConfig =
= new AtomicReference<>(); new AtomicReference<>();
private final boolean retryEnabled; 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. // Setting this to true and observing this equal to true are run in different threads.
private volatile boolean initComplete; private volatile boolean initComplete;
ServiceConfigInterceptor( ServiceConfigInterceptor(boolean retryEnabled) {
boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit) {
this.retryEnabled = retryEnabled; this.retryEnabled = retryEnabled;
this.maxRetryAttemptsLimit = maxRetryAttemptsLimit;
this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit;
} }
void handleUpdate(@Nullable Map<String, ?> serviceConfig) { void handleUpdate(@Nullable ManagedChannelServiceConfig serviceConfig) {
// TODO(carl-mastrangelo): delete this. managedChannelServiceConfig.set(serviceConfig);
ManagedChannelServiceConfig conf;
if (serviceConfig == null) {
conf = new ManagedChannelServiceConfig(
new HashMap<String, MethodInfo>(), new HashMap<String, MethodInfo>(), null, null);
} else {
conf = ManagedChannelServiceConfig.fromServiceConfig(
serviceConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit, null);
}
managedChannelServiceConfig.set(conf);
initComplete = true; initComplete = true;
} }

View File

@ -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<ManagedChannelServiceConfig2> 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<RetryPolicy.Provider> RETRY_POLICY_KEY =
CallOptions.Key.create("internal-retry-policy");
static final CallOptions.Key<HedgingPolicy.Provider> HEDGING_POLICY_KEY =
CallOptions.Key.create("internal-hedging-policy");
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
final MethodDescriptor<ReqT, RespT> 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.
*
* <p>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.
*
* <p>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;
}
}

View File

@ -24,7 +24,6 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@ -481,19 +480,6 @@ public class AbstractManagedChannelImplBuilderTest {
assertThat(builder.lookUpServiceConfig).isFalse(); 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> { static class Builder extends AbstractManagedChannelImplBuilder<Builder> {
Builder(String target) { Builder(String target) {
super(target); super(target);

View File

@ -51,23 +51,25 @@ import io.grpc.LoadBalancer.SubchannelStateListener;
import io.grpc.LoadBalancerProvider; import io.grpc.LoadBalancerProvider;
import io.grpc.LoadBalancerRegistry; import io.grpc.LoadBalancerRegistry;
import io.grpc.ManagedChannel; import io.grpc.ManagedChannel;
import io.grpc.NameResolver.ConfigOrError;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext;
import io.grpc.grpclb.GrpclbLoadBalancerProvider; import io.grpc.grpclb.GrpclbLoadBalancerProvider;
import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer;
import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicyException; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicyException;
import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicySelection; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicySelection;
import io.grpc.internal.AutoConfiguredLoadBalancerFactory.ResolvedPolicySelection;
import io.grpc.util.ForwardingLoadBalancerHelper; import io.grpc.util.ForwardingLoadBalancerHelper;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -78,8 +80,8 @@ import org.mockito.ArgumentCaptor;
/** /**
* Unit tests for {@link AutoConfiguredLoadBalancerFactory}. * Unit tests for {@link AutoConfiguredLoadBalancerFactory}.
*/ */
@Deprecated // to be migrate to AutoConfiguredLoadBalancerFactoryTest2
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
// TODO(creamsoup) remove backward compatible check when fully migrated
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class AutoConfiguredLoadBalancerFactoryTest { public class AutoConfiguredLoadBalancerFactoryTest {
private static final LoadBalancerRegistry defaultRegistry = private static final LoadBalancerRegistry defaultRegistry =
@ -90,12 +92,18 @@ public class AutoConfiguredLoadBalancerFactoryTest {
private final ChannelLogger channelLogger = mock(ChannelLogger.class); private final ChannelLogger channelLogger = mock(ChannelLogger.class);
private final LoadBalancer testLbBalancer = mock(LoadBalancer.class); private final LoadBalancer testLbBalancer = mock(LoadBalancer.class);
private final LoadBalancer testLbBalancer2 = mock(LoadBalancer.class); private final LoadBalancer testLbBalancer2 = mock(LoadBalancer.class);
private final LoadBalancerProvider testLbBalancerProvider = private final AtomicReference<ConfigOrError> nextParsedConfigOrError =
mock(LoadBalancerProvider.class, new AtomicReference<>(ConfigOrError.fromConfig("default"));
delegatesTo(new FakeLoadBalancerProvider("test_lb", testLbBalancer))); private final AtomicReference<ConfigOrError> nextParsedConfigOrError2 =
private final LoadBalancerProvider testLbBalancerProvider2 = new AtomicReference<>(ConfigOrError.fromConfig("default2"));
mock(LoadBalancerProvider.class, private final FakeLoadBalancerProvider testLbBalancerProvider =
delegatesTo(new FakeLoadBalancerProvider("test_lb2", testLbBalancer2))); 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 @Before
public void setUp() { public void setUp() {
@ -190,6 +198,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(servers) .setAddresses(servers)
.setAttributes(Attributes.EMPTY) .setAttributes(Attributes.EMPTY)
.setLoadBalancingPolicyConfig(null)
.build()); .build());
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
@ -197,13 +206,11 @@ public class AutoConfiguredLoadBalancerFactoryTest {
} }
@Test @Test
public void handleResolvedAddressGroups_shutsDownOldBalancer() { public void handleResolvedAddressGroups_shutsDownOldBalancer() throws Exception {
Map<String, String> serviceConfig = new HashMap<>(); Map<String, ?> serviceConfig =
serviceConfig.put("loadBalancingPolicy", "round_robin"); parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": { } } ] }");
Attributes serviceConfigAttrs = ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger);
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
final List<EquivalentAddressGroup> servers = final List<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){}));
Helper helper = new TestHelper() { Helper helper = new TestHelper() {
@ -232,7 +239,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
Status handleResult = lb.tryHandleResolvedAddresses( Status handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(servers) .setAddresses(servers)
.setAttributes(serviceConfigAttrs) .setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.build()); .build());
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
@ -242,13 +249,13 @@ public class AutoConfiguredLoadBalancerFactoryTest {
} }
@Test @Test
@SuppressWarnings("unchecked")
public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exception { public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exception {
Map<String, ?> serviceConfig = Map<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }");
Attributes serviceConfigAttrs = ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
Attributes.newBuilder() assertThat(lbConfigs.getConfig()).isNotNull();
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
final List<EquivalentAddressGroup> servers = final List<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){}));
Helper helper = new TestHelper(); Helper helper = new TestHelper();
@ -257,7 +264,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
Status handleResult = lb.tryHandleResolvedAddresses( Status handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(servers) .setAddresses(servers)
.setAttributes(serviceConfigAttrs) .setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.build()); .build());
verify(testLbBalancerProvider).newLoadBalancer(same(helper)); verify(testLbBalancerProvider).newLoadBalancer(same(helper));
@ -267,22 +274,22 @@ public class AutoConfiguredLoadBalancerFactoryTest {
ArgumentCaptor.forClass(ResolvedAddresses.class); ArgumentCaptor.forClass(ResolvedAddresses.class);
verify(testLbBalancer).handleResolvedAddresses(resultCaptor.capture()); verify(testLbBalancer).handleResolvedAddresses(resultCaptor.capture());
assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder();
Attributes actualAttributes = resultCaptor.getValue().getAttributes(); assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG))
assertThat(actualAttributes.get(ATTR_LOAD_BALANCING_CONFIG)) .isEqualTo(rawServiceConfig);
.isEqualTo(Collections.singletonMap("setting1", "high"));
verify(testLbBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution(); verify(testLbBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution();
ArgumentCaptor<Map<String, ?>> lbConfigCaptor = ArgumentCaptor.forClass(Map.class);
verify(testLbBalancerProvider).parseLoadBalancingPolicyConfig(lbConfigCaptor.capture());
assertThat(lbConfigCaptor.getValue()).containsExactly("setting1", "high");
verifyNoMoreInteractions(testLbBalancer); verifyNoMoreInteractions(testLbBalancer);
serviceConfig = rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"low\" } } ] }"); parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"low\" } } ] }");
serviceConfigAttrs = lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
handleResult = lb.tryHandleResolvedAddresses( handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(servers) .setAddresses(servers)
.setAttributes(serviceConfigAttrs) .setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.build()); .build());
resultCaptor = resultCaptor =
@ -290,10 +297,11 @@ public class AutoConfiguredLoadBalancerFactoryTest {
verify(testLbBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); verify(testLbBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture());
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder();
actualAttributes = resultCaptor.getValue().getAttributes(); assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG))
// But the balancer config is changed. .isEqualTo(rawServiceConfig);
assertThat(actualAttributes.get(ATTR_LOAD_BALANCING_CONFIG)) verify(testLbBalancerProvider, times(2))
.isEqualTo(Collections.singletonMap("setting1", "low")); .parseLoadBalancingPolicyConfig(lbConfigCaptor.capture());
assertThat(lbConfigCaptor.getValue()).containsExactly("setting1", "low");
// Service config didn't change policy, thus the delegateLb is not swapped // Service config didn't change policy, thus the delegateLb is not swapped
verifyNoMoreInteractions(testLbBalancer); verifyNoMoreInteractions(testLbBalancer);
verify(testLbBalancerProvider).newLoadBalancer(any(Helper.class)); 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 // This case only happens when grpclb is missing. We will use a local registry
LoadBalancerRegistry registry = new LoadBalancerRegistry(); LoadBalancerRegistry registry = new LoadBalancerRegistry();
registry.register(new PickFirstLoadBalancerProvider()); registry.register(new PickFirstLoadBalancerProvider());
registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer)); registry.register(
new FakeLoadBalancerProvider(
"round_robin", testLbBalancer, /* nextParsedLbPolicyConfig= */ null));
final List<EquivalentAddressGroup> servers = final List<EquivalentAddressGroup> servers =
Arrays.asList( Arrays.asList(
@ -339,11 +349,11 @@ public class AutoConfiguredLoadBalancerFactoryTest {
Map<String, ?> serviceConfig = Map<String, ?> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }");
ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(serviceConfig, helper.getChannelLogger());
Status handleResult = lb.tryHandleResolvedAddresses( Status handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(Collections.<EquivalentAddressGroup>emptyList()) .setAddresses(Collections.<EquivalentAddressGroup>emptyList())
.setAttributes(Attributes.newBuilder() .setLoadBalancingPolicyConfig(lbConfig.getConfig())
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build())
.build()); .build());
assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse(); assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse();
@ -358,13 +368,14 @@ public class AutoConfiguredLoadBalancerFactoryTest {
Helper helper = new TestHelper(); Helper helper = new TestHelper();
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper);
Map<String, ?> serviceConfig = Map<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb2\": { \"setting1\": \"high\" } } ] }"); parseConfig("{\"loadBalancingConfig\": [ {\"test_lb2\": { \"setting1\": \"high\" } } ] }");
ConfigOrError lbConfigs =
lbf.parseLoadBalancerPolicy(rawServiceConfig, helper.getChannelLogger());
Status handleResult = lb.tryHandleResolvedAddresses( Status handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(Collections.<EquivalentAddressGroup>emptyList()) .setAddresses(Collections.<EquivalentAddressGroup>emptyList())
.setAttributes(Attributes.newBuilder() .setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build())
.build()); .build());
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
@ -374,26 +385,25 @@ public class AutoConfiguredLoadBalancerFactoryTest {
ArgumentCaptor.forClass(ResolvedAddresses.class); ArgumentCaptor.forClass(ResolvedAddresses.class);
verify(testLbBalancer2).handleResolvedAddresses(resultCaptor.capture()); verify(testLbBalancer2).handleResolvedAddresses(resultCaptor.capture());
assertThat(resultCaptor.getValue().getAddresses()).isEmpty(); assertThat(resultCaptor.getValue().getAddresses()).isEmpty();
Attributes actualAttributes = resultCaptor.getValue().getAttributes(); assertThat(resultCaptor.getValue().getLoadBalancingPolicyConfig())
.isEqualTo(nextParsedConfigOrError2.get().getConfig());
Map<String, ?> lbConfig = actualAttributes.get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG); assertThat(resultCaptor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG))
assertThat(lbConfig).isEqualTo(Collections.<String, Object>singletonMap("setting1", "high")); .isEqualTo(rawServiceConfig);
assertThat(actualAttributes.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isSameInstanceAs(serviceConfig);
} }
@Test @Test
public void decideLoadBalancerProvider_noBalancerAddresses_noServiceConfig_pickFirst() public void decideLoadBalancerProvider_noBalancerAddresses_noServiceConfig_pickFirst()
throws Exception { throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig = null; PolicySelection policySelection = null;
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); 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.serverList).isEqualTo(servers);
assertThat(selection.config).isNull(); assertThat(selection.policySelection.config).isNull();
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@ -402,39 +412,43 @@ public class AutoConfiguredLoadBalancerFactoryTest {
throws Exception { throws Exception {
AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory("test_lb") AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory("test_lb")
.newLoadBalancer(new TestHelper()); .newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig = null; PolicySelection policySelection = null;
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); 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.serverList).isEqualTo(servers);
assertThat(selection.config).isNull(); assertThat(selection.policySelection.config).isNull();
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@Test @Test
public void decideLoadBalancerProvider_oneBalancer_noServiceConfig_grpclb() throws Exception { public void decideLoadBalancerProvider_oneBalancer_noServiceConfig_grpclb() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig = null; PolicySelection policySelection = null;
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList( Collections.singletonList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
new SocketAddress(){}, new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); 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.serverList).isEqualTo(servers);
assertThat(selection.config).isNull(); assertThat(selection.policySelection.config).isNull();
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@Test @Test
public void decideLoadBalancerProvider_serviceConfigLbPolicy() throws Exception { public void decideLoadBalancerProvider_serviceConfigLbPolicy() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, String> serviceConfig = new HashMap<>(); Map<String, ?> rawServiceConfig =
serviceConfig.put("loadBalancingPolicy", "round_robin"); parseConfig("{\"loadBalancingPolicy\": \"round_robin\"}");
ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfig.getConfig()).isNotNull();
PolicySelection policySelection = (PolicySelection) lbConfig.getConfig();
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Arrays.asList( Arrays.asList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
@ -443,21 +457,23 @@ public class AutoConfiguredLoadBalancerFactoryTest {
new EquivalentAddressGroup( new EquivalentAddressGroup(
new SocketAddress(){})); new SocketAddress(){}));
List<EquivalentAddressGroup> backends = Arrays.asList(servers.get(1)); List<EquivalentAddressGroup> 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"); "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider");
assertThat(selection.serverList).isEqualTo(backends); assertThat(selection.serverList).isEqualTo(backends);
assertThat(selection.config).isEqualTo(Collections.<String, Object>emptyMap());
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@SuppressWarnings("unchecked")
@Test @Test
public void decideLoadBalancerProvider_serviceConfigLbConfig() throws Exception { public void decideLoadBalancerProvider_serviceConfigLbConfig() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig = Map<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {} } ] }"); parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}");
ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfig.getConfig()).isNotNull();
PolicySelection policySelection = (PolicySelection) lbConfig.getConfig();
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Arrays.asList( Arrays.asList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
@ -466,55 +482,54 @@ public class AutoConfiguredLoadBalancerFactoryTest {
new EquivalentAddressGroup( new EquivalentAddressGroup(
new SocketAddress(){})); new SocketAddress(){}));
List<EquivalentAddressGroup> backends = Arrays.asList(servers.get(1)); List<EquivalentAddressGroup> 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"); "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider");
assertThat(selection.serverList).isEqualTo(backends); assertThat(selection.serverList).isEqualTo(backends);
assertThat(selection.config).isEqualTo(Collections.<String, Object>emptyMap());
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@Test @Test
public void decideLoadBalancerProvider_grpclbConfigPropagated() throws Exception { public void decideLoadBalancerProvider_grpclbConfigPropagated() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig = Map<String, ?> rawServiceConfig =
parseConfig( parseConfig(
"{\"loadBalancingConfig\": [" "{\"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<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList( Collections.singletonList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
new SocketAddress(){}, new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); 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.serverList).isEqualTo(servers);
assertThat(selection.config).isEqualTo( assertThat(selection.policySelection.config)
parseConfig("{\"childPolicy\": [ {\"pick_first\": {} } ] }")); .isEqualTo(((PolicySelection) lbConfig.getConfig()).config);
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@Test @Test
public void decideLoadBalancerProvider_policyUnavailButGrpclbAddressPresent() throws Exception { public void decideLoadBalancerProvider_policyUnavailButGrpclbAddressPresent() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig =
parseConfig(
"{\"loadBalancingConfig\": ["
+ "{\"unavail\": {} }"
+ "] }");
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList( Collections.singletonList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
new SocketAddress(){}, new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); 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.serverList).isEqualTo(servers);
assertThat(selection.config).isNull(); assertThat(selection.policySelection.config).isNull();
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@ -524,34 +539,32 @@ public class AutoConfiguredLoadBalancerFactoryTest {
LoadBalancerRegistry registry = new LoadBalancerRegistry(); LoadBalancerRegistry registry = new LoadBalancerRegistry();
registry.register(new PickFirstLoadBalancerProvider()); registry.register(new PickFirstLoadBalancerProvider());
LoadBalancerProvider fakeRoundRobinProvider = LoadBalancerProvider fakeRoundRobinProvider =
new FakeLoadBalancerProvider("round_robin", testLbBalancer); new FakeLoadBalancerProvider("round_robin", testLbBalancer, null);
registry.register(fakeRoundRobinProvider); registry.register(fakeRoundRobinProvider);
AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory( AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory(
registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper()); registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"grpclb\": {} } ] }");
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Arrays.asList( Arrays.asList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
new SocketAddress(){}, new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()), Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()),
new EquivalentAddressGroup(new SocketAddress(){})); new EquivalentAddressGroup(new SocketAddress(){}));
PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig); ResolvedPolicySelection selection = lb.resolveLoadBalancerProvider(servers, null);
assertThat(selection.provider).isSameInstanceAs(fakeRoundRobinProvider); assertThat(selection.policySelection.provider).isSameInstanceAs(fakeRoundRobinProvider);
assertThat(selection.config).isNull(); assertThat(selection.policySelection.config).isNull();
verify(channelLogger).log( verify(channelLogger).log(
eq(ChannelLogLevel.ERROR), eq(ChannelLogLevel.ERROR),
startsWith("Found balancer addresses but grpclb runtime is missing")); startsWith("Found balancer addresses but grpclb runtime is missing"));
// Called for the second time, the warning is only logged once // 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 // Balancer addresses are filtered out in the server list passed to round_robin
assertThat(selection.serverList).containsExactly(servers.get(1)); assertThat(selection.serverList).containsExactly(servers.get(1));
assertThat(selection.config).isNull(); verifyNoMoreInteractions(channelLogger);;
verifyNoMoreInteractions(channelLogger);
} }
@Test @Test
@ -559,18 +572,16 @@ public class AutoConfiguredLoadBalancerFactoryTest {
throws Exception { throws Exception {
LoadBalancerRegistry registry = new LoadBalancerRegistry(); LoadBalancerRegistry registry = new LoadBalancerRegistry();
registry.register(new PickFirstLoadBalancerProvider()); registry.register(new PickFirstLoadBalancerProvider());
registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer)); registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer, null));
AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory( AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory(
registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper()); registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"grpclb\": {} } ] }");
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList( Collections.singletonList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
new SocketAddress(){}, new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build())); Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()));
try { try {
lb.decideLoadBalancerProvider(servers, serviceConfig); lb.resolveLoadBalancerProvider(servers, null);
fail("Should throw"); fail("Should throw");
} catch (PolicyException e) { } catch (PolicyException e) {
assertThat(e) assertThat(e)
@ -579,105 +590,26 @@ public class AutoConfiguredLoadBalancerFactoryTest {
} }
} }
@Test
public void decideLoadBalancerProvider_serviceConfigLbPolicyOverridesDefault() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, String> serviceConfig = new HashMap<>();
serviceConfig.put("loadBalancingPolicy", "round_robin");
List<EquivalentAddressGroup> 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 @Test
public void decideLoadBalancerProvider_serviceConfigLbConfigOverridesDefault() throws Exception { public void decideLoadBalancerProvider_serviceConfigLbConfigOverridesDefault() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper()); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, ?> serviceConfig = Map<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {\"setting1\": \"high\"} } ] }"); parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {} } ] }");
ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfigs.getConfig()).isNotNull();
PolicySelection policySelection = (PolicySelection) lbConfigs.getConfig();
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); 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"); "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider");
assertThat(selection.serverList).isEqualTo(servers);
assertThat(selection.config).isEqualTo(Collections.singletonMap("setting1", "high"));
verifyZeroInteractions(channelLogger); verifyZeroInteractions(channelLogger);
} }
@Test @Test
public void decideLoadBalancerProvider_serviceConfigLbPolicyFailsOnUnknown() { public void channelTracing_lbPolicyChanged() throws Exception {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
Map<String, String> serviceConfig = new HashMap<>();
serviceConfig.put("loadBalancingPolicy", "MAGIC_BALANCER");
List<EquivalentAddressGroup> 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<String, ?> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"magic_balancer\": {} } ] }");
List<EquivalentAddressGroup> 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<String, ?> serviceConfig =
parseConfig(
"{\"loadBalancingConfig\": [ {\"magic_balancer\": {} }, {\"round_robin\": {} } ] }");
List<EquivalentAddressGroup> 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<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){}));
PolicySelection selection = lb.decideLoadBalancerProvider(
servers, Collections.<String, Object>emptyMap());
assertThat(selection.provider).isInstanceOf(PickFirstLoadBalancerProvider.class);
assertThat(selection.serverList).isEqualTo(servers);
assertThat(selection.config).isNull();
verifyZeroInteractions(channelLogger);
}
@Test
public void channelTracing_lbPolicyChanged() {
final FakeClock clock = new FakeClock(); final FakeClock clock = new FakeClock();
List<EquivalentAddressGroup> servers = List<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){}));
@ -734,38 +666,44 @@ public class AutoConfiguredLoadBalancerFactoryTest {
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
verifyNoMoreInteractions(channelLogger); verifyNoMoreInteractions(channelLogger);
Map<String, String> serviceConfig = new HashMap<>(); ConfigOrError testLbParsedConfig = ConfigOrError.fromConfig("foo");
serviceConfig.put("loadBalancingPolicy", "round_robin"); nextParsedConfigOrError.set(testLbParsedConfig);
Map<String, ?> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { } } ] }");
ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger);
handleResult = lb.tryHandleResolvedAddresses( handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(servers) .setAddresses(servers)
.setAttributes(Attributes.newBuilder() .setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build())
.build()); .build());
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
verify(channelLogger).log( verify(channelLogger).log(
eq(ChannelLogLevel.INFO), eq(ChannelLogLevel.INFO),
eq("Load balancer changed from {0} to {1}"), eq("Load balancer changed from {0} to {1}"),
eq("PickFirstLoadBalancer"), eq("RoundRobinLoadBalancer")); eq("PickFirstLoadBalancer"),
eq(testLbBalancer.getClass().getSimpleName()));
verify(channelLogger).log( verify(channelLogger).log(
eq(ChannelLogLevel.DEBUG), eq(ChannelLogLevel.DEBUG),
eq("Load-balancing config: {0}"), eq("Load-balancing config: {0}"),
eq(Collections.emptyMap())); eq(testLbParsedConfig.getConfig()));
verifyNoMoreInteractions(channelLogger); 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( handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
.setAddresses(servers) .setAddresses(servers)
.setAttributes(Attributes.newBuilder() .setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build())
.build()); .build());
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
verify(channelLogger, times(2)).log( verify(channelLogger).log(
eq(ChannelLogLevel.DEBUG), eq(ChannelLogLevel.DEBUG),
eq("Load-balancing config: {0}"), eq("Load-balancing config: {0}"),
eq(Collections.emptyMap())); eq(testLbParsedConfig.getConfig()));
verifyNoMoreInteractions(channelLogger); verifyNoMoreInteractions(channelLogger);
servers = Collections.singletonList(new EquivalentAddressGroup( servers = Collections.singletonList(new EquivalentAddressGroup(
@ -781,11 +719,122 @@ public class AutoConfiguredLoadBalancerFactoryTest {
verify(channelLogger).log( verify(channelLogger).log(
eq(ChannelLogLevel.INFO), eq(ChannelLogLevel.INFO),
eq("Load balancer changed from {0} to {1}"), eq("Load balancer changed from {0} to {1}"),
eq("RoundRobinLoadBalancer"), eq("GrpclbLoadBalancer")); eq(testLbBalancer.getClass().getSimpleName()), eq("GrpclbLoadBalancer"));
verifyNoMoreInteractions(channelLogger); verifyNoMoreInteractions(channelLogger);
} }
@Test
public void parseLoadBalancerConfig_failedOnUnknown() throws Exception {
Map<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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 { public static class ForwardingLoadBalancer extends LoadBalancer {
private final LoadBalancer delegate; 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 String policyName;
private final LoadBalancer balancer; private final LoadBalancer balancer;
private final AtomicReference<ConfigOrError> nextParsedLbPolicyConfig;
FakeLoadBalancerProvider(String policyName, LoadBalancer balancer) { FakeLoadBalancerProvider(
String policyName,
LoadBalancer balancer,
AtomicReference<ConfigOrError> nextParsedLbPolicyConfig) {
this.policyName = policyName; this.policyName = policyName;
this.balancer = balancer; this.balancer = balancer;
this.nextParsedLbPolicyConfig = nextParsedLbPolicyConfig;
} }
@Override @Override
@ -914,5 +968,14 @@ public class AutoConfiguredLoadBalancerFactoryTest {
public LoadBalancer newLoadBalancer(Helper helper) { public LoadBalancer newLoadBalancer(Helper helper) {
return balancer; return balancer;
} }
@Override
public ConfigOrError parseLoadBalancingPolicyConfig(
Map<String, ?> rawLoadBalancingPolicyConfig) {
if (nextParsedLbPolicyConfig == null) {
return super.parseLoadBalancingPolicyConfig(rawLoadBalancingPolicyConfig);
}
return nextParsedLbPolicyConfig.get();
}
} }
} }

View File

@ -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<ConfigOrError> nextParsedConfigOrError =
new AtomicReference<>(ConfigOrError.fromConfig("default"));
private final AtomicReference<ConfigOrError> 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<EquivalentAddressGroup> 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<String, ?> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": { } } ] }");
ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig, channelLogger);
final List<EquivalentAddressGroup> 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<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }");
ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfigs.getConfig()).isNotNull();
final List<EquivalentAddressGroup> 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<ResolvedAddresses> 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<Map<String, ?>> 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<EquivalentAddressGroup> 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<String, ?> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }");
ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(serviceConfig, helper.getChannelLogger());
Status handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(Collections.<EquivalentAddressGroup>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<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb2\": { \"setting1\": \"high\" } } ] }");
ConfigOrError lbConfigs =
lbf.parseLoadBalancerPolicy(rawServiceConfig, helper.getChannelLogger());
Status handleResult = lb.tryHandleResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
.setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.build());
assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer2);
assertThat(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).isTrue();
ArgumentCaptor<ResolvedAddresses> 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<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingPolicy\": \"round_robin\"}");
ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfig.getConfig()).isNotNull();
PolicySelection policySelection = (PolicySelection) lbConfig.getConfig();
List<EquivalentAddressGroup> servers =
Arrays.asList(
new EquivalentAddressGroup(
new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()),
new EquivalentAddressGroup(
new SocketAddress(){}));
List<EquivalentAddressGroup> 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<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}");
ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfig.getConfig()).isNotNull();
PolicySelection policySelection = (PolicySelection) lbConfig.getConfig();
List<EquivalentAddressGroup> servers =
Arrays.asList(
new EquivalentAddressGroup(
new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()),
new EquivalentAddressGroup(
new SocketAddress(){}));
List<EquivalentAddressGroup> 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<String, ?> rawServiceConfig =
parseConfig(
"{\"loadBalancingConfig\": ["
+ "{\"grpclb\": {\"childPolicy\": [ {\"pick_first\": {} } ] } }"
+ "] }");
ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfig.getConfig()).isNotNull();
PolicySelection policySelection = (PolicySelection) lbConfig.getConfig();
List<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<String, ?> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": {} } ] }");
ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig, channelLogger);
assertThat(lbConfigs.getConfig()).isNotNull();
PolicySelection policySelection = (PolicySelection) lbConfigs.getConfig();
List<EquivalentAddressGroup> 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<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){}));
Helper helper = new TestHelper() {
@Override
@Deprecated
public Subchannel createSubchannel(List<EquivalentAddressGroup> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<String, ?> 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<EquivalentAddressGroup> 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<String, ?> parseConfig(String json) throws Exception {
return (Map<String, ?>) 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<EquivalentAddressGroup> addrs;
final Attributes attrs;
@Override
public void start(SubchannelStateListener listener) {
}
@Override
public void shutdown() {
}
@Override
public void requestConnection() {
}
@Override
public List<EquivalentAddressGroup> getAllAddresses() {
return addrs;
}
@Override
public Attributes getAttributes() {
return attrs;
}
@Override
public void updateAddresses(List<EquivalentAddressGroup> 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<ConfigOrError> nextParsedLbPolicyConfig;
FakeLoadBalancerProvider(
String policyName,
LoadBalancer balancer,
AtomicReference<ConfigOrError> 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<String, ?> rawLoadBalancingPolicyConfig) {
if (nextParsedLbPolicyConfig == null) {
return super.parseLoadBalancingPolicyConfig(rawLoadBalancingPolicyConfig);
}
return nextParsedLbPolicyConfig.get();
}
}
}

View File

@ -59,10 +59,17 @@ public class HedgingPolicyTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj; Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj;
ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( ServiceConfigInterceptor serviceConfigInterceptor =
/* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 3, new ServiceConfigInterceptor(/* retryEnabled= */ true);
/* maxHedgedAttemptsLimit = */ 4); serviceConfigInterceptor
serviceConfigInterceptor.handleUpdate(serviceConfig); .handleUpdate(
ManagedChannelServiceConfig
.fromServiceConfig(
serviceConfig,
/* retryEnabled= */ true,
/* maxRetryAttemptsLimit= */ 3,
/* maxHedgedAttemptsLimit= */ 4,
/* loadBalancingConfig= */ null));
MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder(); MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();
@ -131,10 +138,17 @@ public class HedgingPolicyTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj; Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj;
ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( ServiceConfigInterceptor serviceConfigInterceptor =
/* retryEnabled = */ false, /* maxRetryAttemptsLimit = */ 3, new ServiceConfigInterceptor(/* retryEnabled= */ false);
/* maxHedgedAttemptsLimit = */ 4); serviceConfigInterceptor
serviceConfigInterceptor.handleUpdate(serviceConfig); .handleUpdate(
ManagedChannelServiceConfig
.fromServiceConfig(
serviceConfig,
/* retryEnabled= */ false,
/* maxRetryAttemptsLimit= */ 3,
/* maxHedgedAttemptsLimit= */ 4,
/* loadBalancingConfig= */ null));
MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder(); MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();

View File

@ -16,6 +16,7 @@
package io.grpc.internal; package io.grpc.internal;
import static com.google.common.truth.Truth.assertThat;
import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -87,7 +88,6 @@ import org.mockito.junit.MockitoRule;
/** /**
* Unit tests for {@link ManagedChannelImpl}'s idle mode. * Unit tests for {@link ManagedChannelImpl}'s idle mode.
*/ */
@Deprecated // migrate to ManagedChannelImplIdlenessTest2
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class ManagedChannelImplIdlenessTest { public class ManagedChannelImplIdlenessTest {
@Rule @Rule
@ -234,9 +234,12 @@ public class ManagedChannelImplIdlenessTest {
.setAttributes(Attributes.EMPTY) .setAttributes(Attributes.EMPTY)
.build(); .build();
nameResolverListenerCaptor.getValue().onResult(resolutionResult); nameResolverListenerCaptor.getValue().onResult(resolutionResult);
verify(mockLoadBalancer).handleResolvedAddresses(
ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) ArgumentCaptor<ResolvedAddresses> resolvedAddressCaptor =
.build()); ArgumentCaptor.forClass(ResolvedAddresses.class);
verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture());
assertThat(resolvedAddressCaptor.getValue().getAddresses())
.containsExactlyElementsIn(servers);
} }
@Test @Test

View File

@ -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<String, Integer> method =
MethodDescriptor.<String, Integer>newBuilder()
.setType(MethodType.UNKNOWN)
.setFullMethodName("service/method")
.setRequestMarshaller(new StringMarshaller())
.setResponseMarshaller(new IntegerMarshaller())
.build();
private final List<EquivalentAddressGroup> servers = Lists.newArrayList();
private final ObjectPool<Executor> executorPool =
new FixedObjectPool<Executor>(executor.getScheduledExecutorService());
private final ObjectPool<Executor> oobExecutorPool =
new FixedObjectPool<Executor>(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<Integer> mockCallListener;
@Mock private ClientCall.Listener<Integer> mockCallListener2;
@Captor private ArgumentCaptor<NameResolver.Listener2> nameResolverListenerCaptor;
private BlockingQueue<MockClientTransportInfo> 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> {
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.<ClientInterceptor>emptyList(),
TimeProvider.SYSTEM_TIME_PROVIDER);
newTransports = TestUtils.captureTransports(mockTransportFactory);
for (int i = 0; i < 2; i++) {
ArrayList<SocketAddress> 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<ScheduledTask> 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<String, Integer> 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<ResolvedAddresses> 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<String, Integer> 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<String, Integer> 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<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata());
// Verify that we have exited the idle mode
ArgumentCaptor<Helper> 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<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata()); // Create LB
ArgumentCaptor<Helper> 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<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata()); // Create LB
ArgumentCaptor<Helper> 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<SocketAddress> 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<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata());
// Verify that we have exited the idle mode
ArgumentCaptor<Helper> 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<String, Integer> 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<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata()); // Create LB
ArgumentCaptor<Helper> 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<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata()); // Create LB
ArgumentCaptor<Helper> 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<SocketAddress> 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<Subchannel> 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));
}
});
}
}

View File

@ -53,7 +53,6 @@ import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; 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.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
@ -103,6 +102,7 @@ import io.grpc.ServerMethodDefinition;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.Status.Code; import io.grpc.Status.Code;
import io.grpc.StringMarshaller; import io.grpc.StringMarshaller;
import io.grpc.internal.AutoConfiguredLoadBalancerFactory.PolicySelection;
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions; import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
import io.grpc.internal.InternalSubchannel.TransportLogger; import io.grpc.internal.InternalSubchannel.TransportLogger;
import io.grpc.internal.ManagedChannelImpl.ScParser; import io.grpc.internal.ManagedChannelImpl.ScParser;
@ -152,8 +152,9 @@ import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
/** Unit tests for {@link ManagedChannelImpl}. */ /** Unit tests for {@link ManagedChannelImpl}. */
@Deprecated // to be migrated to ManagedChannelImplTest2
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
// TODO(creamsoup) remove backward compatible check when fully migrated
@SuppressWarnings("deprecation")
public class ManagedChannelImplTest { public class ManagedChannelImplTest {
private static final int DEFAULT_PORT = 447; private static final int DEFAULT_PORT = 447;
@ -272,6 +273,8 @@ public class ManagedChannelImplTest {
private boolean requestConnection = true; private boolean requestConnection = true;
private BlockingQueue<MockClientTransportInfo> transports; private BlockingQueue<MockClientTransportInfo> transports;
private boolean panicExpected; private boolean panicExpected;
@Captor
private ArgumentCaptor<ResolvedAddresses> resolvedAddressCaptor;
private ArgumentCaptor<ClientStreamListener> streamListenerCaptor = private ArgumentCaptor<ClientStreamListener> streamListenerCaptor =
ArgumentCaptor.forClass(ClientStreamListener.class); ArgumentCaptor.forClass(ClientStreamListener.class);
@ -749,11 +752,8 @@ public class ManagedChannelImplTest {
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class));
verify(mockLoadBalancer).handleResolvedAddresses( verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture());
ResolvedAddresses.newBuilder() assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup);
.setAddresses(Arrays.asList(addressGroup))
.setAttributes(Attributes.EMPTY)
.build());
SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class); SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class);
SubchannelStateListener stateListener2 = 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 // Pass a FakeNameResolverFactory with an empty list and LB config
FakeNameResolverFactory nameResolverFactory = FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri).build(); new FakeNameResolverFactory.Builder(expectedUri).build();
Map<String, Object> serviceConfig = Map<String, Object> rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }"); parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }");
Attributes serviceConfigAttrs = ManagedChannelServiceConfig parsedServiceConfig =
Attributes.newBuilder() createManagedChannelServiceConfig(rawServiceConfig, null);
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) nameResolverFactory.nextConfigOrError.set(ConfigOrError.fromConfig(parsedServiceConfig));
.build(); nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
createChannel(); createChannel();
@ -985,7 +984,7 @@ public class ManagedChannelImplTest {
verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture()); verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture());
Status status = statusCaptor.getValue(); Status status = statusCaptor.getValue();
assertSame(Status.Code.UNAVAILABLE, status.getCode()); assertSame(Status.Code.UNAVAILABLE, status.getCode());
Truth.assertThat(status.getDescription()).startsWith(errorDescription); assertThat(status.getDescription()).startsWith(errorDescription);
// A resolution retry has been scheduled // A resolution retry has been scheduled
assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); 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 // Pass a FakeNameResolverFactory with an empty list and LB config
FakeNameResolverFactory nameResolverFactory = FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri).build(); new FakeNameResolverFactory.Builder(expectedUri).build();
Map<String, Object> serviceConfig = String rawLbConfig = "{ \"setting1\": \"high\" }";
parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }"); Map<String, Object> rawServiceConfig =
Attributes serviceConfigAttrs = parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": " + rawLbConfig + " } ] }");
Attributes.newBuilder() ManagedChannelServiceConfig parsedServiceConfig =
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) createManagedChannelServiceConfig(
.build(); rawServiceConfig,
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); new PolicySelection(
mockLoadBalancerProvider,
parseConfig(rawLbConfig),
new Object()));
nameResolverFactory.nextConfigOrError.set(ConfigOrError.fromConfig(parsedServiceConfig));
nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
createChannel(); createChannel();
@ -1018,7 +1022,7 @@ public class ManagedChannelImplTest {
Map<String, ?> lbConfig = actualAttrs.get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG); Map<String, ?> lbConfig = actualAttrs.get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG);
assertEquals(ImmutableMap.of("setting1", "high"), lbConfig); assertEquals(ImmutableMap.of("setting1", "high"), lbConfig);
assertSame( assertSame(
serviceConfig, actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)); rawServiceConfig, actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG));
// A no resolution retry // A no resolution retry
assertEquals(0, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); assertEquals(0, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER));
@ -1099,10 +1103,8 @@ public class ManagedChannelImplTest {
// Simulate name resolution results // Simulate name resolution results
EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs);
inOrder.verify(mockLoadBalancer).handleResolvedAddresses( inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture());
ResolvedAddresses.newBuilder() assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup);
.setAddresses(Arrays.asList(addressGroup))
.build());
Subchannel subchannel = Subchannel subchannel =
createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener);
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
@ -1248,10 +1250,9 @@ public class ManagedChannelImplTest {
// Simulate name resolution results // Simulate name resolution results
EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs);
inOrder.verify(mockLoadBalancer).handleResolvedAddresses( inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture());
ResolvedAddresses.newBuilder() assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup);
.setAddresses(Arrays.asList(addressGroup))
.build());
Subchannel subchannel = Subchannel subchannel =
createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener);
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
@ -2760,7 +2761,6 @@ public class ManagedChannelImplTest {
.setAddresses(Collections.singletonList( .setAddresses(Collections.singletonList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))))
.setAttributes(Attributes.EMPTY)
.build(); .build();
nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1);
assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize);
@ -2778,7 +2778,6 @@ public class ManagedChannelImplTest {
.setAddresses(Collections.singletonList( .setAddresses(Collections.singletonList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))))
.setAttributes(Attributes.EMPTY)
.build(); .build();
nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2);
assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1);
@ -2800,11 +2799,16 @@ public class ManagedChannelImplTest {
Attributes.newBuilder() Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, new HashMap<String, Object>()) .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, new HashMap<String, Object>())
.build(); .build();
ManagedChannelServiceConfig mcsc1 = createManagedChannelServiceConfig(
ImmutableMap.<String, Object>of(),
new PolicySelection(
mockLoadBalancerProvider, ImmutableMap.of("foo", "bar"), null));
ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() ResolutionResult resolutionResult1 = ResolutionResult.newBuilder()
.setAddresses(Collections.singletonList( .setAddresses(Collections.singletonList(
new EquivalentAddressGroup( new EquivalentAddressGroup(
Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))))
.setAttributes(attributes) .setAttributes(attributes)
.setServiceConfig(ConfigOrError.fromConfig(mcsc1))
.build(); .build();
nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult1);
assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1);
@ -2821,6 +2825,7 @@ public class ManagedChannelImplTest {
new EquivalentAddressGroup( new EquivalentAddressGroup(
Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))))
.setAttributes(attributes) .setAttributes(attributes)
.setServiceConfig(ConfigOrError.fromConfig(mcsc1))
.build(); .build();
nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2);
assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize);
@ -2838,6 +2843,7 @@ public class ManagedChannelImplTest {
new EquivalentAddressGroup( new EquivalentAddressGroup(
Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))))
.setAttributes(attributes) .setAttributes(attributes)
.setServiceConfig(ConfigOrError.fromConfig(ManagedChannelServiceConfig.empty()))
.build(); .build();
nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult3); nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult3);
assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1);
@ -3187,16 +3193,21 @@ public class ManagedChannelImplTest {
name.put("service", "service"); name.put("service", "service");
methodConfig.put("name", Arrays.<Object>asList(name)); methodConfig.put("name", Arrays.<Object>asList(name));
methodConfig.put("retryPolicy", retryPolicy); methodConfig.put("retryPolicy", retryPolicy);
Map<String, Object> serviceConfig = new HashMap<>(); Map<String, Object> rawServiceConfig = new HashMap<>();
serviceConfig.put("methodConfig", Arrays.<Object>asList(methodConfig)); rawServiceConfig.put("methodConfig", Arrays.<Object>asList(methodConfig));
Attributes attributesWithRetryPolicy = Attributes Attributes attributesWithRetryPolicy = Attributes
.newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build(); .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig).build();
FakeNameResolverFactory nameResolverFactory = FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri) new FakeNameResolverFactory.Builder(expectedUri)
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
.build(); .build();
nameResolverFactory.nextResolvedAttributes.set(attributesWithRetryPolicy); ManagedChannelServiceConfig managedChannelServiceConfig =
createManagedChannelServiceConfig(rawServiceConfig, null);
nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
nameResolverFactory.nextConfigOrError.set(
ConfigOrError.fromConfig(managedChannelServiceConfig));
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
channelBuilder.executor(MoreExecutors.directExecutor()); channelBuilder.executor(MoreExecutors.directExecutor());
channelBuilder.enableRetry(); channelBuilder.enableRetry();
@ -3296,16 +3307,21 @@ public class ManagedChannelImplTest {
name.put("service", "service"); name.put("service", "service");
methodConfig.put("name", Arrays.<Object>asList(name)); methodConfig.put("name", Arrays.<Object>asList(name));
methodConfig.put("hedgingPolicy", hedgingPolicy); methodConfig.put("hedgingPolicy", hedgingPolicy);
Map<String, Object> serviceConfig = new HashMap<>(); Map<String, Object> rawServiceConfig = new HashMap<>();
serviceConfig.put("methodConfig", Arrays.<Object>asList(methodConfig)); rawServiceConfig.put("methodConfig", Arrays.<Object>asList(methodConfig));
Attributes attributesWithRetryPolicy = Attributes Attributes attributesWithRetryPolicy = Attributes
.newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build(); .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig).build();
FakeNameResolverFactory nameResolverFactory = FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri) new FakeNameResolverFactory.Builder(expectedUri)
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
.build(); .build();
nameResolverFactory.nextResolvedAttributes.set(attributesWithRetryPolicy); ManagedChannelServiceConfig managedChannelServiceConfig =
createManagedChannelServiceConfig(rawServiceConfig, null);
nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
nameResolverFactory.nextConfigOrError.set(
ConfigOrError.fromConfig(managedChannelServiceConfig));
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
channelBuilder.executor(MoreExecutors.directExecutor()); channelBuilder.executor(MoreExecutors.directExecutor());
channelBuilder.enableRetry(); channelBuilder.enableRetry();
@ -3388,6 +3404,8 @@ public class ManagedChannelImplTest {
@Test @Test
public void badServiceConfigIsRecoverable() throws Exception { public void badServiceConfigIsRecoverable() throws Exception {
final Map<String, Object> invalidServiceConfig =
parseConfig("{\"loadBalancingConfig\": [{\"kaboom\": {}}]}");
final List<EquivalentAddressGroup> addresses = final List<EquivalentAddressGroup> addresses =
ImmutableList.of(new EquivalentAddressGroup(new SocketAddress() {})); ImmutableList.of(new EquivalentAddressGroup(new SocketAddress() {}));
final class FakeNameResolver extends NameResolver { final class FakeNameResolver extends NameResolver {
@ -3407,9 +3425,11 @@ public class ManagedChannelImplTest {
.setAttributes( .setAttributes(
Attributes.newBuilder() Attributes.newBuilder()
.set( .set(
GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, invalidServiceConfig)
ImmutableMap.<String, Object>of("loadBalancingPolicy", "kaboom"))
.build()) .build())
.setServiceConfig(
ConfigOrError.fromError(
Status.INTERNAL.withDescription("kaboom is invalid")))
.build()); .build());
} }
@ -3454,23 +3474,31 @@ public class ManagedChannelImplTest {
ListenableFuture<Void> future1 = ClientCalls.futureUnaryCall(call1, null); ListenableFuture<Void> future1 = ClientCalls.futureUnaryCall(call1, null);
executor.runDueTasks(); executor.runDueTasks();
try { try {
future1.get(); future1.get(1, TimeUnit.SECONDS);
Assert.fail(); Assert.fail();
} catch (ExecutionException e) { } catch (ExecutionException e) {
assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("kaboom"); assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("kaboom");
} }
// ok the service config is bad, let's fix it. // ok the service config is bad, let's fix it.
Map<String, Object> 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( factory.resolver.listener.onResult(
ResolutionResult.newBuilder() ResolutionResult.newBuilder()
.setAddresses(addresses) .setAddresses(addresses)
.setAttributes( .setAttributes(
Attributes.newBuilder() Attributes.newBuilder()
.set( .set(
GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig)
ImmutableMap.<String, Object>of("loadBalancingPolicy", "round_robin"))
.build()) .build())
.setServiceConfig(ConfigOrError.fromConfig(managedChannelServiceConfig))
.build()); .build());
ClientCall<Void, Void> call2 = mychannel.newCall( ClientCall<Void, Void> call2 = mychannel.newCall(
@ -3621,7 +3649,8 @@ public class ManagedChannelImplTest {
retryEnabled, retryEnabled,
maxRetryAttemptsLimit, maxRetryAttemptsLimit,
maxHedgedAttemptsLimit, maxHedgedAttemptsLimit,
autoConfiguredLoadBalancerFactory); autoConfiguredLoadBalancerFactory,
mock(ChannelLogger.class));
ConfigOrError coe = parser.parseServiceConfig(ImmutableMap.<String, Object>of()); ConfigOrError coe = parser.parseServiceConfig(ImmutableMap.<String, Object>of());
@ -3643,7 +3672,8 @@ public class ManagedChannelImplTest {
retryEnabled, retryEnabled,
maxRetryAttemptsLimit, maxRetryAttemptsLimit,
maxHedgedAttemptsLimit, maxHedgedAttemptsLimit,
autoConfiguredLoadBalancerFactory); autoConfiguredLoadBalancerFactory,
mock(ChannelLogger.class));
ConfigOrError coe = ConfigOrError coe =
parser.parseServiceConfig(ImmutableMap.<String, Object>of("methodConfig", "bogus")); parser.parseServiceConfig(ImmutableMap.<String, Object>of("methodConfig", "bogus"));
@ -3666,14 +3696,15 @@ public class ManagedChannelImplTest {
retryEnabled, retryEnabled,
maxRetryAttemptsLimit, maxRetryAttemptsLimit,
maxHedgedAttemptsLimit, maxHedgedAttemptsLimit,
autoConfiguredLoadBalancerFactory); autoConfiguredLoadBalancerFactory,
mock(ChannelLogger.class));
ConfigOrError coe = ConfigOrError coe =
parser.parseServiceConfig(ImmutableMap.of("loadBalancingConfig", ImmutableList.of())); parser.parseServiceConfig(ImmutableMap.of("loadBalancingConfig", ImmutableList.of()));
assertThat(coe.getError()).isNull(); assertThat(coe.getError()).isNull();
ManagedChannelServiceConfig cfg = (ManagedChannelServiceConfig) coe.getConfig(); ManagedChannelServiceConfig cfg = (ManagedChannelServiceConfig) coe.getConfig();
assertThat(cfg.getLoadBalancingConfig()).isEqualTo(null); assertThat(cfg.getLoadBalancingConfig()).isNull();
} }
@Test @Test
@ -3686,15 +3717,15 @@ public class ManagedChannelImplTest {
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
channelBuilder.disableServiceConfigLookUp(); channelBuilder.disableServiceConfigLookUp();
Map<String, Object> serviceConfig = Map<String, Object> rawServiceConfig =
parseConfig("{\"methodConfig\":[{" parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}]," + "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}"); + "\"waitForReady\":true}]}");
Attributes serviceConfigAttrs = ManagedChannelServiceConfig managedChannelServiceConfig =
Attributes.newBuilder() createManagedChannelServiceConfig(rawServiceConfig, null);
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
.build(); nameResolverFactory.nextConfigOrError.set(
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); ConfigOrError.fromConfig(managedChannelServiceConfig));
createChannel(); createChannel();
@ -3703,7 +3734,7 @@ public class ManagedChannelImplTest {
verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture());
assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup);
Attributes actualAttrs = resultCaptor.getValue().getAttributes(); 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)); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally { } finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
@ -3725,12 +3756,12 @@ public class ManagedChannelImplTest {
+ "\"waitForReady\":true}]}"); + "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig); channelBuilder.defaultServiceConfig(defaultServiceConfig);
Map<String, Object> serviceConfig = new HashMap<>(); Map<String, Object> rawServiceConfig = new HashMap<>();
Attributes serviceConfigAttrs = ManagedChannelServiceConfig managedChannelServiceConfig =
Attributes.newBuilder() createManagedChannelServiceConfig(rawServiceConfig, null);
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
.build(); nameResolverFactory.nextConfigOrError.set(
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); ConfigOrError.fromConfig(managedChannelServiceConfig));
createChannel(); createChannel();
@ -3757,15 +3788,15 @@ public class ManagedChannelImplTest {
.setServers(ImmutableList.of(addressGroup)).build(); .setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
Map<String, Object> serviceConfig = Map<String, Object> rawServiceConfig =
parseConfig("{\"methodConfig\":[{" parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}]," + "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}"); + "\"waitForReady\":true}]}");
Attributes serviceConfigAttrs = ManagedChannelServiceConfig managedChannelServiceConfig =
Attributes.newBuilder() createManagedChannelServiceConfig(rawServiceConfig, null);
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
.build(); nameResolverFactory.nextConfigOrError.set(
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); ConfigOrError.fromConfig(managedChannelServiceConfig));
createChannel(); createChannel();
ArgumentCaptor<ResolvedAddresses> resultCaptor = ArgumentCaptor<ResolvedAddresses> resultCaptor =
@ -3775,19 +3806,19 @@ public class ManagedChannelImplTest {
Attributes actualAttrs = resultCaptor.getValue().getAttributes(); Attributes actualAttrs = resultCaptor.getValue().getAttributes();
assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(serviceConfig); .isEqualTo(rawServiceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
// new config // new config
serviceConfig = rawServiceConfig =
parseConfig("{\"methodConfig\":[{" parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}]," + "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":false}]}"); + "\"waitForReady\":false}]}");
serviceConfigAttrs = managedChannelServiceConfig =
Attributes.newBuilder() createManagedChannelServiceConfig(rawServiceConfig, null);
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
.build(); nameResolverFactory.nextConfigOrError.set(
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); ConfigOrError.fromConfig(managedChannelServiceConfig));
nameResolverFactory.allResolved(); nameResolverFactory.allResolved();
resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class);
@ -3795,7 +3826,7 @@ public class ManagedChannelImplTest {
assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup);
actualAttrs = resultCaptor.getValue().getAttributes(); actualAttrs = resultCaptor.getValue().getAttributes();
assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(serviceConfig); .isEqualTo(rawServiceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally { } finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
@ -3816,15 +3847,15 @@ public class ManagedChannelImplTest {
+ "\"waitForReady\":true}]}"); + "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig); channelBuilder.defaultServiceConfig(defaultServiceConfig);
Map<String, Object> serviceConfig = Map<String, Object> rawServiceConfig =
parseConfig("{\"methodConfig\":[{" parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService2\"}]," + "\"name\":[{\"service\":\"SimpleService2\"}],"
+ "\"waitForReady\":false}]}"); + "\"waitForReady\":false}]}");
Attributes serviceConfigAttrs = ManagedChannelServiceConfig managedChannelServiceConfig =
Attributes.newBuilder() createManagedChannelServiceConfig(rawServiceConfig, null);
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig) nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
.build(); nameResolverFactory.nextConfigOrError.set(
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); ConfigOrError.fromConfig(managedChannelServiceConfig));
createChannel(); createChannel();
ArgumentCaptor<ResolvedAddresses> resultCaptor = ArgumentCaptor<ResolvedAddresses> resultCaptor =
@ -3833,7 +3864,7 @@ public class ManagedChannelImplTest {
assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup);
Attributes actualAttrs = resultCaptor.getValue().getAttributes(); Attributes actualAttrs = resultCaptor.getValue().getAttributes();
assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG)) assertThat(actualAttrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(serviceConfig); .isEqualTo(rawServiceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally { } finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
@ -3855,8 +3886,8 @@ public class ManagedChannelImplTest {
+ "\"waitForReady\":true}]}"); + "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig); channelBuilder.defaultServiceConfig(defaultServiceConfig);
Attributes serviceConfigAttrs = Attributes.EMPTY; nameResolverFactory.nextRawServiceConfig.set(null);
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); nameResolverFactory.nextConfigOrError.set(null);
createChannel(); createChannel();
ArgumentCaptor<ResolvedAddresses> resultCaptor = ArgumentCaptor<ResolvedAddresses> resultCaptor =
@ -3881,8 +3912,12 @@ public class ManagedChannelImplTest {
.setServers(ImmutableList.of(addressGroup)).build(); .setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
Attributes serviceConfigAttrs = Attributes.EMPTY; Map<String, Object> rawServiceConfig = Collections.emptyMap();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs); ManagedChannelServiceConfig managedChannelServiceConfig =
createManagedChannelServiceConfig(rawServiceConfig, null);
nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig);
nameResolverFactory.nextConfigOrError.set(
ConfigOrError.fromConfig(managedChannelServiceConfig));
createChannel(); createChannel();
ArgumentCaptor<ResolvedAddresses> resultCaptor = ArgumentCaptor<ResolvedAddresses> resultCaptor =
@ -3890,7 +3925,7 @@ public class ManagedChannelImplTest {
verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture());
assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup);
Attributes actualAttrs = resultCaptor.getValue().getAttributes(); 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)); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally { } finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
@ -3983,10 +4018,9 @@ public class ManagedChannelImplTest {
final List<EquivalentAddressGroup> servers; final List<EquivalentAddressGroup> servers;
final boolean resolvedAtStart; final boolean resolvedAtStart;
final Status error; final Status error;
final ArrayList<FakeNameResolver> resolvers = new ArrayList<>(); final ArrayList<FakeNameResolverFactory.FakeNameResolver> resolvers = new ArrayList<>();
// The Attributes argument of the next invocation of listener.onAddresses(servers, attrs) final AtomicReference<ConfigOrError> nextConfigOrError = new AtomicReference<>();
final AtomicReference<Attributes> nextResolvedAttributes = final AtomicReference<Map<String, ?>> nextRawServiceConfig = new AtomicReference<>();
new AtomicReference<>(Attributes.EMPTY);
FakeNameResolverFactory( FakeNameResolverFactory(
URI expectedUri, URI expectedUri,
@ -4005,7 +4039,8 @@ public class ManagedChannelImplTest {
return null; return null;
} }
assertEquals(DEFAULT_PORT, args.getDefaultPort()); assertEquals(DEFAULT_PORT, args.getDefaultPort());
FakeNameResolver resolver = new FakeNameResolver(error); FakeNameResolverFactory.FakeNameResolver resolver =
new FakeNameResolverFactory.FakeNameResolver(error);
resolvers.add(resolver); resolvers.add(resolver);
return resolver; return resolver;
} }
@ -4016,7 +4051,7 @@ public class ManagedChannelImplTest {
} }
void allResolved() { void allResolved() {
for (FakeNameResolver resolver : resolvers) { for (FakeNameResolverFactory.FakeNameResolver resolver : resolvers) {
resolver.resolved(); resolver.resolved();
} }
} }
@ -4052,13 +4087,24 @@ public class ManagedChannelImplTest {
listener.onError(error); listener.onError(error);
return; return;
} }
listener.onResult( ResolutionResult.Builder builder =
ResolutionResult.newBuilder() ResolutionResult.newBuilder()
.setAddresses(servers) .setAddresses(servers);
.setAttributes(nextResolvedAttributes.get()) ConfigOrError configOrError = nextConfigOrError.get();
Map<String, ?> rawServiceConfig = nextRawServiceConfig.get();
if (configOrError != null) {
builder.setServiceConfig(configOrError);
}
if (rawServiceConfig != null) {
builder.setAttributes(
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, rawServiceConfig)
.build()); .build());
} }
listener.onResult(builder.build());
}
@Override public void shutdown() { @Override public void shutdown() {
shutdown = true; shutdown = true;
} }
@ -4079,17 +4125,17 @@ public class ManagedChannelImplTest {
this.expectedUri = expectedUri; this.expectedUri = expectedUri;
} }
Builder setServers(List<EquivalentAddressGroup> servers) { FakeNameResolverFactory.Builder setServers(List<EquivalentAddressGroup> servers) {
this.servers = servers; this.servers = servers;
return this; return this;
} }
Builder setResolvedAtStart(boolean resolvedAtStart) { FakeNameResolverFactory.Builder setResolvedAtStart(boolean resolvedAtStart) {
this.resolvedAtStart = resolvedAtStart; this.resolvedAtStart = resolvedAtStart;
return this; return this;
} }
Builder setError(Status error) { FakeNameResolverFactory.Builder setError(Status error) {
this.error = error; this.error = error;
return this; return this;
} }
@ -4207,4 +4253,11 @@ public class ManagedChannelImplTest {
private static Map<String, Object> parseConfig(String json) throws Exception { private static Map<String, Object> parseConfig(String json) throws Exception {
return (Map<String, Object>) JsonParser.parse(json); return (Map<String, Object>) JsonParser.parse(json);
} }
private static ManagedChannelServiceConfig createManagedChannelServiceConfig(
Map<String, Object> rawServiceConfig, PolicySelection policySelection) {
// Provides dummy variable for retry related params (not used in this test class)
return ManagedChannelServiceConfig
.fromServiceConfig(rawServiceConfig, true, 3, 4, policySelection);
}
} }

View File

@ -62,10 +62,17 @@ public class RetryPolicyTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj; Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj;
ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( ServiceConfigInterceptor serviceConfigInterceptor =
/* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 4, new ServiceConfigInterceptor(/* retryEnabled= */ true);
/* maxHedgedAttemptsLimit = */ 3); serviceConfigInterceptor
serviceConfigInterceptor.handleUpdate(serviceConfig); .handleUpdate(
ManagedChannelServiceConfig
.fromServiceConfig(
serviceConfig,
/* retryEnabled= */ true,
/* maxRetryAttemptsLimit= */ 4,
/* maxHedgedAttemptsLimit= */ 3,
/* loadBalancingConfig= */ null));
MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder(); MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();
@ -140,10 +147,17 @@ public class RetryPolicyTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj; Map<String, ?> serviceConfig = (Map<String, ?>) serviceConfigObj;
ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( ServiceConfigInterceptor serviceConfigInterceptor =
/* retryEnabled = */ false, /* maxRetryAttemptsLimit = */ 4, new ServiceConfigInterceptor(/* retryEnabled= */ false);
/* maxHedgedAttemptsLimit = */ 3); serviceConfigInterceptor
serviceConfigInterceptor.handleUpdate(serviceConfig); .handleUpdate(
ManagedChannelServiceConfig
.fromServiceConfig(
serviceConfig,
/* retryEnabled= */ false,
/* maxRetryAttemptsLimit= */ 4,
/* maxHedgedAttemptsLimit= */ 3,
/* loadBalancingConfig= */ null));
MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder(); MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();

View File

@ -95,7 +95,7 @@ public class ServiceConfigErrorHandlingTest {
@Override @Override
public boolean shouldAccept(Runnable command) { public boolean shouldAccept(Runnable command) {
return command.toString().contains( 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 ExpectedException thrown = ExpectedException.none();
@Rule public final MockitoRule mocks = MockitoJUnit.rule(); @Rule public final MockitoRule mocks = MockitoJUnit.rule();
private ManagedChannelImpl2 channel; private ManagedChannelImpl channel;
private final AtomicReference<Status> nextLbPolicyConfigError = new AtomicReference<>(); private final AtomicReference<Status> nextLbPolicyConfigError = new AtomicReference<>();
private FakeLoadBalancer mockLoadBalancer = private FakeLoadBalancer mockLoadBalancer =
@ -157,7 +157,7 @@ public class ServiceConfigErrorHandlingTest {
checkState(channel == null); checkState(channel == null);
channel = channel =
new ManagedChannelImpl2( new ManagedChannelImpl(
channelBuilder, channelBuilder,
mockTransportFactory, mockTransportFactory,
new FakeBackoffPolicyProvider(), new FakeBackoffPolicyProvider(),
@ -175,7 +175,7 @@ public class ServiceConfigErrorHandlingTest {
channel.exitIdleMode(); channel.exitIdleMode();
} }
}); });
if (channelBuilder.idleTimeoutMillis != ManagedChannelImpl2.IDLE_TIMEOUT_MILLIS_DISABLE) { if (channelBuilder.idleTimeoutMillis != ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE) {
numExpectedTasks += 1; numExpectedTasks += 1;
} }

View File

@ -47,7 +47,6 @@ import org.mockito.MockitoAnnotations;
/** /**
* Unit tests for {@link ServiceConfigInterceptor}. * Unit tests for {@link ServiceConfigInterceptor}.
*/ */
@Deprecated // migrate to ServiceConfigInterceptor(Test)?2
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class ServiceConfigInterceptorTest { public class ServiceConfigInterceptorTest {
@ -61,8 +60,8 @@ public class ServiceConfigInterceptorTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
} }
private final ServiceConfigInterceptor interceptor = new ServiceConfigInterceptor( private final ServiceConfigInterceptor interceptor =
/* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 5, /* maxHedgedAttemptsLimit = */ 6); new ServiceConfigInterceptor(/* retryEnabled = */ true);
private final String fullMethodName = private final String fullMethodName =
MethodDescriptor.generateFullMethodName("service", "method"); MethodDescriptor.generateFullMethodName("service", "method");
@ -72,8 +71,6 @@ public class ServiceConfigInterceptorTest {
.setFullMethodName(fullMethodName) .setFullMethodName(fullMethodName)
.build(); .build();
private static final class JsonObj extends HashMap<String, Object> { private static final class JsonObj extends HashMap<String, Object> {
private JsonObj(Object ... kv) { private JsonObj(Object ... kv) {
for (int i = 0; i < kv.length; i += 2) { for (int i = 0; i < kv.length; i += 2) {
@ -93,8 +90,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); 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); 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 methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(createManagedChannelServiceConfig(serviceConfig));
interceptor.handleUpdate(null); interceptor.handleUpdate(null);
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel);
@ -134,8 +133,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 1d); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 1d);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(parsedServiceConfig);
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
@ -148,8 +149,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 10d); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 10d);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(parsedServiceConfig);
interceptor.interceptCall( interceptor.interceptCall(
methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(5), channel); methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(5), channel);
@ -163,8 +166,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 5d); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 5d);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(parsedServiceConfig);
interceptor.interceptCall( interceptor.interceptCall(
methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(10), channel); methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(10), channel);
@ -178,8 +183,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 1d); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 1d);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(parsedServiceConfig);
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
@ -192,8 +199,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 5d); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 5d);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(parsedServiceConfig);
interceptor.interceptCall( interceptor.interceptCall(
methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(10), channel); methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(10), channel);
@ -207,8 +216,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 10d); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 10d);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(parsedServiceConfig);
interceptor.interceptCall( interceptor.interceptCall(
methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(5), channel); methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(5), channel);
@ -222,8 +233,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", false); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", false);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); 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); 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 methodConfig2 = new JsonObj("name", new JsonList(name2), "timeout", "1s");
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2)); 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); interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
@ -255,8 +270,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "100000s"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "100000s");
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); 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); Deadline existingDeadline = Deadline.after(1000, TimeUnit.NANOSECONDS);
interceptor.interceptCall( interceptor.interceptCall(
@ -273,8 +290,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "1s"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "1s");
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); 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); Deadline existingDeadline = Deadline.after(1234567890, TimeUnit.NANOSECONDS);
interceptor.interceptCall( interceptor.interceptCall(
@ -284,7 +303,6 @@ public class ServiceConfigInterceptorTest {
assertThat(callOptionsCap.getValue().getDeadline()).isNotEqualTo(existingDeadline); assertThat(callOptionsCap.getValue().getDeadline()).isNotEqualTo(existingDeadline);
} }
@Test @Test
public void handleUpdate_failsOnMissingServiceName() { public void handleUpdate_failsOnMissingServiceName() {
JsonObj name = new JsonObj("method", "method"); JsonObj name = new JsonObj("method", "method");
@ -294,9 +312,11 @@ public class ServiceConfigInterceptorTest {
thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("missing service"); thrown.expectMessage("missing service");
interceptor.handleUpdate(serviceConfig); ManagedChannelServiceConfig parsedServiceConfig =
} createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(parsedServiceConfig);
}
@Test @Test
public void handleUpdate_failsOnDuplicateMethod() { public void handleUpdate_failsOnDuplicateMethod() {
@ -308,7 +328,10 @@ public class ServiceConfigInterceptorTest {
thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Duplicate method"); thrown.expectMessage("Duplicate method");
interceptor.handleUpdate(serviceConfig); ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(parsedServiceConfig);
} }
@Test @Test
@ -319,7 +342,10 @@ public class ServiceConfigInterceptorTest {
thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("no names in method config"); thrown.expectMessage("no names in method config");
interceptor.handleUpdate(serviceConfig); ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(parsedServiceConfig);
} }
@Test @Test
@ -332,7 +358,10 @@ public class ServiceConfigInterceptorTest {
thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Duplicate service"); thrown.expectMessage("Duplicate service");
interceptor.handleUpdate(serviceConfig); ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(parsedServiceConfig);
} }
@Test @Test
@ -346,7 +375,10 @@ public class ServiceConfigInterceptorTest {
thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Duplicate service"); thrown.expectMessage("Duplicate service");
interceptor.handleUpdate(serviceConfig); ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(parsedServiceConfig);
} }
@Test @Test
@ -358,13 +390,17 @@ public class ServiceConfigInterceptorTest {
JsonObj name2 = new JsonObj("service", "service", "method", "method"); JsonObj name2 = new JsonObj("service", "service", "method", "method");
JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2)); JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2));
JsonObj serviceConfig2 = new JsonObj("methodConfig", new JsonList(methodConfig2)); 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().getServiceMap()).isNotEmpty();
assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty();
interceptor.handleUpdate(serviceConfig2); interceptor.handleUpdate(parsedServiceConfig2);
assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty();
assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isNotEmpty(); assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isNotEmpty();
@ -376,8 +412,10 @@ public class ServiceConfigInterceptorTest {
JsonObj name2 = new JsonObj("service", "service", "method", "method"); JsonObj name2 = new JsonObj("service", "service", "method", "method");
JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2)); JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2));
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
ManagedChannelServiceConfig parsedServiceConfig =
createManagedChannelServiceConfig(serviceConfig);
interceptor.handleUpdate(serviceConfig); interceptor.handleUpdate(parsedServiceConfig);
assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()) assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap())
.containsExactly( .containsExactly(
@ -387,7 +425,6 @@ public class ServiceConfigInterceptorTest {
"service2", new MethodInfo(methodConfig, false, 1, 1)); "service2", new MethodInfo(methodConfig, false, 1, 1));
} }
@Test @Test
public void methodInfo_validateDeadline() { public void methodInfo_validateDeadline() {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
@ -408,7 +445,6 @@ public class ServiceConfigInterceptorTest {
assertThat(info.timeoutNanos).isEqualTo(Long.MAX_VALUE); assertThat(info.timeoutNanos).isEqualTo(Long.MAX_VALUE);
} }
@Test @Test
public void methodInfo_badMaxRequestSize() { public void methodInfo_badMaxRequestSize() {
JsonObj name = new JsonObj("service", "service"); JsonObj name = new JsonObj("service", "service");
@ -431,6 +467,17 @@ public class ServiceConfigInterceptorTest {
new MethodInfo(methodConfig, false, 1, 1); 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<Void> { private static final class NoopMarshaller implements MethodDescriptor.Marshaller<Void> {
@Override @Override

View File

@ -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<CallOptions> 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<Void, Void> methodDescriptor =
MethodDescriptor.newBuilder(new NoopMarshaller(), new NoopMarshaller())
.setType(MethodType.UNARY)
.setFullMethodName(fullMethodName)
.build();
private static final class JsonObj extends HashMap<String, Object> {
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<Object> {
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<Void> {
@Override
public InputStream stream(Void value) {
return null;
}
@Override
public Void parse(InputStream stream) {
return null;
}
}
}