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 static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023 024import java.util.concurrent.atomic.AtomicInteger; 025import java.util.concurrent.atomic.AtomicLong; 026import org.apache.hadoop.hbase.client.RegionInfo; 027import org.apache.hadoop.hbase.client.RegionInfoBuilder; 028import org.apache.hadoop.hbase.master.RegionState; 029import org.apache.hadoop.hbase.testclassification.MasterTests; 030import org.apache.hadoop.hbase.testclassification.SmallTests; 031import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper; 032import org.apache.hadoop.hbase.util.ManualEnvironmentEdge; 033import org.junit.jupiter.api.AfterEach; 034import org.junit.jupiter.api.BeforeEach; 035import org.junit.jupiter.api.Tag; 036import org.junit.jupiter.api.Test; 037 038@Tag(MasterTests.TAG) 039@Tag(SmallTests.TAG) 040public class TestRegionInTransitionTracker { 041 042 private RegionInTransitionTracker tracker; 043 private RegionInfo regionInfo; 044 private RegionStateNode regionStateNode; 045 private ManualEnvironmentEdge edge; 046 private AtomicLong ritDuration; 047 private AtomicInteger ritDurationCalls; 048 049 @BeforeEach 050 public void setUp() { 051 ritDuration = new AtomicLong(-1L); 052 ritDurationCalls = new AtomicInteger(); 053 tracker = new RegionInTransitionTracker(duration -> { 054 ritDuration.set(duration); 055 ritDurationCalls.incrementAndGet(); 056 }); 057 regionInfo = RegionInfoBuilder.FIRST_META_REGIONINFO; 058 regionStateNode = new RegionStateNode(regionInfo, new AtomicInteger()); 059 060 edge = new ManualEnvironmentEdge(); 061 edge.setValue(1_000L); 062 EnvironmentEdgeManagerTestHelper.injectEdge(edge); 063 } 064 065 @AfterEach 066 public void tearDown() { 067 EnvironmentEdgeManagerTestHelper.reset(); 068 tracker.stop(); 069 } 070 071 @Test 072 public void testInjectedRitDurationConsumerUsesFirstEnterTimestamp() throws Exception { 073 regionStateNode.setState(RegionState.State.OPEN); 074 tracker.handleRegionStateNodeOperation(regionStateNode); 075 assertFalse(tracker.isRegionInTransition(regionInfo)); 076 assertFalse(tracker.hasRegionsInTransition()); 077 assertEquals(0, tracker.getRegionsInTransition().size()); 078 079 edge.incValue(100L); 080 regionStateNode.transitionState(RegionState.State.CLOSING, RegionState.State.OPEN); 081 tracker.handleRegionStateNodeOperation(regionStateNode); 082 assertTrue(tracker.isRegionInTransition(regionInfo)); 083 assertEquals(1, tracker.getRegionsInTransition().size()); 084 085 edge.incValue(100L); 086 regionStateNode.transitionState(RegionState.State.CLOSED, RegionState.State.CLOSING); 087 tracker.handleRegionStateNodeOperation(regionStateNode); 088 assertTrue(tracker.isRegionInTransition(regionInfo)); 089 assertEquals(1, tracker.getRegionsInTransition().size()); 090 091 edge.incValue(100L); 092 regionStateNode.transitionState(RegionState.State.OPENING, RegionState.State.CLOSED); 093 tracker.handleRegionStateNodeOperation(regionStateNode); 094 assertTrue(tracker.isRegionInTransition(regionInfo)); 095 assertEquals(1, tracker.getRegionsInTransition().size()); 096 097 edge.incValue(100L); 098 regionStateNode.transitionState(RegionState.State.OPEN, RegionState.State.OPENING); 099 tracker.handleRegionStateNodeOperation(regionStateNode); 100 101 assertFalse(tracker.isRegionInTransition(regionInfo)); 102 assertFalse(tracker.hasRegionsInTransition()); 103 assertEquals(1, ritDurationCalls.get()); 104 assertTrue(ritDuration.get() >= 0); 105 assertEquals(300L, ritDuration.get()); 106 } 107 108 @Test 109 public void testRegionCrashUsesCrashTimestampAsRitStart() { 110 regionStateNode.setState(RegionState.State.OPEN); 111 112 edge.incValue(100L); 113 long crashTime = edge.currentTime(); 114 regionStateNode.crashed(crashTime); 115 116 edge.incValue(100L); 117 tracker.regionCrashed(regionStateNode, crashTime); 118 assertTrue(tracker.isRegionInTransition(regionInfo)); 119 120 edge.incValue(200L); 121 tracker.handleRegionStateNodeOperation(regionStateNode); 122 123 assertFalse(tracker.isRegionInTransition(regionInfo)); 124 assertEquals(1, ritDurationCalls.get()); 125 assertEquals(300L, ritDuration.get()); 126 } 127 128 @Test 129 public void testRegionCrashWithStaleProcedureUsesCrashTimestamp() { 130 // A procedure attached before the hosting server died reports an old timestamp via 131 // getLastUpdate(); the crash time must still win as the RIT start so the duration is not 132 // anchored at the stale procedure time. 133 long staleProcTime = 100L; 134 regionStateNode.setProcedure(new FixedLastUpdateProcedure(staleProcTime)); 135 regionStateNode.setState(RegionState.State.OPEN); 136 // Sanity: getLastUpdate() is masked by the attached procedure. 137 assertEquals(staleProcTime, regionStateNode.getLastUpdate()); 138 139 edge.incValue(100L); 140 long crashTime = edge.currentTime(); 141 regionStateNode.crashed(crashTime); 142 143 edge.incValue(100L); 144 tracker.regionCrashed(regionStateNode, crashTime); 145 assertTrue(tracker.isRegionInTransition(regionInfo)); 146 147 edge.incValue(200L); 148 tracker.handleRegionStateNodeOperation(regionStateNode); 149 150 assertFalse(tracker.isRegionInTransition(regionInfo)); 151 assertEquals(1, ritDurationCalls.get()); 152 // Anchored at crashTime (1100), not the stale procedure time (100): 1400 - 1100 = 300. 153 assertEquals(300L, ritDuration.get()); 154 } 155 156 @Test 157 public void testProcedureLastUpdateUsedAsRitStartForNormalTransition() { 158 // For a normal (non-crash) transition the RIT start follows RegionStateNode#getLastUpdate, 159 // which is the attached procedure's last update time. This exercises the procedure-masked 160 // branch of getLastUpdate() that production always hits. 161 long procTime = 1200L; 162 regionStateNode.setProcedure(new FixedLastUpdateProcedure(procTime)); 163 regionStateNode.setState(RegionState.State.OPENING); 164 tracker.handleRegionStateNodeOperation(regionStateNode); 165 assertTrue(tracker.isRegionInTransition(regionInfo)); 166 167 edge.setValue(1500L); 168 regionStateNode.setState(RegionState.State.OPEN); 169 tracker.handleRegionStateNodeOperation(regionStateNode); 170 171 assertFalse(tracker.isRegionInTransition(regionInfo)); 172 assertEquals(1, ritDurationCalls.get()); 173 // Start anchored at the procedure lastUpdate (1200), removed at 1500 -> 300. 174 assertEquals(300L, ritDuration.get()); 175 } 176 177 /** 178 * Minimal {@link TransitRegionStateProcedure} stub whose {@link #getLastUpdate()} returns a fixed 179 * value, so tests can exercise the procedure-masked branch of 180 * {@link RegionStateNode#getLastUpdate()} without standing up a procedure executor. 181 */ 182 private static final class FixedLastUpdateProcedure extends TransitRegionStateProcedure { 183 private final long lastUpdate; 184 185 FixedLastUpdateProcedure(long lastUpdate) { 186 this.lastUpdate = lastUpdate; 187 } 188 189 @Override 190 public long getLastUpdate() { 191 return lastUpdate; 192 } 193 } 194}