001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.balancer;
019
020import java.time.Duration;
021import java.util.List;
022import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
023import org.apache.yetus.audience.InterfaceAudience;
024import org.apache.yetus.audience.InterfaceStability;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028@InterfaceAudience.Private
029@InterfaceStability.Evolving
030public abstract class RegionPlanConditionalCandidateGenerator extends CandidateGenerator {
031
032  private static final Logger LOG =
033    LoggerFactory.getLogger(RegionPlanConditionalCandidateGenerator.class);
034
035  private static final Duration WEIGHT_CACHE_TTL = Duration.ofMinutes(1);
036  private long lastWeighedAt = -1;
037  private double lastWeight = 0.0;
038
039  private final BalancerConditionals balancerConditionals;
040
041  RegionPlanConditionalCandidateGenerator(BalancerConditionals balancerConditionals) {
042    this.balancerConditionals = balancerConditionals;
043  }
044
045  BalancerConditionals getBalancerConditionals() {
046    return this.balancerConditionals;
047  }
048
049  /**
050   * Generates a balancing action to appease the conditional.
051   * @param cluster    Current state of the cluster.
052   * @param isWeighing Flag indicating if the generator is being used for weighing.
053   * @return A BalanceAction, or NULL_ACTION if no action is needed.
054   */
055  abstract BalanceAction generateCandidate(BalancerClusterState cluster, boolean isWeighing);
056
057  @Override
058  BalanceAction generate(BalancerClusterState cluster) {
059    BalanceAction balanceAction = generateCandidate(cluster, false);
060    if (!willBeAccepted(cluster, balanceAction)) {
061      LOG.debug("Generated action is not widely accepted by all conditionals. "
062        + "Likely we are finding our way out of a deadlock. balanceAction={}", balanceAction);
063    }
064    return balanceAction;
065  }
066
067  BalanceAction batchMovesAndResetClusterState(BalancerClusterState cluster,
068    List<MoveRegionAction> moves) {
069    if (moves.isEmpty()) {
070      return BalanceAction.NULL_ACTION;
071    }
072    MoveBatchAction batchAction = new MoveBatchAction(moves);
073    undoBatchAction(cluster, batchAction);
074    return batchAction;
075  }
076
077  boolean willBeAccepted(BalancerClusterState cluster, BalanceAction action) {
078    BalancerConditionals balancerConditionals = getBalancerConditionals();
079    if (balancerConditionals == null) {
080      return true;
081    }
082    return !balancerConditionals.isViolating(cluster, action);
083  }
084
085  void undoBatchAction(BalancerClusterState cluster, MoveBatchAction batchAction) {
086    for (int i = batchAction.getMoveActions().size() - 1; i >= 0; i--) {
087      MoveRegionAction action = batchAction.getMoveActions().get(i);
088      cluster.doAction(action.undoAction());
089    }
090  }
091
092  void clearWeightCache() {
093    lastWeighedAt = -1;
094  }
095
096  double getWeight(BalancerClusterState cluster) {
097    boolean hasCandidate = false;
098
099    // Candidate generation is expensive, so for re-weighing generators we will cache
100    // the value for a bit
101    if (EnvironmentEdgeManager.currentTime() - lastWeighedAt < WEIGHT_CACHE_TTL.toMillis()) {
102      return lastWeight;
103    } else {
104      hasCandidate = generateCandidate(cluster, true) != BalanceAction.NULL_ACTION;
105      lastWeighedAt = EnvironmentEdgeManager.currentTime();
106    }
107
108    if (hasCandidate) {
109      // If this generator has something to do, then it's important
110      lastWeight = CandidateGenerator.MAX_WEIGHT;
111    } else {
112      lastWeight = 0;
113    }
114    return lastWeight;
115  }
116}