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.assignment;
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.concurrent.ConcurrentSkipListMap;
023import org.apache.hadoop.hbase.TableName;
024import org.apache.hadoop.hbase.client.RegionInfo;
025import org.apache.hadoop.hbase.client.TableState;
026import org.apache.hadoop.hbase.master.RegionState;
027import org.apache.hadoop.hbase.master.TableStateManager;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * Tracks regions that are currently in transition (RIT) - those not yet in their terminal state.
034 */
035@InterfaceAudience.Private
036public class RegionInTransitionTracker {
037  private static final Logger LOG = LoggerFactory.getLogger(RegionInTransitionTracker.class);
038
039  private final List<RegionState.State> DISABLE_TABLE_REGION_STATE =
040    List.of(RegionState.State.OFFLINE, RegionState.State.CLOSED);
041
042  private final List<RegionState.State> ENABLE_TABLE_REGION_STATE = List.of(RegionState.State.OPEN);
043
044  private final ConcurrentSkipListMap<RegionInfo, RegionStateNode> regionInTransition =
045    new ConcurrentSkipListMap<>(RegionInfo.COMPARATOR);
046
047  private TableStateManager tableStateManager;
048
049  public boolean isRegionInTransition(final RegionInfo regionInfo) {
050    return regionInTransition.containsKey(regionInfo);
051  }
052
053  /**
054   * Handles a region whose hosting RegionServer has crashed. When a RegionServer fails, all regions
055   * it was hosting are automatically added to the RIT list since they need to be reassigned to
056   * other servers.
057   */
058  public void regionCrashed(RegionStateNode regionStateNode) {
059    if (regionStateNode.getRegionInfo().getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) {
060      return;
061    }
062
063    if (addRegionInTransition(regionStateNode)) {
064      LOG.debug("{} added to RIT list because hosting region server is crashed ",
065        regionStateNode.getRegionInfo().getEncodedName());
066    }
067  }
068
069  /**
070   * Processes a region state change and updates the RIT tracking accordingly. This is the core
071   * method that determines whether a region should be added to or removed from the RIT list based
072   * on its current state and the table's enabled/disabled status. This method should be called
073   * whenever a region state changes get stored to hbase:meta Note: Only default replicas (replica
074   * ID 0) are tracked. Read replicas are ignored.
075   * @param regionStateNode the region state node with the current state information
076   */
077  public void handleRegionStateNodeOperation(RegionStateNode regionStateNode) {
078    // only consider default replica for availability
079    if (regionStateNode.getRegionInfo().getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) {
080      return;
081    }
082
083    RegionState.State currentState = regionStateNode.getState();
084    boolean tableEnabled = isTableEnabled(regionStateNode.getTable());
085    List<RegionState.State> terminalStates =
086      tableEnabled ? ENABLE_TABLE_REGION_STATE : DISABLE_TABLE_REGION_STATE;
087
088    // if region is merged or split it should not be in RIT list
089    if (
090      currentState == RegionState.State.SPLIT || currentState == RegionState.State.MERGED
091        || regionStateNode.getRegionInfo().isSplit()
092    ) {
093      if (removeRegionInTransition(regionStateNode.getRegionInfo())) {
094        LOG.debug("Removed {} from RIT list as it is split or merged",
095          regionStateNode.getRegionInfo().getEncodedName());
096      }
097    } else if (!terminalStates.contains(currentState)) {
098      if (addRegionInTransition(regionStateNode)) {
099        LOG.debug("{} added to RIT list because it is in-between state, region state : {} ",
100          regionStateNode.getRegionInfo().getEncodedName(), currentState);
101      }
102    } else {
103      if (removeRegionInTransition(regionStateNode.getRegionInfo())) {
104        LOG.debug("Removed {} from RIT list as reached to terminal state {}",
105          regionStateNode.getRegionInfo().getEncodedName(), currentState);
106      }
107    }
108  }
109
110  private boolean isTableEnabled(TableName tableName) {
111    if (tableStateManager != null) {
112      return tableStateManager.isTableState(tableName, TableState.State.ENABLED,
113        TableState.State.ENABLING);
114    }
115    // AssignmentManager calls setTableStateManager once hbase:meta is confirmed online, if it is
116    // still null it means confirmation is still pending. One should not access TableStateManger
117    // till the time.
118    assert TableName.isMetaTableName(tableName);
119    return true;
120  }
121
122  /**
123   * Handles the deletion of a region by removing it from RIT tracking. This is called when a region
124   * is permanently removed from the cluster, typically after a successful merge operation where the
125   * parent regions are cleaned up. During table deletion, table should be already disabled and all
126   * the region are already OFFLINE
127   * @param regionInfo the region being deleted
128   */
129  public void handleRegionDelete(RegionInfo regionInfo) {
130    removeRegionInTransition(regionInfo);
131  }
132
133  private boolean addRegionInTransition(final RegionStateNode regionStateNode) {
134    return regionInTransition.putIfAbsent(regionStateNode.getRegionInfo(), regionStateNode) == null;
135  }
136
137  private boolean removeRegionInTransition(final RegionInfo regionInfo) {
138    return regionInTransition.remove(regionInfo) != null;
139  }
140
141  public void stop() {
142    regionInTransition.clear();
143  }
144
145  public boolean hasRegionsInTransition() {
146    return !regionInTransition.isEmpty();
147  }
148
149  public List<RegionStateNode> getRegionsInTransition() {
150    return new ArrayList<>(regionInTransition.values());
151  }
152
153  public void setTableStateManager(TableStateManager tableStateManager) {
154    this.tableStateManager = tableStateManager;
155  }
156}