All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.internal.AutoConfiguredLoadBalancerFactory Maven / Gradle / Ivy

There is a newer version: 1.66.0
Show newest version
/*
 * Copyright 2018 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.internal;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.VisibleForTesting;
import io.grpc.Attributes;
import io.grpc.ChannelLogger.ChannelLogLevel;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
import io.grpc.EquivalentAddressGroup;
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.Status;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.annotation.Nullable;

public final class AutoConfiguredLoadBalancerFactory extends LoadBalancer.Factory {
  private static final Logger logger =
      Logger.getLogger(AutoConfiguredLoadBalancerFactory.class.getName());

  private final LoadBalancerRegistry registry;
  private final String defaultPolicy;

  public AutoConfiguredLoadBalancerFactory(String defaultPolicy) {
    this(LoadBalancerRegistry.getDefaultRegistry(), defaultPolicy);
  }

  @VisibleForTesting
  AutoConfiguredLoadBalancerFactory(LoadBalancerRegistry registry, String defaultPolicy) {
    this.registry = checkNotNull(registry, "registry");
    this.defaultPolicy = checkNotNull(defaultPolicy, "defaultPolicy");
  }

  @Override
  public LoadBalancer newLoadBalancer(Helper helper) {
    return new AutoConfiguredLoadBalancer(helper);
  }

  private static final class NoopLoadBalancer extends LoadBalancer {

    @Override
    public void handleResolvedAddressGroups(List s, Attributes a) {}

    @Override
    public void handleNameResolutionError(Status error) {}

    @Override
    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {}

    @Override
    public void shutdown() {}
  }

  @VisibleForTesting
  public final class AutoConfiguredLoadBalancer extends LoadBalancer {
    private final Helper helper;
    private LoadBalancer delegate;
    private LoadBalancerProvider delegateProvider;
    private boolean roundRobinDueToGrpclbDepMissing;

    AutoConfiguredLoadBalancer(Helper helper) {
      this.helper = helper;
      delegateProvider = registry.getProvider(defaultPolicy);
      if (delegateProvider == null) {
        throw new IllegalStateException("Could not find policy '" + defaultPolicy
            + "'. Make sure its implementation is either registered to LoadBalancerRegistry or"
            + " included in META-INF/services/io.grpc.LoadBalancerProvider from your jar files.");
      }
      delegate = delegateProvider.newLoadBalancer(helper);
    }

    //  Must be run inside ChannelExecutor.
    @Override
    public void handleResolvedAddressGroups(
        List servers, Attributes attributes) {
      if (attributes.get(ATTR_LOAD_BALANCING_CONFIG) != null) {
        throw new IllegalArgumentException(
            "Unexpected ATTR_LOAD_BALANCING_CONFIG from upstream: "
            + attributes.get(ATTR_LOAD_BALANCING_CONFIG));
      }
      Map configMap = attributes.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
      PolicySelection selection;
      try {
        selection = decideLoadBalancerProvider(servers, configMap);
      } catch (PolicyException e) {
        Status s = Status.INTERNAL.withDescription(e.getMessage());
        helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s));
        delegate.shutdown();
        delegateProvider = null;
        delegate = new NoopLoadBalancer();
        return;
      }

      if (delegateProvider == null
          || !selection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) {
        helper.updateBalancingState(ConnectivityState.CONNECTING, new EmptyPicker());
        delegate.shutdown();
        delegateProvider = selection.provider;
        LoadBalancer old = delegate;
        delegate = delegateProvider.newLoadBalancer(helper);
        helper.getChannelLogger().log(
            ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}",
            old.getClass().getSimpleName(), delegate.getClass().getSimpleName());
      }

      if (selection.config != null) {
        helper.getChannelLogger().log(
            ChannelLogLevel.DEBUG, "Load-balancing config: {0}", selection.config);
        attributes =
            attributes.toBuilder().set(ATTR_LOAD_BALANCING_CONFIG, selection.config).build();
      }

      LoadBalancer delegate = getDelegate();
      if (selection.serverList.isEmpty()
          && !delegate.canHandleEmptyAddressListFromNameResolution()) {
        delegate.handleNameResolutionError(
            Status.UNAVAILABLE.withDescription(
                "Name resolver returned no usable address. addrs="
                + servers + ", attrs=" + attributes));
      } else {
        delegate.handleResolvedAddressGroups(selection.serverList, attributes);
      }
    }

    @Override
    public void handleNameResolutionError(Status error) {
      getDelegate().handleNameResolutionError(error);
    }

    @Override
    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
      getDelegate().handleSubchannelState(subchannel, stateInfo);
    }

    @Override
    public boolean canHandleEmptyAddressListFromNameResolution() {
      return true;
    }

    @Override
    public void shutdown() {
      delegate.shutdown();
      delegate = null;
    }

    @VisibleForTesting
    public LoadBalancer getDelegate() {
      return delegate;
    }

    @VisibleForTesting
    void setDelegate(LoadBalancer lb) {
      delegate = lb;
    }

    @VisibleForTesting
    LoadBalancerProvider getDelegateProvider() {
      return delegateProvider;
    }

    /**
     * Picks a load balancer based on given criteria.  In order of preference:
     *
     * 
    *
  1. User provided lb on the channel. This is a degenerate case and not handled here.
  2. *
  3. "grpclb" if any gRPC LB balancer addresses are present
  4. *
  5. The policy picked by the service config
  6. *
  7. "pick_first" if the service config choice does not specify
  8. *
* * @param servers The list of servers reported * @param config the service config object * @return the new load balancer factory, never null */ @VisibleForTesting PolicySelection decideLoadBalancerProvider( List servers, @Nullable Map config) throws PolicyException { // Check for balancer addresses boolean haveBalancerAddress = false; List backendAddrs = new ArrayList<>(); for (EquivalentAddressGroup s : servers) { if (s.getAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY) != null) { haveBalancerAddress = true; } else { backendAddrs.add(s); } } if (haveBalancerAddress) { LoadBalancerProvider grpclbProvider = registry.getProvider("grpclb"); if (grpclbProvider == null) { if (backendAddrs.isEmpty()) { throw new PolicyException( "Received ONLY balancer addresses but grpclb runtime is missing"); } if (!roundRobinDueToGrpclbDepMissing) { roundRobinDueToGrpclbDepMissing = true; String errorMsg = "Found balancer addresses but grpclb runtime is missing." + " Will use round_robin. Please include grpc-grpclb in your runtime depedencies."; helper.getChannelLogger().log(ChannelLogLevel.ERROR, errorMsg); logger.warning(errorMsg); } return new PolicySelection( getProviderOrThrow( "round_robin", "received balancer addresses but grpclb runtime is missing"), backendAddrs, null); } else { return new PolicySelection(grpclbProvider, servers, null); } } roundRobinDueToGrpclbDepMissing = false; List> lbConfigs = null; if (config != null) { lbConfigs = ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(config); } if (lbConfigs != null && !lbConfigs.isEmpty()) { LinkedHashSet policiesTried = new LinkedHashSet<>(); for (Map lbConfig : lbConfigs) { if (lbConfig.size() != 1) { throw new PolicyException( "There are " + lbConfig.size() + " load-balancing configs in a list item. Exactly one is expected. Config=" + lbConfig); } Entry entry = lbConfig.entrySet().iterator().next(); String policy = entry.getKey(); LoadBalancerProvider provider = registry.getProvider(policy); if (provider != null) { if (!policiesTried.isEmpty()) { helper.getChannelLogger().log( ChannelLogLevel.DEBUG, "{0} specified by Service Config are not available", policiesTried); } return new PolicySelection(provider, servers, (Map) entry.getValue()); } policiesTried.add(policy); } throw new PolicyException( "None of " + policiesTried + " specified by Service Config are available."); } return new PolicySelection( getProviderOrThrow(defaultPolicy, "using default policy"), servers, null); } } private LoadBalancerProvider getProviderOrThrow(String policy, String choiceReason) throws PolicyException { LoadBalancerProvider provider = registry.getProvider(policy); if (provider == null) { throw new PolicyException( "Trying to load '" + policy + "' because " + choiceReason + ", but it's unavailable"); } return provider; } @VisibleForTesting static class PolicyException extends Exception { private static final long serialVersionUID = 1L; private PolicyException(String msg) { super(msg); } } @VisibleForTesting static final class PolicySelection { final LoadBalancerProvider provider; final List serverList; @Nullable final Map config; @SuppressWarnings("unchecked") PolicySelection( LoadBalancerProvider provider, List serverList, @Nullable Map config) { this.provider = checkNotNull(provider, "provider"); this.serverList = Collections.unmodifiableList(checkNotNull(serverList, "serverList")); this.config = (Map) config; } } private static final class EmptyPicker extends SubchannelPicker { @Override public PickResult pickSubchannel(PickSubchannelArgs args) { return PickResult.withNoResult(); } } private static final class FailingPicker extends SubchannelPicker { private final Status failure; FailingPicker(Status failure) { this.failure = failure; } @Override public PickResult pickSubchannel(PickSubchannelArgs args) { return PickResult.withError(failure); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy