mirror of https://github.com/grpc/grpc-java.git
interop-testing: LB that adds the rpc-behavior to calls (#9186)
This commit is contained in:
parent
5bb721e217
commit
4a5f6adf73
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright 2022 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.testing.integration;
|
||||
|
||||
import io.grpc.ConnectivityState;
|
||||
import io.grpc.LoadBalancer;
|
||||
import io.grpc.LoadBalancer.Helper;
|
||||
import io.grpc.LoadBalancer.PickResult;
|
||||
import io.grpc.LoadBalancer.PickSubchannelArgs;
|
||||
import io.grpc.LoadBalancer.SubchannelPicker;
|
||||
import io.grpc.LoadBalancerProvider;
|
||||
import io.grpc.LoadBalancerRegistry;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.NameResolver.ConfigOrError;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.internal.JsonUtil;
|
||||
import io.grpc.util.ForwardingLoadBalancer;
|
||||
import io.grpc.util.ForwardingLoadBalancerHelper;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Provides a xDS interop test {@link LoadBalancer} designed to work with {@link XdsTestServer}. It
|
||||
* looks for an "rpc_behavior" field in its configuration and includes the value in the
|
||||
* "rpc-behavior" metadata entry that is sent to the server. This will cause the test server to
|
||||
* behave in a predefined way. Endpoint picking logic is delegated to the
|
||||
* {@link PickFirstLoadBalancer}.
|
||||
*
|
||||
* <p>Initial use case is to prove that a custom load balancer can be configured by the control
|
||||
* plane via xDS. An interop test will configure this LB and then verify it has been correctly
|
||||
* configured by observing a specific RPC behavior by the server(s).
|
||||
*
|
||||
* <p>For more details on what behaviors can be specified, please see:
|
||||
* https://github.com/grpc/grpc/blob/master/doc/xds-test-descriptions.md#server
|
||||
*/
|
||||
public class RpcBehaviorLoadBalancerProvider extends LoadBalancerProvider {
|
||||
|
||||
@Override
|
||||
public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawLoadBalancingPolicyConfig) {
|
||||
String rpcBehavior = JsonUtil.getString(rawLoadBalancingPolicyConfig, "rpcBehavior");
|
||||
if (rpcBehavior == null) {
|
||||
return ConfigOrError.fromError(
|
||||
Status.INVALID_ARGUMENT.withDescription("no 'rpcBehavior' defined"));
|
||||
}
|
||||
return ConfigOrError.fromConfig(new RpcBehaviorConfig(rpcBehavior));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
||||
return new RpcBehaviorLoadBalancer(helper,
|
||||
LoadBalancerRegistry.getDefaultRegistry().getProvider("round_robin")
|
||||
.newLoadBalancer(helper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPolicyName() {
|
||||
return "test.RpcBehaviorLoadBalancer";
|
||||
}
|
||||
|
||||
static class RpcBehaviorConfig {
|
||||
|
||||
final String rpcBehavior;
|
||||
|
||||
RpcBehaviorConfig(String rpcBehavior) {
|
||||
this.rpcBehavior = rpcBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates all calls to another LB and wraps the given helper in {@link RpcBehaviorHelper} that
|
||||
* assures that the rpc-behavior metadata header gets added to all calls.
|
||||
*/
|
||||
static class RpcBehaviorLoadBalancer extends ForwardingLoadBalancer {
|
||||
|
||||
private final RpcBehaviorHelper helper;
|
||||
private final LoadBalancer delegateLb;
|
||||
|
||||
RpcBehaviorLoadBalancer(Helper helper, LoadBalancer delegateLb) {
|
||||
this.helper = new RpcBehaviorHelper(helper);
|
||||
this.delegateLb = delegateLb;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LoadBalancer delegate() {
|
||||
return delegateLb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
|
||||
helper.setRpcBehavior(
|
||||
((RpcBehaviorConfig) resolvedAddresses.getLoadBalancingPolicyConfig()).rpcBehavior);
|
||||
delegateLb.handleResolvedAddresses(resolvedAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the picker that is provided when the balancing change updates with the {@link
|
||||
* RpcBehaviorPicker} that injects the rpc-behavior metadata entry.
|
||||
*/
|
||||
static class RpcBehaviorHelper extends ForwardingLoadBalancerHelper {
|
||||
|
||||
private final Helper delegateHelper;
|
||||
private String rpcBehavior;
|
||||
|
||||
RpcBehaviorHelper(Helper delegateHelper) {
|
||||
this.delegateHelper = delegateHelper;
|
||||
}
|
||||
|
||||
void setRpcBehavior(String rpcBehavior) {
|
||||
this.rpcBehavior = rpcBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Helper delegate() {
|
||||
return delegateHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBalancingState(@Nonnull ConnectivityState newState,
|
||||
@Nonnull SubchannelPicker newPicker) {
|
||||
delegateHelper.updateBalancingState(newState, new RpcBehaviorPicker(newPicker, rpcBehavior));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes the rpc-behavior metadata entry on each subchannel pick.
|
||||
*/
|
||||
static class RpcBehaviorPicker extends SubchannelPicker {
|
||||
|
||||
private static final String RPC_BEHAVIOR_HEADER_KEY = "rpc-behavior";
|
||||
|
||||
private final SubchannelPicker delegatePicker;
|
||||
private final String rpcBehavior;
|
||||
|
||||
RpcBehaviorPicker(SubchannelPicker delegatePicker, String rpcBehavior) {
|
||||
this.delegatePicker = delegatePicker;
|
||||
this.rpcBehavior = rpcBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickResult pickSubchannel(PickSubchannelArgs args) {
|
||||
args.getHeaders()
|
||||
.put(Metadata.Key.of(RPC_BEHAVIOR_HEADER_KEY, Metadata.ASCII_STRING_MARSHALLER),
|
||||
rpcBehavior);
|
||||
return delegatePicker.pickSubchannel(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
io.grpc.testing.integration.RpcBehaviorLoadBalancerProvider
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2022 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.testing.integration;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.ConnectivityState;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.LoadBalancer;
|
||||
import io.grpc.LoadBalancer.Helper;
|
||||
import io.grpc.LoadBalancer.ResolvedAddresses;
|
||||
import io.grpc.LoadBalancer.SubchannelPicker;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.internal.PickSubchannelArgsImpl;
|
||||
import io.grpc.testing.TestMethodDescriptors;
|
||||
import io.grpc.testing.integration.RpcBehaviorLoadBalancerProvider.RpcBehaviorConfig;
|
||||
import io.grpc.testing.integration.RpcBehaviorLoadBalancerProvider.RpcBehaviorHelper;
|
||||
import io.grpc.testing.integration.RpcBehaviorLoadBalancerProvider.RpcBehaviorLoadBalancer;
|
||||
import io.grpc.testing.integration.RpcBehaviorLoadBalancerProvider.RpcBehaviorPicker;
|
||||
import java.net.SocketAddress;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RpcBehaviorLoadBalancerProvider}.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class RpcBehaviorLoadBalancerProviderTest {
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private LoadBalancer mockDelegateLb;
|
||||
|
||||
@Mock
|
||||
private Helper mockHelper;
|
||||
|
||||
@Mock
|
||||
private SubchannelPicker mockPicker;
|
||||
|
||||
@Test
|
||||
public void parseValidConfig() {
|
||||
assertThat(buildConfig().rpcBehavior).isEqualTo("error-code-15");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInvalidConfig() {
|
||||
Status status = new RpcBehaviorLoadBalancerProvider().parseLoadBalancingPolicyConfig(
|
||||
ImmutableMap.of("foo", "bar")).getError();
|
||||
assertThat(status.getDescription()).contains("rpcBehavior");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleResolvedAddressesDelegated() {
|
||||
RpcBehaviorLoadBalancer lb = new RpcBehaviorLoadBalancer(mockHelper, mockDelegateLb);
|
||||
ResolvedAddresses resolvedAddresses = buildResolvedAddresses(buildConfig());
|
||||
lb.handleResolvedAddresses(resolvedAddresses);
|
||||
verify(mockDelegateLb).handleResolvedAddresses(resolvedAddresses);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void helperWrapsPicker() {
|
||||
RpcBehaviorHelper helper = new RpcBehaviorHelper(mockHelper);
|
||||
helper.setRpcBehavior("error-code-15");
|
||||
helper.updateBalancingState(ConnectivityState.READY, mockPicker);
|
||||
|
||||
verify(mockHelper).updateBalancingState(eq(ConnectivityState.READY),
|
||||
isA(RpcBehaviorPicker.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pickerAddsRpcBehaviorMetadata() {
|
||||
PickSubchannelArgsImpl args = new PickSubchannelArgsImpl(TestMethodDescriptors.voidMethod(),
|
||||
new Metadata(), CallOptions.DEFAULT);
|
||||
new RpcBehaviorPicker(mockPicker, "error-code-15").pickSubchannel(args);
|
||||
|
||||
assertThat(args.getHeaders()
|
||||
.get(Metadata.Key.of("rpc-behavior", Metadata.ASCII_STRING_MARSHALLER))).isEqualTo(
|
||||
"error-code-15");
|
||||
}
|
||||
|
||||
private RpcBehaviorConfig buildConfig() {
|
||||
RpcBehaviorConfig config = (RpcBehaviorConfig) new RpcBehaviorLoadBalancerProvider()
|
||||
.parseLoadBalancingPolicyConfig(
|
||||
ImmutableMap.of("rpcBehavior", "error-code-15")).getConfig();
|
||||
return config;
|
||||
}
|
||||
|
||||
private ResolvedAddresses buildResolvedAddresses(RpcBehaviorConfig config) {
|
||||
ResolvedAddresses resolvedAddresses = ResolvedAddresses.newBuilder()
|
||||
.setLoadBalancingPolicyConfig(config)
|
||||
.setAddresses(ImmutableList.of(
|
||||
new EquivalentAddressGroup(new SocketAddress() {
|
||||
})))
|
||||
.setAttributes(Attributes.newBuilder().build()).build();
|
||||
return resolvedAddresses;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue