Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
shade.polaris.io.grpc.util.MultiChildLoadBalancer Maven / Gradle / Ivy
/*
* Copyright 2023 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.ConnectivityState.CONNECTING;
import static io.grpc.ConnectivityState.IDLE;
import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.SHUTDOWN;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.grpc.Attributes;
import io.grpc.ConnectivityState;
import io.grpc.EquivalentAddressGroup;
import io.grpc.Internal;
import io.grpc.LoadBalancer;
import io.grpc.LoadBalancerProvider;
import io.grpc.Status;
import io.grpc.internal.PickFirstLoadBalancerProvider;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* A base load balancing policy for those policies which has multiple children such as
* ClusterManager or the petiole policies. For internal use only.
*/
@Internal
public abstract class MultiChildLoadBalancer extends LoadBalancer {
private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName());
private final Map childLbStates = new LinkedHashMap<>();
private final Helper helper;
// Set to true if currently in the process of handling resolved addresses.
protected boolean resolvingAddresses;
protected final LoadBalancerProvider pickFirstLbProvider = new PickFirstLoadBalancerProvider();
protected ConnectivityState currentConnectivityState;
protected MultiChildLoadBalancer(Helper helper) {
this.helper = checkNotNull(helper, "helper");
logger.log(Level.FINE, "Created");
}
/**
* Using the state of all children will calculate the current connectivity state,
* update fields, generate a picker and then call
* {@link Helper#updateBalancingState(ConnectivityState, SubchannelPicker)}.
*/
protected abstract void updateOverallBalancingState();
/**
* Override to utilize parsing of the policy configuration or alternative helper/lb generation.
*/
protected Map createChildLbMap(ResolvedAddresses resolvedAddresses) {
Map childLbMap = new HashMap<>();
List addresses = resolvedAddresses.getAddresses();
for (EquivalentAddressGroup eag : addresses) {
Endpoint endpoint = new Endpoint(eag); // keys need to be just addresses
ChildLbState existingChildLbState = childLbStates.get(endpoint);
if (existingChildLbState != null) {
childLbMap.put(endpoint, existingChildLbState);
} else {
childLbMap.put(endpoint,
createChildLbState(endpoint, null, getInitialPicker(), resolvedAddresses));
}
}
return childLbMap;
}
/**
* Override to create an instance of a subclass.
*/
protected ChildLbState createChildLbState(Object key, Object policyConfig,
SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) {
return new ChildLbState(key, pickFirstLbProvider, policyConfig, initialPicker);
}
/**
* Override to completely replace the default logic or to do additional activities.
*/
@Override
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
try {
resolvingAddresses = true;
// process resolvedAddresses to update children
AcceptResolvedAddrRetVal acceptRetVal = acceptResolvedAddressesInternal(resolvedAddresses);
if (!acceptRetVal.status.isOk()) {
return acceptRetVal.status;
}
// Update the picker and our connectivity state
updateOverallBalancingState();
// shutdown removed children
shutdownRemoved(acceptRetVal.removedChildren);
return acceptRetVal.status;
} finally {
resolvingAddresses = false;
}
}
/**
* Override this if your keys are not of type Endpoint.
* @param key Key to identify the ChildLbState
* @param resolvedAddresses list of addresses which include attributes
* @param childConfig a load balancing policy config. This field is optional.
* @return a fully loaded ResolvedAddresses object for the specified key
*/
protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses,
Object childConfig) {
Endpoint endpointKey;
if (key instanceof EquivalentAddressGroup) {
endpointKey = new Endpoint((EquivalentAddressGroup) key);
} else {
checkArgument(key instanceof Endpoint, "key is wrong type");
endpointKey = (Endpoint) key;
}
// Retrieve the non-stripped version
EquivalentAddressGroup eagToUse = null;
for (EquivalentAddressGroup currEag : resolvedAddresses.getAddresses()) {
if (endpointKey.equals(new Endpoint(currEag))) {
eagToUse = currEag;
break;
}
}
checkNotNull(eagToUse, key + " no longer present in load balancer children");
return resolvedAddresses.toBuilder()
.setAddresses(Collections.singletonList(eagToUse))
.setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build())
.setLoadBalancingPolicyConfig(childConfig)
.build();
}
/**
* Handle the name resolution error.
*
*
Override if you need special handling.
*/
@Override
public void handleNameResolutionError(Status error) {
if (currentConnectivityState != READY) {
helper.updateBalancingState(TRANSIENT_FAILURE, getErrorPicker(error));
}
}
/**
* Handle the name resolution error only for the specified child.
*
*
Override if you need special handling.
*/
protected void handleNameResolutionError(ChildLbState child, Status error) {
child.lb.handleNameResolutionError(error);
}
/**
* Creates a picker representing the state before any connections have been established.
*
*
Override to produce a custom picker.
*/
protected SubchannelPicker getInitialPicker() {
return new FixedResultPicker(PickResult.withNoResult());
}
/**
* Creates a new picker representing an error status.
*
*
Override to produce a custom picker when there are errors.
*/
protected SubchannelPicker getErrorPicker(Status error) {
return new FixedResultPicker(PickResult.withError(error));
}
@Override
public void shutdown() {
logger.log(Level.FINE, "Shutdown");
for (ChildLbState state : childLbStates.values()) {
state.shutdown();
}
childLbStates.clear();
}
/**
* This does the work to update the child map and calculate which children have been removed.
* You must call {@link #updateOverallBalancingState} to update the picker
* and call {@link #shutdownRemoved(List)} to shutdown the endpoints that have been removed.
*/
protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal(
ResolvedAddresses resolvedAddresses) {
logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses);
// Subclass handles any special manipulation to create appropriate types of keyed ChildLbStates
Map newChildren = createChildLbMap(resolvedAddresses);
// Handle error case
if (newChildren.isEmpty()) {
Status unavailableStatus = Status.UNAVAILABLE.withDescription(
"NameResolver returned no usable address. " + resolvedAddresses);
handleNameResolutionError(unavailableStatus);
return new AcceptResolvedAddrRetVal(unavailableStatus, null);
}
addMissingChildren(newChildren);
updateChildrenWithResolvedAddresses(resolvedAddresses, newChildren);
return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildren.keySet()));
}
protected final void addMissingChildren(Map newChildren) {
// Do adds and identify reused children
for (Map.Entry entry : newChildren.entrySet()) {
final Object key = entry.getKey();
if (!childLbStates.containsKey(key)) {
childLbStates.put(key, entry.getValue());
}
}
}
protected final void updateChildrenWithResolvedAddresses(ResolvedAddresses resolvedAddresses,
Map newChildren) {
for (Map.Entry entry : newChildren.entrySet()) {
Object childConfig = entry.getValue().getConfig();
ChildLbState childLbState = childLbStates.get(entry.getKey());
ResolvedAddresses childAddresses =
getChildAddresses(entry.getKey(), resolvedAddresses, childConfig);
childLbState.setResolvedAddresses(childAddresses); // update child
childLbState.lb.handleResolvedAddresses(childAddresses); // update child LB
}
}
/**
* Identifies which children have been removed (are not part of the newChildKeys).
*/
protected final List getRemovedChildren(Set newChildKeys) {
List removedChildren = new ArrayList<>();
// Do removals
for (Object key : ImmutableList.copyOf(childLbStates.keySet())) {
if (!newChildKeys.contains(key)) {
ChildLbState childLbState = childLbStates.remove(key);
removedChildren.add(childLbState);
}
}
return removedChildren;
}
protected final void shutdownRemoved(List removedChildren) {
// Do shutdowns after updating picker to reduce the chance of failing an RPC by picking a
// subchannel that has been shutdown.
for (ChildLbState childLbState : removedChildren) {
childLbState.shutdown();
}
}
@Nullable
protected static ConnectivityState aggregateState(
@Nullable ConnectivityState overallState, ConnectivityState childState) {
if (overallState == null) {
return childState;
}
if (overallState == READY || childState == READY) {
return READY;
}
if (overallState == CONNECTING || childState == CONNECTING) {
return CONNECTING;
}
if (overallState == IDLE || childState == IDLE) {
return IDLE;
}
return overallState;
}
protected final Helper getHelper() {
return helper;
}
@VisibleForTesting
public final ImmutableMap getImmutableChildMap() {
return ImmutableMap.copyOf(childLbStates);
}
@VisibleForTesting
public final Collection getChildLbStates() {
return childLbStates.values();
}
@VisibleForTesting
public final ChildLbState getChildLbState(Object key) {
if (key == null) {
return null;
}
if (key instanceof EquivalentAddressGroup) {
key = new Endpoint((EquivalentAddressGroup) key);
}
return childLbStates.get(key);
}
@VisibleForTesting
public final ChildLbState getChildLbStateEag(EquivalentAddressGroup eag) {
return getChildLbState(new Endpoint(eag));
}
/**
* Filters out non-ready child load balancers (subchannels).
*/
protected final List getReadyChildren() {
List activeChildren = new ArrayList<>();
for (ChildLbState child : getChildLbStates()) {
if (child.getCurrentState() == READY) {
activeChildren.add(child);
}
}
return activeChildren;
}
/**
* This represents the state of load balancer children. Each endpoint (represented by an
* EquivalentAddressGroup or EDS string) will have a separate ChildLbState which in turn will
* have a single child LoadBalancer created from the provided factory.
*
* A ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
* petiole policy above and the PickFirstLoadBalancer's helper below.
*
*
If you wish to store additional state information related to each subchannel, then extend
* this class.
*/
public class ChildLbState {
private final Object key;
private ResolvedAddresses resolvedAddresses;
private final Object config;
private final LoadBalancer lb;
private ConnectivityState currentState;
private SubchannelPicker currentPicker;
public ChildLbState(Object key, LoadBalancer.Factory policyFactory, Object childConfig,
SubchannelPicker initialPicker) {
this.key = key;
this.currentPicker = initialPicker;
this.config = childConfig;
this.lb = policyFactory.newLoadBalancer(createChildHelper());
this.currentState = CONNECTING;
}
protected ChildLbStateHelper createChildHelper() {
return new ChildLbStateHelper();
}
/**
* Override for unique behavior such as delayed shutdowns of subchannels.
*/
protected void shutdown() {
lb.shutdown();
this.currentState = SHUTDOWN;
logger.log(Level.FINE, "Child balancer {0} deleted", key);
}
@Override
public String toString() {
return "Address = " + key
+ ", state = " + currentState
+ ", picker type: " + currentPicker.getClass()
+ ", lb: " + lb;
}
public final Object getKey() {
return key;
}
@VisibleForTesting
public final LoadBalancer getLb() {
return lb;
}
@VisibleForTesting
public final SubchannelPicker getCurrentPicker() {
return currentPicker;
}
protected final Subchannel getSubchannels(PickSubchannelArgs args) {
if (getCurrentPicker() == null) {
return null;
}
return getCurrentPicker().pickSubchannel(args).getSubchannel();
}
public final ConnectivityState getCurrentState() {
return currentState;
}
protected final void setCurrentState(ConnectivityState newState) {
currentState = newState;
}
protected final void setCurrentPicker(SubchannelPicker newPicker) {
currentPicker = newPicker;
}
public final EquivalentAddressGroup getEag() {
if (resolvedAddresses == null || resolvedAddresses.getAddresses().isEmpty()) {
return null;
}
return resolvedAddresses.getAddresses().get(0);
}
protected final void setResolvedAddresses(ResolvedAddresses newAddresses) {
checkNotNull(newAddresses, "Missing address list for child");
resolvedAddresses = newAddresses;
}
private Object getConfig() {
return config;
}
@VisibleForTesting
public final ResolvedAddresses getResolvedAddresses() {
return resolvedAddresses;
}
/**
* ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
* petiole policy above and the PickFirstLoadBalancer's helper below.
*
*
The ChildLbState updates happen during updateBalancingState. Otherwise, it is doing
* simple forwarding.
*/
protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
/**
* Update current state and picker for this child and then use
* {@link #updateOverallBalancingState()} for the parent LB.
*
*
Override this if you don't want to automatically request a connection when in IDLE
*/
@Override
public void updateBalancingState(final ConnectivityState newState,
final SubchannelPicker newPicker) {
if (!childLbStates.containsKey(key)) {
return;
}
currentState = newState;
currentPicker = newPicker;
// If we are already in the process of resolving addresses, the overall balancing state
// will be updated at the end of it, and we don't need to trigger that update here.
if (!resolvingAddresses) {
if (newState == IDLE) {
lb.requestConnection();
}
updateOverallBalancingState();
}
}
@Override
protected Helper delegate() {
return helper;
}
}
}
/**
* Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets.
* Ignores the attributes, orders the addresses in a deterministic manner and converts each
* address into a string for easy comparison. Also caches the hashcode.
* Is used as a key for ChildLbState for most load balancers (ClusterManagerLB uses a String).
*/
protected static class Endpoint {
final String[] addrs;
final int hashCode;
public Endpoint(EquivalentAddressGroup eag) {
checkNotNull(eag, "eag");
addrs = new String[eag.getAddresses().size()];
int i = 0;
for (SocketAddress address : eag.getAddresses()) {
addrs[i++] = address.toString();
}
Arrays.sort(addrs);
hashCode = Arrays.hashCode(addrs);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
if (!(other instanceof Endpoint)) {
return false;
}
Endpoint o = (Endpoint) other;
if (o.hashCode != hashCode || o.addrs.length != addrs.length) {
return false;
}
return Arrays.equals(o.addrs, this.addrs);
}
@Override
public String toString() {
return Arrays.toString(addrs);
}
}
protected static class AcceptResolvedAddrRetVal {
public final Status status;
public final List removedChildren;
public AcceptResolvedAddrRetVal(Status status, List removedChildren) {
this.status = status;
this.removedChildren = removedChildren;
}
}
}