• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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