mirror of https://github.com/grpc/grpc-java.git
xds: implement per-RPC hash generation (#7922)
Generates a hash value for each RPC based on the HashPolicies configured for the Route that the RPC is routed to.
This commit is contained in:
parent
b5c0a4a97a
commit
4b52639aa1
|
|
@ -23,6 +23,8 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||||
interface ThreadSafeRandom {
|
interface ThreadSafeRandom {
|
||||||
int nextInt(int bound);
|
int nextInt(int bound);
|
||||||
|
|
||||||
|
long nextLong();
|
||||||
|
|
||||||
final class ThreadSafeRandomImpl implements ThreadSafeRandom {
|
final class ThreadSafeRandomImpl implements ThreadSafeRandom {
|
||||||
|
|
||||||
static final ThreadSafeRandom instance = new ThreadSafeRandomImpl();
|
static final ThreadSafeRandom instance = new ThreadSafeRandomImpl();
|
||||||
|
|
@ -33,5 +35,10 @@ interface ThreadSafeRandom {
|
||||||
public int nextInt(int bound) {
|
public int nextInt(int bound) {
|
||||||
return ThreadLocalRandom.current().nextInt(bound);
|
return ThreadLocalRandom.current().nextInt(bound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long nextLong() {
|
||||||
|
return ThreadLocalRandom.current().nextLong();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
|
||||||
import io.grpc.xds.VirtualHost.Route;
|
import io.grpc.xds.VirtualHost.Route;
|
||||||
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||||
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
|
||||||
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||||
import io.grpc.xds.XdsClient.LdsResourceWatcher;
|
import io.grpc.xds.XdsClient.LdsResourceWatcher;
|
||||||
import io.grpc.xds.XdsClient.LdsUpdate;
|
import io.grpc.xds.XdsClient.LdsUpdate;
|
||||||
|
|
@ -95,6 +96,8 @@ final class XdsNameResolver extends NameResolver {
|
||||||
|
|
||||||
static final CallOptions.Key<String> CLUSTER_SELECTION_KEY =
|
static final CallOptions.Key<String> CLUSTER_SELECTION_KEY =
|
||||||
CallOptions.Key.create("io.grpc.xds.CLUSTER_SELECTION_KEY");
|
CallOptions.Key.create("io.grpc.xds.CLUSTER_SELECTION_KEY");
|
||||||
|
static final CallOptions.Key<Long> RPC_HASH_KEY =
|
||||||
|
CallOptions.Key.create("io.grpc.xds.RPC_HASH_KEY");
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static boolean enableTimeout =
|
static boolean enableTimeout =
|
||||||
Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_TIMEOUT"));
|
Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_TIMEOUT"));
|
||||||
|
|
@ -119,6 +122,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static AtomicLong activeFaultInjectedStreamCounter = new AtomicLong();
|
static AtomicLong activeFaultInjectedStreamCounter = new AtomicLong();
|
||||||
|
|
||||||
|
private final InternalLogId logId;
|
||||||
private final XdsLogger logger;
|
private final XdsLogger logger;
|
||||||
private final String authority;
|
private final String authority;
|
||||||
private final ServiceConfigParser serviceConfigParser;
|
private final ServiceConfigParser serviceConfigParser;
|
||||||
|
|
@ -126,6 +130,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final XdsClientPoolFactory xdsClientPoolFactory;
|
private final XdsClientPoolFactory xdsClientPoolFactory;
|
||||||
private final ThreadSafeRandom random;
|
private final ThreadSafeRandom random;
|
||||||
|
private final XxHash64 hashFunc = XxHash64.INSTANCE;
|
||||||
private final ConcurrentMap<String, AtomicInteger> clusterRefs = new ConcurrentHashMap<>();
|
private final ConcurrentMap<String, AtomicInteger> clusterRefs = new ConcurrentHashMap<>();
|
||||||
private final ConfigSelector configSelector = new ConfigSelector();
|
private final ConfigSelector configSelector = new ConfigSelector();
|
||||||
|
|
||||||
|
|
@ -152,7 +157,8 @@ final class XdsNameResolver extends NameResolver {
|
||||||
this.scheduler = checkNotNull(scheduler, "scheduler");
|
this.scheduler = checkNotNull(scheduler, "scheduler");
|
||||||
this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory");
|
this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory");
|
||||||
this.random = checkNotNull(random, "random");
|
this.random = checkNotNull(random, "random");
|
||||||
logger = XdsLogger.withLogId(InternalLogId.allocate("xds-resolver", name));
|
logId = InternalLogId.allocate("xds-resolver", name);
|
||||||
|
logger = XdsLogger.withLogId(logId);
|
||||||
logger.log(XdsLogLevel.INFO, "Created resolver for {0}", name);
|
logger.log(XdsLogLevel.INFO, "Created resolver for {0}", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,26 +353,33 @@ final class XdsNameResolver extends NameResolver {
|
||||||
private final class ConfigSelector extends InternalConfigSelector {
|
private final class ConfigSelector extends InternalConfigSelector {
|
||||||
@Override
|
@Override
|
||||||
public Result selectConfig(PickSubchannelArgs args) {
|
public Result selectConfig(PickSubchannelArgs args) {
|
||||||
// Index ASCII headers by keys.
|
// Index ASCII headers by key, multi-value headers are concatenated for matching purposes.
|
||||||
Map<String, Iterable<String>> asciiHeaders = new HashMap<>();
|
Map<String, String> asciiHeaders = new HashMap<>();
|
||||||
Metadata headers = args.getHeaders();
|
Metadata headers = args.getHeaders();
|
||||||
for (String headerName : headers.keys()) {
|
for (String headerName : headers.keys()) {
|
||||||
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
|
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Metadata.Key<String> key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
|
Metadata.Key<String> key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
|
||||||
asciiHeaders.put(headerName, headers.getAll(key));
|
Iterable<String> values = headers.getAll(key);
|
||||||
|
if (values != null) {
|
||||||
|
asciiHeaders.put(headerName, Joiner.on(",").join(values));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Special hack for exposing headers: "content-type".
|
||||||
|
asciiHeaders.put("content-type", "application/grpc");
|
||||||
String cluster = null;
|
String cluster = null;
|
||||||
Route selectedRoute = null;
|
Route selectedRoute = null;
|
||||||
HttpFault selectedFaultConfig;
|
HttpFault selectedFaultConfig;
|
||||||
|
RoutingConfig routingCfg;
|
||||||
do {
|
do {
|
||||||
selectedFaultConfig = routingConfig.faultConfig;
|
routingCfg = routingConfig;
|
||||||
for (Route route : routingConfig.routes) {
|
selectedFaultConfig = routingCfg.faultConfig;
|
||||||
|
for (Route route : routingCfg.routes) {
|
||||||
if (matchRoute(route.routeMatch(), "/" + args.getMethodDescriptor().getFullMethodName(),
|
if (matchRoute(route.routeMatch(), "/" + args.getMethodDescriptor().getFullMethodName(),
|
||||||
asciiHeaders, random)) {
|
asciiHeaders, random)) {
|
||||||
selectedRoute = route;
|
selectedRoute = route;
|
||||||
if (routingConfig.applyFaultInjection && route.httpFault() != null) {
|
if (routingCfg.applyFaultInjection && route.httpFault() != null) {
|
||||||
selectedFaultConfig = route.httpFault();
|
selectedFaultConfig = route.httpFault();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -390,7 +403,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
accumulator += weightedCluster.weight();
|
accumulator += weightedCluster.weight();
|
||||||
if (select < accumulator) {
|
if (select < accumulator) {
|
||||||
cluster = weightedCluster.name();
|
cluster = weightedCluster.name();
|
||||||
if (routingConfig.applyFaultInjection && weightedCluster.httpFault() != null) {
|
if (routingCfg.applyFaultInjection && weightedCluster.httpFault() != null) {
|
||||||
selectedFaultConfig = weightedCluster.httpFault();
|
selectedFaultConfig = weightedCluster.httpFault();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -403,7 +416,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
if (enableTimeout) {
|
if (enableTimeout) {
|
||||||
Long timeoutNano = selectedRoute.routeAction().timeoutNano();
|
Long timeoutNano = selectedRoute.routeAction().timeoutNano();
|
||||||
if (timeoutNano == null) {
|
if (timeoutNano == null) {
|
||||||
timeoutNano = routingConfig.fallbackTimeoutNano;
|
timeoutNano = routingCfg.fallbackTimeoutNano;
|
||||||
}
|
}
|
||||||
if (timeoutNano > 0) {
|
if (timeoutNano > 0) {
|
||||||
rawServiceConfig = generateServiceConfigWithMethodTimeoutConfig(timeoutNano);
|
rawServiceConfig = generateServiceConfigWithMethodTimeoutConfig(timeoutNano);
|
||||||
|
|
@ -417,7 +430,6 @@ final class XdsNameResolver extends NameResolver {
|
||||||
parsedServiceConfig.getError().augmentDescription(
|
parsedServiceConfig.getError().augmentDescription(
|
||||||
"Failed to parse service config (method config)"));
|
"Failed to parse service config (method config)"));
|
||||||
}
|
}
|
||||||
final String finalCluster = cluster;
|
|
||||||
if (selectedFaultConfig != null && selectedFaultConfig.maxActiveFaults() != null
|
if (selectedFaultConfig != null && selectedFaultConfig.maxActiveFaults() != null
|
||||||
&& activeFaultInjectedStreamCounter.get() >= selectedFaultConfig.maxActiveFaults()) {
|
&& activeFaultInjectedStreamCounter.get() >= selectedFaultConfig.maxActiveFaults()) {
|
||||||
selectedFaultConfig = null;
|
selectedFaultConfig = null;
|
||||||
|
|
@ -447,15 +459,18 @@ final class XdsNameResolver extends NameResolver {
|
||||||
abortStatus = determineFaultAbortStatus(selectedFaultConfig.faultAbort(), headers);
|
abortStatus = determineFaultAbortStatus(selectedFaultConfig.faultAbort(), headers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
final String finalCluster = cluster;
|
||||||
final Long finalDelayNanos = delayNanos;
|
final Long finalDelayNanos = delayNanos;
|
||||||
final Status finalAbortStatus = abortStatus;
|
final Status finalAbortStatus = abortStatus;
|
||||||
|
final long hash = generateHash(selectedRoute.routeAction().hashPolicies(), asciiHeaders);
|
||||||
class ClusterSelectionInterceptor implements ClientInterceptor {
|
class ClusterSelectionInterceptor implements ClientInterceptor {
|
||||||
@Override
|
@Override
|
||||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||||
final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions,
|
final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions,
|
||||||
final Channel next) {
|
final Channel next) {
|
||||||
final CallOptions callOptionsForCluster =
|
final CallOptions callOptionsForCluster =
|
||||||
callOptions.withOption(CLUSTER_SELECTION_KEY, finalCluster);
|
callOptions.withOption(CLUSTER_SELECTION_KEY, finalCluster)
|
||||||
|
.withOption(RPC_HASH_KEY, hash);
|
||||||
Supplier<ClientCall<ReqT, RespT>> configApplyingCallSupplier =
|
Supplier<ClientCall<ReqT, RespT>> configApplyingCallSupplier =
|
||||||
new Supplier<ClientCall<ReqT, RespT>>() {
|
new Supplier<ClientCall<ReqT, RespT>>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -553,6 +568,36 @@ final class XdsNameResolver extends NameResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long generateHash(List<HashPolicy> hashPolicies, Map<String, String> headers) {
|
||||||
|
Long hash = null;
|
||||||
|
for (HashPolicy policy : hashPolicies) {
|
||||||
|
Long newHash = null;
|
||||||
|
if (policy.type() == HashPolicy.Type.HEADER) {
|
||||||
|
if (headers.containsKey(policy.headerName())) {
|
||||||
|
String value = headers.get(policy.headerName());
|
||||||
|
if (policy.regEx() != null && policy.regExSubstitution() != null) {
|
||||||
|
value = policy.regEx().matcher(value).replaceAll(policy.regExSubstitution());
|
||||||
|
}
|
||||||
|
newHash = hashFunc.hashAsciiString(value);
|
||||||
|
}
|
||||||
|
} else if (policy.type() == HashPolicy.Type.CHANNEL_ID) {
|
||||||
|
newHash = hashFunc.hashLong(logId.getId());
|
||||||
|
}
|
||||||
|
if (newHash != null ) {
|
||||||
|
// Rotating the old value prevents duplicate hash rules from cancelling each other out
|
||||||
|
// and preserves all of the entropy.
|
||||||
|
long oldHash = hash != null ? ((hash << 1L) | (hash >> 63L)) : 0;
|
||||||
|
hash = oldHash ^ newHash;
|
||||||
|
}
|
||||||
|
// If the policy is a terminal policy and a hash has been generated, ignore
|
||||||
|
// the rest of the hash policies.
|
||||||
|
if (policy.isTerminal() && hash != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hash == null ? random.nextLong() : hash;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Long determineFaultDelayNanos(FaultDelay faultDelay, Metadata headers) {
|
private Long determineFaultDelayNanos(FaultDelay faultDelay, Metadata headers) {
|
||||||
Long delayNanos;
|
Long delayNanos;
|
||||||
|
|
@ -748,7 +793,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static boolean matchRoute(RouteMatch routeMatch, String fullMethodName,
|
static boolean matchRoute(RouteMatch routeMatch, String fullMethodName,
|
||||||
Map<String, Iterable<String>> headers, ThreadSafeRandom random) {
|
Map<String, String> headers, ThreadSafeRandom random) {
|
||||||
if (!matchPath(routeMatch.pathMatcher(), fullMethodName)) {
|
if (!matchPath(routeMatch.pathMatcher(), fullMethodName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -774,18 +819,9 @@ final class XdsNameResolver extends NameResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchHeaders(
|
private static boolean matchHeaders(
|
||||||
List<HeaderMatcher> headerMatchers, Map<String, Iterable<String>> headers) {
|
List<HeaderMatcher> headerMatchers, Map<String, String> headers) {
|
||||||
for (HeaderMatcher headerMatcher : headerMatchers) {
|
for (HeaderMatcher headerMatcher : headerMatchers) {
|
||||||
Iterable<String> headerValues = headers.get(headerMatcher.name());
|
if (!matchHeader(headerMatcher, headers.get(headerMatcher.name()))) {
|
||||||
// Special cases for hiding headers: "grpc-previous-rpc-attempts".
|
|
||||||
if (headerMatcher.name().equals("grpc-previous-rpc-attempts")) {
|
|
||||||
headerValues = null;
|
|
||||||
}
|
|
||||||
// Special case for exposing headers: "content-type".
|
|
||||||
if (headerMatcher.name().equals("content-type")) {
|
|
||||||
headerValues = Collections.singletonList("application/grpc");
|
|
||||||
}
|
|
||||||
if (!matchHeader(headerMatcher, headerValues)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -793,33 +829,31 @@ final class XdsNameResolver extends NameResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static boolean matchHeader(HeaderMatcher headerMatcher,
|
static boolean matchHeader(HeaderMatcher headerMatcher, @Nullable String value) {
|
||||||
@Nullable Iterable<String> headerValues) {
|
|
||||||
if (headerMatcher.present() != null) {
|
if (headerMatcher.present() != null) {
|
||||||
return (headerValues == null) == headerMatcher.present().equals(headerMatcher.inverted());
|
return (value == null) == headerMatcher.present().equals(headerMatcher.inverted());
|
||||||
}
|
}
|
||||||
if (headerValues == null) {
|
if (value == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String valueStr = Joiner.on(",").join(headerValues);
|
|
||||||
boolean baseMatch;
|
boolean baseMatch;
|
||||||
if (headerMatcher.exactValue() != null) {
|
if (headerMatcher.exactValue() != null) {
|
||||||
baseMatch = headerMatcher.exactValue().equals(valueStr);
|
baseMatch = headerMatcher.exactValue().equals(value);
|
||||||
} else if (headerMatcher.safeRegEx() != null) {
|
} else if (headerMatcher.safeRegEx() != null) {
|
||||||
baseMatch = headerMatcher.safeRegEx().matches(valueStr);
|
baseMatch = headerMatcher.safeRegEx().matches(value);
|
||||||
} else if (headerMatcher.range() != null) {
|
} else if (headerMatcher.range() != null) {
|
||||||
long numValue;
|
long numValue;
|
||||||
try {
|
try {
|
||||||
numValue = Long.parseLong(valueStr);
|
numValue = Long.parseLong(value);
|
||||||
baseMatch = numValue >= headerMatcher.range().start()
|
baseMatch = numValue >= headerMatcher.range().start()
|
||||||
&& numValue <= headerMatcher.range().end();
|
&& numValue <= headerMatcher.range().end();
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
baseMatch = false;
|
baseMatch = false;
|
||||||
}
|
}
|
||||||
} else if (headerMatcher.prefix() != null) {
|
} else if (headerMatcher.prefix() != null) {
|
||||||
baseMatch = valueStr.startsWith(headerMatcher.prefix());
|
baseMatch = value.startsWith(headerMatcher.prefix());
|
||||||
} else {
|
} else {
|
||||||
baseMatch = valueStr.endsWith(headerMatcher.suffix());
|
baseMatch = value.endsWith(headerMatcher.suffix());
|
||||||
}
|
}
|
||||||
return baseMatch != headerMatcher.inverted();
|
return baseMatch != headerMatcher.inverted();
|
||||||
}
|
}
|
||||||
|
|
@ -1033,7 +1067,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grouping of the list of usable routes and their corresponding fallback timeout value.
|
* VirtualHost-level configuration for request routing.
|
||||||
*/
|
*/
|
||||||
private static class RoutingConfig {
|
private static class RoutingConfig {
|
||||||
private final long fallbackTimeoutNano;
|
private final long fallbackTimeoutNano;
|
||||||
|
|
@ -1042,7 +1076,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
@Nullable
|
@Nullable
|
||||||
private final HttpFault faultConfig;
|
private final HttpFault faultConfig;
|
||||||
|
|
||||||
private static RoutingConfig empty =
|
private static final RoutingConfig empty =
|
||||||
new RoutingConfig(0L, Collections.<Route>emptyList(), false, null);
|
new RoutingConfig(0L, Collections.<Route>emptyList(), false, null);
|
||||||
|
|
||||||
private RoutingConfig(
|
private RoutingConfig(
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,11 @@ public class WeightedRandomPickerTest {
|
||||||
assertThat(nextInt).isLessThan(bound);
|
assertThat(nextInt).isLessThan(bound);
|
||||||
return nextInt;
|
return nextInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long nextLong() {
|
||||||
|
throw new UnsupportedOperationException("Should not be called");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final FakeRandom fakeRandom = new FakeRandom();
|
private final FakeRandom fakeRandom = new FakeRandom();
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,14 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.re2j.Pattern;
|
import com.google.re2j.Pattern;
|
||||||
import io.grpc.CallOptions;
|
import io.grpc.CallOptions;
|
||||||
import io.grpc.Channel;
|
import io.grpc.Channel;
|
||||||
import io.grpc.ClientCall;
|
import io.grpc.ClientCall;
|
||||||
import io.grpc.ClientInterceptor;
|
import io.grpc.ClientInterceptor;
|
||||||
|
import io.grpc.ClientInterceptors;
|
||||||
import io.grpc.InternalConfigSelector;
|
import io.grpc.InternalConfigSelector;
|
||||||
import io.grpc.InternalConfigSelector.Result;
|
import io.grpc.InternalConfigSelector.Result;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
|
|
@ -133,11 +135,13 @@ public class XdsNameResolverTest {
|
||||||
ArgumentCaptor<Status> errorCaptor;
|
ArgumentCaptor<Status> errorCaptor;
|
||||||
private XdsNameResolver resolver;
|
private XdsNameResolver resolver;
|
||||||
private TestCall<?, ?> testCall;
|
private TestCall<?, ?> testCall;
|
||||||
private boolean originalEnableTimeout = XdsNameResolver.enableTimeout;
|
private boolean originalEnableTimeout;
|
||||||
private AtomicLong originalFaultCounter = XdsNameResolver.activeFaultInjectedStreamCounter;
|
private AtomicLong originalFaultCounter;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
originalEnableTimeout = XdsNameResolver.enableTimeout;
|
||||||
|
originalFaultCounter = XdsNameResolver.activeFaultInjectedStreamCounter;
|
||||||
XdsNameResolver.enableTimeout = true;
|
XdsNameResolver.enableTimeout = true;
|
||||||
XdsNameResolver.activeFaultInjectedStreamCounter = new AtomicLong();
|
XdsNameResolver.activeFaultInjectedStreamCounter = new AtomicLong();
|
||||||
resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, scheduler,
|
resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, scheduler,
|
||||||
|
|
@ -331,6 +335,100 @@ public class XdsNameResolverTest {
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolved_rpcHashingByHeader() {
|
||||||
|
resolver.start(mockListener);
|
||||||
|
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
||||||
|
xdsClient.deliverLdsUpdate(
|
||||||
|
AUTHORITY,
|
||||||
|
Collections.singletonList(
|
||||||
|
Route.create(
|
||||||
|
RouteMatch.withPathExactOnly(
|
||||||
|
"/" + TestMethodDescriptors.voidMethod().getFullMethodName()),
|
||||||
|
RouteAction.forCluster(cluster1, Collections.singletonList(HashPolicy.forHeader(
|
||||||
|
false, "custom-key", Pattern.compile("value"), "val")),
|
||||||
|
null), null)));
|
||||||
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
|
InternalConfigSelector configSelector =
|
||||||
|
resolutionResultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY);
|
||||||
|
|
||||||
|
// First call, with header "custom-key": "custom-value".
|
||||||
|
startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
ImmutableMap.of("custom-key", "custom-value"), CallOptions.DEFAULT);
|
||||||
|
long hash1 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY);
|
||||||
|
|
||||||
|
// Second call, with header "custom-key": "custom-val", "another-key": "another-value".
|
||||||
|
startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
ImmutableMap.of("custom-key", "custom-val", "another-key", "another-value"),
|
||||||
|
CallOptions.DEFAULT);
|
||||||
|
long hash2 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY);
|
||||||
|
|
||||||
|
// Third call, with header "custom-key": "value".
|
||||||
|
startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
ImmutableMap.of("custom-key", "value"), CallOptions.DEFAULT);
|
||||||
|
long hash3 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY);
|
||||||
|
|
||||||
|
assertThat(hash2).isEqualTo(hash1);
|
||||||
|
assertThat(hash3).isNotEqualTo(hash1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolved_rpcHashingByChannelId() {
|
||||||
|
resolver.start(mockListener);
|
||||||
|
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
||||||
|
xdsClient.deliverLdsUpdate(
|
||||||
|
AUTHORITY,
|
||||||
|
Collections.singletonList(
|
||||||
|
Route.create(
|
||||||
|
RouteMatch.withPathExactOnly(
|
||||||
|
"/" + TestMethodDescriptors.voidMethod().getFullMethodName()),
|
||||||
|
RouteAction.forCluster(cluster1, Collections.singletonList(
|
||||||
|
HashPolicy.forChannelId(false)), null), null)));
|
||||||
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
|
InternalConfigSelector configSelector =
|
||||||
|
resolutionResultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY);
|
||||||
|
|
||||||
|
// First call, with header "custom-key": "value1".
|
||||||
|
startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
ImmutableMap.of("custom-key", "value1"),
|
||||||
|
CallOptions.DEFAULT);
|
||||||
|
long hash1 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY);
|
||||||
|
|
||||||
|
// Second call, with no custom header.
|
||||||
|
startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(),
|
||||||
|
CallOptions.DEFAULT);
|
||||||
|
long hash2 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY);
|
||||||
|
|
||||||
|
// A different resolver/Channel.
|
||||||
|
resolver.shutdown();
|
||||||
|
reset(mockListener);
|
||||||
|
resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, scheduler,
|
||||||
|
xdsClientPoolFactory, mockRandom);
|
||||||
|
resolver.start(mockListener);
|
||||||
|
xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
||||||
|
xdsClient.deliverLdsUpdate(
|
||||||
|
AUTHORITY,
|
||||||
|
Collections.singletonList(
|
||||||
|
Route.create(
|
||||||
|
RouteMatch.withPathExactOnly(
|
||||||
|
"/" + TestMethodDescriptors.voidMethod().getFullMethodName()),
|
||||||
|
RouteAction.forCluster(cluster1, Collections.singletonList(
|
||||||
|
HashPolicy.forChannelId(false)), null), null)));
|
||||||
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
|
configSelector = resolutionResultCaptor.getValue().getAttributes().get(
|
||||||
|
InternalConfigSelector.KEY);
|
||||||
|
|
||||||
|
// Third call, with no custom header.
|
||||||
|
startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(),
|
||||||
|
CallOptions.DEFAULT);
|
||||||
|
long hash3 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY);
|
||||||
|
|
||||||
|
assertThat(hash2).isEqualTo(hash1);
|
||||||
|
assertThat(hash3).isNotEqualTo(hash1);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Test
|
@Test
|
||||||
public void resolved_resourceUpdateAfterCallStarted() {
|
public void resolved_resourceUpdateAfterCallStarted() {
|
||||||
|
|
@ -747,29 +845,26 @@ public class XdsNameResolverTest {
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
// no header abort key provided in metadata, rpc should succeed
|
// no header abort key provided in metadata, rpc should succeed
|
||||||
Metadata metadata = new Metadata();
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, metadata);
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
// header abort http status key provided, rpc should fail
|
// header abort http status key provided, rpc should fail
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_ABORT_HTTP_STATUS_KEY, "404");
|
ImmutableMap.of(HEADER_ABORT_HTTP_STATUS_KEY.name(), "404",
|
||||||
metadata.put(HEADER_ABORT_PERCENTAGE_KEY, "60");
|
HEADER_ABORT_PERCENTAGE_KEY.name(), "60"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcFailed(observer, Status.UNIMPLEMENTED.withDescription("HTTP status code 404"));
|
verifyRpcFailed(observer, Status.UNIMPLEMENTED.withDescription("HTTP status code 404"));
|
||||||
// header abort grpc status key provided, rpc should fail
|
// header abort grpc status key provided, rpc should fail
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_ABORT_GRPC_STATUS_KEY, String.valueOf(
|
ImmutableMap.of(HEADER_ABORT_GRPC_STATUS_KEY.name(),
|
||||||
Status.UNAUTHENTICATED.getCode().value()));
|
String.valueOf(Status.UNAUTHENTICATED.getCode().value()),
|
||||||
metadata.put(HEADER_ABORT_PERCENTAGE_KEY, "60");
|
HEADER_ABORT_PERCENTAGE_KEY.name(), "60"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcFailed(observer, Status.UNAUTHENTICATED);
|
verifyRpcFailed(observer, Status.UNAUTHENTICATED);
|
||||||
// header abort, both http and grpc code keys provided, rpc should fail with http code
|
// header abort, both http and grpc code keys provided, rpc should fail with http code
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_ABORT_HTTP_STATUS_KEY, "404");
|
ImmutableMap.of(HEADER_ABORT_HTTP_STATUS_KEY.name(), "404",
|
||||||
metadata.put(HEADER_ABORT_GRPC_STATUS_KEY, String.valueOf(
|
HEADER_ABORT_GRPC_STATUS_KEY.name(),
|
||||||
Status.UNAUTHENTICATED.getCode().value()));
|
String.valueOf(Status.UNAUTHENTICATED.getCode().value()),
|
||||||
metadata.put(HEADER_ABORT_PERCENTAGE_KEY, "60");
|
HEADER_ABORT_PERCENTAGE_KEY.name(), "60"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcFailed(observer, Status.UNIMPLEMENTED.withDescription("HTTP status code 404"));
|
verifyRpcFailed(observer, Status.UNIMPLEMENTED.withDescription("HTTP status code 404"));
|
||||||
|
|
||||||
// header abort, no header rate, fix rate = 60 %
|
// header abort, no header rate, fix rate = 60 %
|
||||||
|
|
@ -784,9 +879,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_ABORT_HTTP_STATUS_KEY, "404");
|
ImmutableMap.of(HEADER_ABORT_HTTP_STATUS_KEY.name(), "404"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcFailed(observer, Status.UNIMPLEMENTED.withDescription("HTTP status code 404"));
|
verifyRpcFailed(observer, Status.UNIMPLEMENTED.withDescription("HTTP status code 404"));
|
||||||
|
|
||||||
// header abort, no header rate, fix rate = 0
|
// header abort, no header rate, fix rate = 0
|
||||||
|
|
@ -801,9 +895,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_ABORT_HTTP_STATUS_KEY, "404");
|
ImmutableMap.of(HEADER_ABORT_HTTP_STATUS_KEY.name(), "404"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
|
|
||||||
// fixed abort, fix rate = 60%
|
// fixed abort, fix rate = 60%
|
||||||
|
|
@ -820,7 +913,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcFailed(observer, Status.UNAUTHENTICATED.withDescription("unauthenticated"));
|
verifyRpcFailed(observer, Status.UNAUTHENTICATED.withDescription("unauthenticated"));
|
||||||
|
|
||||||
// fixed abort, fix rate = 40%
|
// fixed abort, fix rate = 40%
|
||||||
|
|
@ -837,7 +931,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -860,14 +955,13 @@ public class XdsNameResolverTest {
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
// no header delay key provided in metadata, rpc should succeed immediately
|
// no header delay key provided in metadata, rpc should succeed immediately
|
||||||
Metadata metadata = new Metadata();
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, metadata);
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
// header delay key provided, rpc should be delayed
|
// header delay key provided, rpc should be delayed
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_DELAY_KEY, "1000");
|
ImmutableMap.of(HEADER_DELAY_KEY.name(), "1000", HEADER_DELAY_PERCENTAGE_KEY.name(), "60"),
|
||||||
metadata.put(HEADER_DELAY_PERCENTAGE_KEY, "60");
|
CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcDelayed(observer, TimeUnit.MILLISECONDS.toNanos(1000));
|
verifyRpcDelayed(observer, TimeUnit.MILLISECONDS.toNanos(1000));
|
||||||
|
|
||||||
// header delay, no header rate, fix rate = 60 %
|
// header delay, no header rate, fix rate = 60 %
|
||||||
|
|
@ -882,9 +976,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_DELAY_KEY, "1000");
|
ImmutableMap.of(HEADER_DELAY_KEY.name(), "1000"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcDelayed(observer, TimeUnit.MILLISECONDS.toNanos(1000));
|
verifyRpcDelayed(observer, TimeUnit.MILLISECONDS.toNanos(1000));
|
||||||
|
|
||||||
// header delay, no header rate, fix rate = 0
|
// header delay, no header rate, fix rate = 0
|
||||||
|
|
@ -899,9 +992,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(HEADER_DELAY_KEY, "1000");
|
ImmutableMap.of(HEADER_DELAY_KEY.name(), "1000"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
|
|
||||||
// fixed delay, fix rate = 60%
|
// fixed delay, fix rate = 60%
|
||||||
|
|
@ -916,7 +1008,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcDelayed(observer, 5000L);
|
verifyRpcDelayed(observer, 5000L);
|
||||||
|
|
||||||
// fixed delay, fix rate = 40%
|
// fixed delay, fix rate = 40%
|
||||||
|
|
@ -931,7 +1024,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -955,7 +1049,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, new Metadata());
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -979,9 +1074,9 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
Metadata metadata = new Metadata();
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
metadata.put(DOWNSTREAM_NODE_KEY, "node1.example.com");
|
configSelector, ImmutableMap.of(DOWNSTREAM_NODE_KEY.name(), "node1.example.com"),
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, metadata);
|
CallOptions.DEFAULT);
|
||||||
verifyRpcFailed(observer, Status.UNAUTHENTICATED.withDescription("unauthenticated"));
|
verifyRpcFailed(observer, Status.UNAUTHENTICATED.withDescription("unauthenticated"));
|
||||||
|
|
||||||
// downstream node mismatch
|
// downstream node mismatch
|
||||||
|
|
@ -998,9 +1093,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(DOWNSTREAM_NODE_KEY, "node2.example.com");
|
ImmutableMap.of(DOWNSTREAM_NODE_KEY.name(), "node2.example.com"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
|
|
||||||
// downstream node absent in headers
|
// downstream node absent in headers
|
||||||
|
|
@ -1017,7 +1111,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1026,7 +1121,6 @@ public class XdsNameResolverTest {
|
||||||
resolver.start(mockListener);
|
resolver.start(mockListener);
|
||||||
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
||||||
when(mockRandom.nextInt(1000_000)).thenReturn(500_000); // 50%
|
when(mockRandom.nextInt(1000_000)).thenReturn(500_000); // 50%
|
||||||
Metadata.Key<String> faultKey = Metadata.Key.of("fault_key", Metadata.ASCII_STRING_MARSHALLER);
|
|
||||||
|
|
||||||
// headers match
|
// headers match
|
||||||
HttpFault httpFilterFaultConfig = HttpFault.create(
|
HttpFault httpFilterFaultConfig = HttpFault.create(
|
||||||
|
|
@ -1041,9 +1135,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
Metadata metadata = new Metadata();
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
metadata.put(faultKey, "fault_value");
|
configSelector, ImmutableMap.of("fault_key", "fault_value"), CallOptions.DEFAULT);
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcFailed(observer, Status.UNAUTHENTICATED);
|
verifyRpcFailed(observer, Status.UNAUTHENTICATED);
|
||||||
|
|
||||||
// headers mismatch
|
// headers mismatch
|
||||||
|
|
@ -1060,9 +1153,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
metadata = new Metadata();
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
metadata.put(faultKey, "value_not_match");
|
ImmutableMap.of("fault_key", "value_not_match"), CallOptions.DEFAULT);
|
||||||
observer = startCall(configSelector, metadata);
|
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
|
|
||||||
// headers absent
|
// headers absent
|
||||||
|
|
@ -1079,7 +1171,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer);
|
verifyRpcSucceeded(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1103,13 +1196,16 @@ public class XdsNameResolverTest {
|
||||||
|
|
||||||
// Send two calls, then the first call should delayed and the second call should not be delayed
|
// Send two calls, then the first call should delayed and the second call should not be delayed
|
||||||
// because maxActiveFaults is exceeded.
|
// because maxActiveFaults is exceeded.
|
||||||
ClientCall.Listener<Void> observer1 = startCall(configSelector, new Metadata());
|
ClientCall.Listener<Void> observer1 = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
assertThat(testCall).isNull();
|
assertThat(testCall).isNull();
|
||||||
ClientCall.Listener<Void> observer2 = startCall(configSelector, new Metadata());
|
ClientCall.Listener<Void> observer2 = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcSucceeded(observer2);
|
verifyRpcSucceeded(observer2);
|
||||||
verifyRpcDelayed(observer1, 5000L);
|
verifyRpcDelayed(observer1, 5000L);
|
||||||
// Once all calls are finished, new call should be delayed.
|
// Once all calls are finished, new call should be delayed.
|
||||||
ClientCall.Listener<Void> observer3 = startCall(configSelector, new Metadata());
|
ClientCall.Listener<Void> observer3 = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcDelayed(observer3, 5000L);
|
verifyRpcDelayed(observer3, 5000L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1132,7 +1228,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, new Metadata());
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcDelayedThenAborted(
|
verifyRpcDelayedThenAborted(
|
||||||
observer, 5000L, Status.UNAUTHENTICATED.withDescription("unauthenticated"));
|
observer, 5000L, Status.UNAUTHENTICATED.withDescription("unauthenticated"));
|
||||||
}
|
}
|
||||||
|
|
@ -1164,7 +1261,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, new Metadata());
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcFailed(observer, Status.INTERNAL);
|
verifyRpcFailed(observer, Status.INTERNAL);
|
||||||
|
|
||||||
// Route fault config override
|
// Route fault config override
|
||||||
|
|
@ -1180,7 +1278,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcFailed(observer, Status.UNKNOWN);
|
verifyRpcFailed(observer, Status.UNKNOWN);
|
||||||
|
|
||||||
// WeightedCluster fault config override
|
// WeightedCluster fault config override
|
||||||
|
|
@ -1197,7 +1296,8 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
result = resolutionResultCaptor.getValue();
|
result = resolutionResultCaptor.getValue();
|
||||||
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
observer = startCall(configSelector, new Metadata());
|
observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector,
|
||||||
|
Collections.<String, String>emptyMap(), CallOptions.DEFAULT);
|
||||||
verifyRpcFailed(observer, Status.UNAVAILABLE);
|
verifyRpcFailed(observer, Status.UNAVAILABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1230,28 +1330,26 @@ public class XdsNameResolverTest {
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY);
|
||||||
ClientCall.Listener<Void> observer = startCall(configSelector, new Metadata());
|
ClientCall.Listener<Void> observer = startNewCall(TestMethodDescriptors.voidMethod(),
|
||||||
|
configSelector, Collections.<String, String>emptyMap(), CallOptions.DEFAULT);;
|
||||||
verifyRpcFailed(observer, Status.UNKNOWN);
|
verifyRpcFailed(observer, Status.UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientCall.Listener<Void> startCall(
|
private <ReqT, RespT> ClientCall.Listener<RespT> startNewCall(
|
||||||
InternalConfigSelector configSelector,
|
MethodDescriptor<ReqT, RespT> method, InternalConfigSelector selector,
|
||||||
Metadata metadata) {
|
Map<String, String> headers, CallOptions callOptions) {
|
||||||
testCall = null;
|
Metadata metadata = new Metadata();
|
||||||
MethodDescriptor<Void, Void> method = TestMethodDescriptors.voidMethod();
|
for (String key : headers.keySet()) {
|
||||||
Result result = configSelector.selectConfig(
|
metadata.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), headers.get(key));
|
||||||
new PickSubchannelArgsImpl(method, metadata, CallOptions.DEFAULT));
|
}
|
||||||
assertThat(result.getStatus().isOk()).isTrue();
|
|
||||||
ClientInterceptor interceptor = result.getInterceptor();
|
|
||||||
ClientCall<Void, Void> clientCall = interceptor.interceptCall(
|
|
||||||
method, CallOptions.DEFAULT, channel);
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
ClientCall.Listener<Void> observer =
|
ClientCall.Listener<RespT> listener = mock(ClientCall.Listener.class);
|
||||||
(ClientCall.Listener<Void>) mock(ClientCall.Listener.class);
|
Result result = selector.selectConfig(new PickSubchannelArgsImpl(
|
||||||
clientCall.start(observer, metadata);
|
method, metadata, callOptions));
|
||||||
clientCall.sendMessage(null);
|
ClientCall<ReqT, RespT> call = ClientInterceptors.intercept(channel,
|
||||||
clientCall.halfClose();
|
result.getInterceptor()).newCall(method, callOptions);
|
||||||
return observer;
|
call.start(listener, metadata);
|
||||||
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyRpcSucceeded(ClientCall.Listener<Void> observer) {
|
private void verifyRpcSucceeded(ClientCall.Listener<Void> observer) {
|
||||||
|
|
@ -1287,7 +1385,7 @@ public class XdsNameResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void routeMatching_pathOnly() {
|
public void routeMatching_pathOnly() {
|
||||||
Map<String, Iterable<String>> headers = Collections.emptyMap();
|
Map<String, String> headers = Collections.emptyMap();
|
||||||
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
|
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
|
||||||
|
|
||||||
RouteMatch routeMatch1 =
|
RouteMatch routeMatch1 =
|
||||||
|
|
@ -1320,12 +1418,12 @@ public class XdsNameResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void routeMatching_withHeaders() {
|
public void routeMatching_withHeaders() {
|
||||||
Map<String, Iterable<String>> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put("authority", Collections.singletonList("foo.googleapis.com"));
|
headers.put("authority", "foo.googleapis.com");
|
||||||
headers.put("grpc-encoding", Collections.singletonList("gzip"));
|
headers.put("grpc-encoding", "gzip");
|
||||||
headers.put("user-agent", Collections.singletonList("gRPC-Java"));
|
headers.put("user-agent", "gRPC-Java");
|
||||||
headers.put("content-length", Collections.singletonList("1000"));
|
headers.put("content-length", "1000");
|
||||||
headers.put("custom-key", Arrays.asList("custom-value1", "custom-value2"));
|
headers.put("custom-key", "custom-value1,custom-value2");
|
||||||
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
|
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
|
||||||
|
|
||||||
PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true);
|
PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue