1 /* 2 * Copyright 2022 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.testing.integration; 18 19 import io.grpc.ConnectivityState; 20 import io.grpc.LoadBalancer; 21 import io.grpc.LoadBalancer.Helper; 22 import io.grpc.LoadBalancer.PickResult; 23 import io.grpc.LoadBalancer.PickSubchannelArgs; 24 import io.grpc.LoadBalancer.SubchannelPicker; 25 import io.grpc.LoadBalancerProvider; 26 import io.grpc.LoadBalancerRegistry; 27 import io.grpc.Metadata; 28 import io.grpc.NameResolver.ConfigOrError; 29 import io.grpc.Status; 30 import io.grpc.internal.JsonUtil; 31 import io.grpc.util.ForwardingLoadBalancer; 32 import io.grpc.util.ForwardingLoadBalancerHelper; 33 import java.util.Map; 34 import javax.annotation.Nonnull; 35 36 /** 37 * Provides a xDS interop test {@link LoadBalancer} designed to work with {@link XdsTestServer}. It 38 * looks for an "rpc_behavior" field in its configuration and includes the value in the 39 * "rpc-behavior" metadata entry that is sent to the server. This will cause the test server to 40 * behave in a predefined way. Endpoint picking logic is delegated to the 41 * io.grpc.util.RoundRobinLoadBalancer. 42 * 43 * <p>Initial use case is to prove that a custom load balancer can be configured by the control 44 * plane via xDS. An interop test will configure this LB and then verify it has been correctly 45 * configured by observing a specific RPC behavior by the server(s). 46 * 47 * <p>For more details on what behaviors can be specified, please see: 48 * https://github.com/grpc/grpc/blob/master/doc/xds-test-descriptions.md#server 49 */ 50 public class RpcBehaviorLoadBalancerProvider extends LoadBalancerProvider { 51 52 @Override parseLoadBalancingPolicyConfig(Map<String, ?> rawLoadBalancingPolicyConfig)53 public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawLoadBalancingPolicyConfig) { 54 String rpcBehavior = JsonUtil.getString(rawLoadBalancingPolicyConfig, "rpcBehavior"); 55 if (rpcBehavior == null) { 56 return ConfigOrError.fromError( 57 Status.UNAVAILABLE.withDescription("no 'rpcBehavior' defined")); 58 } 59 return ConfigOrError.fromConfig(new RpcBehaviorConfig(rpcBehavior)); 60 } 61 62 @Override newLoadBalancer(Helper helper)63 public LoadBalancer newLoadBalancer(Helper helper) { 64 RpcBehaviorHelper rpcBehaviorHelper = new RpcBehaviorHelper(helper); 65 return new RpcBehaviorLoadBalancer(rpcBehaviorHelper, 66 LoadBalancerRegistry.getDefaultRegistry().getProvider("round_robin") 67 .newLoadBalancer(rpcBehaviorHelper)); 68 } 69 70 @Override isAvailable()71 public boolean isAvailable() { 72 return true; 73 } 74 75 @Override getPriority()76 public int getPriority() { 77 return 5; 78 } 79 80 @Override getPolicyName()81 public String getPolicyName() { 82 return "test.RpcBehaviorLoadBalancer"; 83 } 84 85 static class RpcBehaviorConfig { 86 87 final String rpcBehavior; 88 RpcBehaviorConfig(String rpcBehavior)89 RpcBehaviorConfig(String rpcBehavior) { 90 this.rpcBehavior = rpcBehavior; 91 } 92 } 93 94 /** 95 * Delegates all calls to another LB and wraps the given helper in {@link RpcBehaviorHelper} that 96 * assures that the rpc-behavior metadata header gets added to all calls. 97 */ 98 static class RpcBehaviorLoadBalancer extends ForwardingLoadBalancer { 99 100 private final RpcBehaviorHelper helper; 101 private final LoadBalancer delegateLb; 102 RpcBehaviorLoadBalancer(RpcBehaviorHelper helper, LoadBalancer delegateLb)103 RpcBehaviorLoadBalancer(RpcBehaviorHelper helper, LoadBalancer delegateLb) { 104 this.helper = helper; 105 this.delegateLb = delegateLb; 106 } 107 108 @Override delegate()109 protected LoadBalancer delegate() { 110 return delegateLb; 111 } 112 113 @Override handleResolvedAddresses(ResolvedAddresses resolvedAddresses)114 public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { 115 helper.setRpcBehavior( 116 ((RpcBehaviorConfig) resolvedAddresses.getLoadBalancingPolicyConfig()).rpcBehavior); 117 delegateLb.handleResolvedAddresses(resolvedAddresses); 118 } 119 } 120 121 /** 122 * Wraps the picker that is provided when the balancing change updates with the {@link 123 * RpcBehaviorPicker} that injects the rpc-behavior metadata entry. 124 */ 125 static class RpcBehaviorHelper extends ForwardingLoadBalancerHelper { 126 127 private final Helper delegateHelper; 128 private String rpcBehavior; 129 RpcBehaviorHelper(Helper delegateHelper)130 RpcBehaviorHelper(Helper delegateHelper) { 131 this.delegateHelper = delegateHelper; 132 } 133 setRpcBehavior(String rpcBehavior)134 void setRpcBehavior(String rpcBehavior) { 135 this.rpcBehavior = rpcBehavior; 136 } 137 138 @Override delegate()139 protected Helper delegate() { 140 return delegateHelper; 141 } 142 143 @Override updateBalancingState(@onnull ConnectivityState newState, @Nonnull SubchannelPicker newPicker)144 public void updateBalancingState(@Nonnull ConnectivityState newState, 145 @Nonnull SubchannelPicker newPicker) { 146 delegateHelper.updateBalancingState(newState, new RpcBehaviorPicker(newPicker, rpcBehavior)); 147 } 148 } 149 150 /** 151 * Includes the rpc-behavior metadata entry on each subchannel pick. 152 */ 153 static class RpcBehaviorPicker extends SubchannelPicker { 154 155 private static final String RPC_BEHAVIOR_HEADER_KEY = "rpc-behavior"; 156 157 private final SubchannelPicker delegatePicker; 158 private final String rpcBehavior; 159 RpcBehaviorPicker(SubchannelPicker delegatePicker, String rpcBehavior)160 RpcBehaviorPicker(SubchannelPicker delegatePicker, String rpcBehavior) { 161 this.delegatePicker = delegatePicker; 162 this.rpcBehavior = rpcBehavior; 163 } 164 165 @Override pickSubchannel(PickSubchannelArgs args)166 public PickResult pickSubchannel(PickSubchannelArgs args) { 167 args.getHeaders() 168 .put(Metadata.Key.of(RPC_BEHAVIOR_HEADER_KEY, Metadata.ASCII_STRING_MARSHALLER), 169 rpcBehavior); 170 return delegatePicker.pickSubchannel(args); 171 } 172 } 173 } 174