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.assertNotNull; 022import static org.junit.jupiter.api.Assertions.assertNull; 023import static org.junit.jupiter.api.Assertions.assertThrows; 024import static org.junit.jupiter.api.Assertions.assertTrue; 025 026import java.util.Collections; 027import java.util.concurrent.Executors; 028import java.util.concurrent.Future; 029import org.apache.hadoop.hbase.DoNotRetryIOException; 030import org.apache.hadoop.hbase.HBaseTestingUtil; 031import org.apache.hadoop.hbase.MetaTableAccessor; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.RegionInfo; 034import org.apache.hadoop.hbase.client.RegionInfoBuilder; 035import org.apache.hadoop.hbase.master.RegionState.State; 036import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 037import org.apache.hadoop.hbase.procedure2.util.StringUtils; 038import org.apache.hadoop.hbase.testclassification.LargeTests; 039import org.apache.hadoop.hbase.testclassification.MasterTests; 040import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 041import org.junit.jupiter.api.Tag; 042import org.junit.jupiter.api.Test; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046@Tag(MasterTests.TAG) 047@Tag(LargeTests.TAG) 048public class TestAssignmentManager extends TestAssignmentManagerBase { 049 050 private static final Logger LOG = LoggerFactory.getLogger(TestAssignmentManager.class); 051 052 @Test 053 public void testAssignWithGoodExec() throws Exception { 054 // collect AM metrics before test 055 collectAssignmentManagerMetrics(); 056 057 testAssign(new GoodRsExecutor()); 058 059 assertEquals(assignSubmittedCount + NREGIONS, 060 assignProcMetrics.getSubmittedCounter().getCount()); 061 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 062 } 063 064 @Test 065 public void testAssignAndCrashBeforeResponse() throws Exception { 066 TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse"); 067 RegionInfo hri = createRegionInfo(tableName, 1); 068 rsDispatcher.setMockRsExecutor(new HangThenRSCrashExecutor()); 069 TransitRegionStateProcedure proc = createAssignProcedure(hri); 070 waitOnFuture(submitProcedure(proc)); 071 } 072 073 @Test 074 public void testUnassignAndCrashBeforeResponse() throws Exception { 075 TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse"); 076 RegionInfo hri = createRegionInfo(tableName, 1); 077 rsDispatcher.setMockRsExecutor(new HangOnCloseThenRSCrashExecutor()); 078 for (int i = 0; i < HangOnCloseThenRSCrashExecutor.TYPES_OF_FAILURE; i++) { 079 TransitRegionStateProcedure assign = createAssignProcedure(hri); 080 waitOnFuture(submitProcedure(assign)); 081 TransitRegionStateProcedure unassign = createUnassignProcedure(hri); 082 waitOnFuture(submitProcedure(unassign)); 083 } 084 } 085 086 @Test 087 public void testAssignSocketTimeout() throws Exception { 088 TableName tableName = TableName.valueOf(testMethodName); 089 RegionInfo hri = createRegionInfo(tableName, 1); 090 091 // collect AM metrics before test 092 collectAssignmentManagerMetrics(); 093 094 rsDispatcher.setMockRsExecutor(new SocketTimeoutRsExecutor(20)); 095 waitOnFuture(submitProcedure(createAssignProcedure(hri))); 096 097 // we crashed a rs, so it is possible that there are other regions on the rs which will also be 098 // reassigned, so here we just assert greater than, not the exact number. 099 assertTrue(assignProcMetrics.getSubmittedCounter().getCount() > assignSubmittedCount); 100 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 101 } 102 103 @Test 104 public void testAssignQueueFullOnce() throws Exception { 105 TableName tableName = TableName.valueOf(testMethodName); 106 RegionInfo hri = createRegionInfo(tableName, 1); 107 108 // collect AM metrics before test 109 collectAssignmentManagerMetrics(); 110 111 rsDispatcher.setMockRsExecutor(new CallQueueTooBigOnceRsExecutor()); 112 waitOnFuture(submitProcedure(createAssignProcedure(hri))); 113 114 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 115 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 116 } 117 118 @Test 119 public void testTimeoutThenQueueFull() throws Exception { 120 TableName tableName = TableName.valueOf(testMethodName); 121 RegionInfo hri = createRegionInfo(tableName, 1); 122 123 // collect AM metrics before test 124 collectAssignmentManagerMetrics(); 125 126 rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(10)); 127 waitOnFuture(submitProcedure(createAssignProcedure(hri))); 128 rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(15)); 129 waitOnFuture(submitProcedure(createUnassignProcedure(hri))); 130 131 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 132 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 133 assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount()); 134 assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount()); 135 } 136 137 private void testAssign(final MockRSExecutor executor) throws Exception { 138 testAssign(executor, NREGIONS); 139 } 140 141 private void testAssign(MockRSExecutor executor, int nRegions) throws Exception { 142 rsDispatcher.setMockRsExecutor(executor); 143 144 TransitRegionStateProcedure[] assignments = new TransitRegionStateProcedure[nRegions]; 145 146 long st = EnvironmentEdgeManager.currentTime(); 147 bulkSubmit(assignments); 148 149 for (int i = 0; i < assignments.length; ++i) { 150 ProcedureTestingUtility.waitProcedure(master.getMasterProcedureExecutor(), assignments[i]); 151 assertTrue(assignments[i].isSuccess(), assignments[i].toString()); 152 } 153 long et = EnvironmentEdgeManager.currentTime(); 154 float sec = ((et - st) / 1000.0f); 155 LOG.info(String.format("[T] Assigning %dprocs in %s (%.2fproc/sec)", assignments.length, 156 StringUtils.humanTimeDiff(et - st), assignments.length / sec)); 157 } 158 159 @Test 160 public void testAssignAnAssignedRegion() throws Exception { 161 final TableName tableName = TableName.valueOf("testAssignAnAssignedRegion"); 162 final RegionInfo hri = createRegionInfo(tableName, 1); 163 164 // collect AM metrics before test 165 collectAssignmentManagerMetrics(); 166 167 rsDispatcher.setMockRsExecutor(new GoodRsExecutor()); 168 169 Future<byte[]> futureA = submitProcedure(createAssignProcedure(hri)); 170 171 // wait first assign 172 waitOnFuture(futureA); 173 am.getRegionStates().isRegionInState(hri, State.OPEN); 174 // Second should be a noop. We should recognize region is already OPEN internally 175 // and skip out doing nothing. 176 // wait second assign 177 Future<byte[]> futureB = submitProcedure(createAssignProcedure(hri)); 178 waitOnFuture(futureB); 179 am.getRegionStates().isRegionInState(hri, State.OPEN); 180 // TODO: What else can we do to ensure just a noop. 181 182 // TODO: Though second assign is noop, it's considered success, can noop be handled in a 183 // better way? 184 assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount()); 185 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 186 } 187 188 @Test 189 public void testUnassignAnUnassignedRegion() throws Exception { 190 final TableName tableName = TableName.valueOf("testUnassignAnUnassignedRegion"); 191 final RegionInfo hri = createRegionInfo(tableName, 1); 192 193 // collect AM metrics before test 194 collectAssignmentManagerMetrics(); 195 196 rsDispatcher.setMockRsExecutor(new GoodRsExecutor()); 197 198 // assign the region first 199 waitOnFuture(submitProcedure(createAssignProcedure(hri))); 200 201 final Future<byte[]> futureA = submitProcedure(createUnassignProcedure(hri)); 202 203 // Wait first unassign. 204 waitOnFuture(futureA); 205 am.getRegionStates().isRegionInState(hri, State.CLOSED); 206 // Second should be a noop. We should recognize region is already CLOSED internally 207 // and skip out doing nothing. 208 final Future<byte[]> futureB = submitProcedure(createUnassignProcedure(hri)); 209 waitOnFuture(futureB); 210 // Ensure we are still CLOSED. 211 am.getRegionStates().isRegionInState(hri, State.CLOSED); 212 // TODO: What else can we do to ensure just a noop. 213 214 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 215 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 216 // TODO: Though second unassign is noop, it's considered success, can noop be handled in a 217 // better way? 218 assertEquals(unassignSubmittedCount + 2, unassignProcMetrics.getSubmittedCounter().getCount()); 219 assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount()); 220 } 221 222 @Test 223 public void testAssignThrowsWithASplitParent() throws Exception { 224 RegionInfo splitParent = RegionInfoBuilder.newBuilder(TableName.valueOf("test-split-split")) 225 .setSplit(true).setOffline(true).build(); 226 RegionStateNode rsn = am.getRegionStates().getOrCreateRegionStateNode(splitParent); 227 rsn.setState(State.SPLIT); 228 229 assertThrows(DoNotRetryIOException.class, () -> am.assign(splitParent)); 230 assertNull(am.createOneAssignProcedure(splitParent, true, false)); 231 } 232 233 // Simulate the pre-fix failover scenario: regionInfo.isSplit()=true but state=OFFLINE because 234 // the old code path did not write SPLIT to info:state, causing loadMeta to fall back to OFFLINE. 235 // See HBASE-30353. 236 @Test 237 public void testAssignThrowsWithASplitParentInOfflineState() throws Exception { 238 RegionInfo splitParent = RegionInfoBuilder.newBuilder(TableName.valueOf("test-split-offline")) 239 .setSplit(true).setOffline(true).build(); 240 RegionStateNode rsn = am.getRegionStates().getOrCreateRegionStateNode(splitParent); 241 rsn.setState(State.OFFLINE); 242 243 assertThrows(DoNotRetryIOException.class, () -> am.assign(splitParent)); 244 assertNull(am.createOneAssignProcedure(splitParent, true, false)); 245 } 246 247 /** 248 * It is possible that when AM send assign meta request to a RS successfully, but RS can not send 249 * back any response, which cause master startup hangs forever 250 */ 251 @Test 252 public void testAssignMetaAndCrashBeforeResponse() throws Exception { 253 tearDown(); 254 // See setUp(), start HBase until set up meta 255 util = new HBaseTestingUtil(); 256 this.executor = Executors.newSingleThreadScheduledExecutor(); 257 setupConfiguration(util.getConfiguration()); 258 master = new MockMasterServices(util.getConfiguration()); 259 rsDispatcher = new MockRSProcedureDispatcher(master); 260 master.start(NSERVERS, rsDispatcher); 261 am = master.getAssignmentManager(); 262 263 // Assign meta 264 rsDispatcher.setMockRsExecutor(new HangThenRSRestartExecutor()); 265 am.assign(RegionInfoBuilder.FIRST_META_REGIONINFO); 266 assertEquals(true, am.isMetaAssigned()); 267 268 // set it back as default, see setUpMeta() 269 am.wakeMetaLoadedEvent(); 270 } 271 272 private void assertCloseThenOpen() { 273 assertEquals(closeSubmittedCount + 1, closeProcMetrics.getSubmittedCounter().getCount()); 274 assertEquals(closeFailedCount, closeProcMetrics.getFailedCounter().getCount()); 275 assertEquals(openSubmittedCount + 1, openProcMetrics.getSubmittedCounter().getCount()); 276 assertEquals(openFailedCount, openProcMetrics.getFailedCounter().getCount()); 277 } 278 279 @Test 280 public void testMove() throws Exception { 281 TableName tableName = TableName.valueOf("testMove"); 282 RegionInfo hri = createRegionInfo(tableName, 1); 283 rsDispatcher.setMockRsExecutor(new GoodRsExecutor()); 284 am.assign(hri); 285 286 // collect AM metrics before test 287 collectAssignmentManagerMetrics(); 288 289 am.move(hri); 290 291 assertEquals(moveSubmittedCount + 1, moveProcMetrics.getSubmittedCounter().getCount()); 292 assertEquals(moveFailedCount, moveProcMetrics.getFailedCounter().getCount()); 293 assertCloseThenOpen(); 294 } 295 296 @Test 297 public void testReopen() throws Exception { 298 TableName tableName = TableName.valueOf("testReopen"); 299 RegionInfo hri = createRegionInfo(tableName, 1); 300 rsDispatcher.setMockRsExecutor(new GoodRsExecutor()); 301 am.assign(hri); 302 303 // collect AM metrics before test 304 collectAssignmentManagerMetrics(); 305 306 TransitRegionStateProcedure proc = 307 TransitRegionStateProcedure.reopen(master.getMasterProcedureExecutor().getEnvironment(), hri); 308 am.getRegionStates().getRegionStateNode(hri).setProcedure(proc); 309 waitOnFuture(submitProcedure(proc)); 310 311 assertEquals(reopenSubmittedCount + 1, reopenProcMetrics.getSubmittedCounter().getCount()); 312 assertEquals(reopenFailedCount, reopenProcMetrics.getFailedCounter().getCount()); 313 assertCloseThenOpen(); 314 } 315 316 @Test 317 public void testLoadRegionFromMetaAfterRegionManuallyAdded() throws Exception { 318 try { 319 this.util.startMiniCluster(); 320 final AssignmentManager am = this.util.getHBaseCluster().getMaster().getAssignmentManager(); 321 final TableName tableName = 322 TableName.valueOf("testLoadRegionFromMetaAfterRegionManuallyAdded"); 323 this.util.createTable(tableName, "f"); 324 RegionInfo hri = createRegionInfo(tableName, 1); 325 assertNull(am.getRegionStates().getRegionState(hri), "RegionInfo was just instantiated by " 326 + "the test, but shouldn't be in AM regionStates yet."); 327 MetaTableAccessor.addRegionsToMeta(this.util.getConnection(), Collections.singletonList(hri), 328 1); 329 // TODO: is there a race here -- no other thread else will refresh the table states behind 330 // the scenes? 331 assertNull(am.getRegionStates().getRegionState(hri), 332 "RegionInfo was manually added in META, but shouldn't be in AM regionStates yet."); 333 am.populateRegionStatesFromMeta(hri.getEncodedName()); 334 assertNotNull(am.getRegionInfo(hri.getRegionName())); 335 assertNotNull(am.getRegionInfo(hri.getEncodedName())); 336 } finally { 337 this.util.killMiniHBaseCluster(); 338 } 339 } 340 341 @Test 342 public void testLoadRegionFromMetaRegionNotInMeta() throws Exception { 343 try { 344 this.util.startMiniCluster(); 345 final AssignmentManager am = this.util.getHBaseCluster().getMaster().getAssignmentManager(); 346 final TableName tableName = TableName.valueOf("testLoadRegionFromMetaRegionNotInMeta"); 347 this.util.createTable(tableName, "f"); 348 final RegionInfo hri = createRegionInfo(tableName, 1); 349 assertNull(am.getRegionStates().getRegionState(hri), 350 "Bogus RegionInfo discovered in RegionStates."); 351 am.populateRegionStatesFromMeta(hri.getEncodedName()); 352 assertNull(am.getRegionStates().getRegionState(hri), "RegionInfo was never added in META"); 353 } finally { 354 this.util.killMiniHBaseCluster(); 355 } 356 } 357}