• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.internal;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.annotations.VisibleForTesting;
22 import com.google.common.base.MoreObjects;
23 import io.grpc.ChannelLogger.ChannelLogLevel;
24 import io.grpc.ConnectivityState;
25 import io.grpc.ConnectivityStateInfo;
26 import io.grpc.LoadBalancer;
27 import io.grpc.LoadBalancer.Helper;
28 import io.grpc.LoadBalancer.PickResult;
29 import io.grpc.LoadBalancer.PickSubchannelArgs;
30 import io.grpc.LoadBalancer.ResolvedAddresses;
31 import io.grpc.LoadBalancer.Subchannel;
32 import io.grpc.LoadBalancer.SubchannelPicker;
33 import io.grpc.LoadBalancerProvider;
34 import io.grpc.LoadBalancerRegistry;
35 import io.grpc.NameResolver.ConfigOrError;
36 import io.grpc.Status;
37 import io.grpc.internal.ServiceConfigUtil.LbConfig;
38 import io.grpc.internal.ServiceConfigUtil.PolicySelection;
39 import java.util.List;
40 import java.util.Map;
41 import javax.annotation.Nullable;
42 
43 // TODO(creamsoup) fully deprecate LoadBalancer.ATTR_LOAD_BALANCING_CONFIG
44 @SuppressWarnings("deprecation")
45 public final class AutoConfiguredLoadBalancerFactory {
46 
47   private final LoadBalancerRegistry registry;
48   private final String defaultPolicy;
49 
AutoConfiguredLoadBalancerFactory(String defaultPolicy)50   public AutoConfiguredLoadBalancerFactory(String defaultPolicy) {
51     this(LoadBalancerRegistry.getDefaultRegistry(), defaultPolicy);
52   }
53 
54   @VisibleForTesting
AutoConfiguredLoadBalancerFactory(LoadBalancerRegistry registry, String defaultPolicy)55   AutoConfiguredLoadBalancerFactory(LoadBalancerRegistry registry, String defaultPolicy) {
56     this.registry = checkNotNull(registry, "registry");
57     this.defaultPolicy = checkNotNull(defaultPolicy, "defaultPolicy");
58   }
59 
newLoadBalancer(Helper helper)60   public AutoConfiguredLoadBalancer newLoadBalancer(Helper helper) {
61     return new AutoConfiguredLoadBalancer(helper);
62   }
63 
64   private static final class NoopLoadBalancer extends LoadBalancer {
65 
66     @Override
67     @Deprecated
68     @SuppressWarnings("InlineMeSuggester")
handleResolvedAddresses(ResolvedAddresses resolvedAddresses)69     public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
70     }
71 
72     @Override
acceptResolvedAddresses(ResolvedAddresses resolvedAddresses)73     public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
74       return true;
75     }
76 
77     @Override
handleNameResolutionError(Status error)78     public void handleNameResolutionError(Status error) {}
79 
80     @Override
shutdown()81     public void shutdown() {}
82   }
83 
84   @VisibleForTesting
85   public final class AutoConfiguredLoadBalancer {
86     private final Helper helper;
87     private LoadBalancer delegate;
88     private LoadBalancerProvider delegateProvider;
89 
AutoConfiguredLoadBalancer(Helper helper)90     AutoConfiguredLoadBalancer(Helper helper) {
91       this.helper = helper;
92       delegateProvider = registry.getProvider(defaultPolicy);
93       if (delegateProvider == null) {
94         throw new IllegalStateException("Could not find policy '" + defaultPolicy
95             + "'. Make sure its implementation is either registered to LoadBalancerRegistry or"
96             + " included in META-INF/services/io.grpc.LoadBalancerProvider from your jar files.");
97       }
98       delegate = delegateProvider.newLoadBalancer(helper);
99     }
100 
101     /**
102      * Returns non-OK status if the delegate rejects the resolvedAddresses (e.g. if it does not
103      * support an empty list).
104      */
tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses)105     boolean tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
106       PolicySelection policySelection =
107           (PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig();
108 
109       if (policySelection == null) {
110         LoadBalancerProvider defaultProvider;
111         try {
112           defaultProvider = getProviderOrThrow(defaultPolicy, "using default policy");
113         } catch (PolicyException e) {
114           Status s = Status.INTERNAL.withDescription(e.getMessage());
115           helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s));
116           delegate.shutdown();
117           delegateProvider = null;
118           delegate = new NoopLoadBalancer();
119           return true;
120         }
121         policySelection =
122             new PolicySelection(defaultProvider, /* config= */ null);
123       }
124 
125       if (delegateProvider == null
126           || !policySelection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) {
127         helper.updateBalancingState(ConnectivityState.CONNECTING, new EmptyPicker());
128         delegate.shutdown();
129         delegateProvider = policySelection.provider;
130         LoadBalancer old = delegate;
131         delegate = delegateProvider.newLoadBalancer(helper);
132         helper.getChannelLogger().log(
133             ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}",
134             old.getClass().getSimpleName(), delegate.getClass().getSimpleName());
135       }
136       Object lbConfig = policySelection.config;
137       if (lbConfig != null) {
138         helper.getChannelLogger().log(
139             ChannelLogLevel.DEBUG, "Load-balancing config: {0}", policySelection.config);
140       }
141 
142       return getDelegate().acceptResolvedAddresses(
143           ResolvedAddresses.newBuilder()
144               .setAddresses(resolvedAddresses.getAddresses())
145               .setAttributes(resolvedAddresses.getAttributes())
146               .setLoadBalancingPolicyConfig(lbConfig)
147               .build());
148     }
149 
handleNameResolutionError(Status error)150     void handleNameResolutionError(Status error) {
151       getDelegate().handleNameResolutionError(error);
152     }
153 
154     @Deprecated
handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo)155     void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
156       getDelegate().handleSubchannelState(subchannel, stateInfo);
157     }
158 
requestConnection()159     void requestConnection() {
160       getDelegate().requestConnection();
161     }
162 
shutdown()163     void shutdown() {
164       delegate.shutdown();
165       delegate = null;
166     }
167 
168     @VisibleForTesting
getDelegate()169     public LoadBalancer getDelegate() {
170       return delegate;
171     }
172 
173     @VisibleForTesting
setDelegate(LoadBalancer lb)174     void setDelegate(LoadBalancer lb) {
175       delegate = lb;
176     }
177 
178     @VisibleForTesting
getDelegateProvider()179     LoadBalancerProvider getDelegateProvider() {
180       return delegateProvider;
181     }
182   }
183 
getProviderOrThrow(String policy, String choiceReason)184   private LoadBalancerProvider getProviderOrThrow(String policy, String choiceReason)
185       throws PolicyException {
186     LoadBalancerProvider provider = registry.getProvider(policy);
187     if (provider == null) {
188       throw new PolicyException(
189           "Trying to load '" + policy + "' because " + choiceReason + ", but it's unavailable");
190     }
191     return provider;
192   }
193 
194   /**
195    * Parses first available LoadBalancer policy from service config. Available LoadBalancer should
196    * be registered to {@link LoadBalancerRegistry}. If the first available LoadBalancer policy is
197    * invalid, it doesn't fall-back to next available policy, instead it returns error. This also
198    * means, it ignores LoadBalancer policies after the first available one even if any of them are
199    * invalid.
200    *
201    * <p>Order of policy preference:
202    *
203    * <ol>
204    *    <li>Policy from "loadBalancingConfig" if present</li>
205    *    <li>The policy from deprecated "loadBalancingPolicy" if present</li>
206    * </ol>
207    * </p>
208    *
209    * <p>Unlike a normal {@link LoadBalancer.Factory}, this accepts a full service config rather than
210    * the LoadBalancingConfig.
211    *
212    * @return the parsed {@link PolicySelection}, or {@code null} if no selection could be made.
213    */
214   @Nullable
parseLoadBalancerPolicy(Map<String, ?> serviceConfig)215   ConfigOrError parseLoadBalancerPolicy(Map<String, ?> serviceConfig) {
216     try {
217       List<LbConfig> loadBalancerConfigs = null;
218       if (serviceConfig != null) {
219         List<Map<String, ?>> rawLbConfigs =
220             ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(serviceConfig);
221         loadBalancerConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(rawLbConfigs);
222       }
223       if (loadBalancerConfigs != null && !loadBalancerConfigs.isEmpty()) {
224         return ServiceConfigUtil.selectLbPolicyFromList(loadBalancerConfigs, registry);
225       }
226       return null;
227     } catch (RuntimeException e) {
228       return ConfigOrError.fromError(
229           Status.UNKNOWN.withDescription("can't parse load balancer configuration").withCause(e));
230     }
231   }
232 
233   @VisibleForTesting
234   static final class PolicyException extends Exception {
235     private static final long serialVersionUID = 1L;
236 
PolicyException(String msg)237     private PolicyException(String msg) {
238       super(msg);
239     }
240   }
241 
242   private static final class EmptyPicker extends SubchannelPicker {
243 
244     @Override
pickSubchannel(PickSubchannelArgs args)245     public PickResult pickSubchannel(PickSubchannelArgs args) {
246       return PickResult.withNoResult();
247     }
248 
249     @Override
toString()250     public String toString() {
251       return MoreObjects.toStringHelper(EmptyPicker.class).toString();
252     }
253   }
254 
255   private static final class FailingPicker extends SubchannelPicker {
256     private final Status failure;
257 
FailingPicker(Status failure)258     FailingPicker(Status failure) {
259       this.failure = failure;
260     }
261 
262     @Override
pickSubchannel(PickSubchannelArgs args)263     public PickResult pickSubchannel(PickSubchannelArgs args) {
264       return PickResult.withError(failure);
265     }
266   }
267 }
268