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