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.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022import static org.junit.Assert.fail; 023 024import java.io.IOException; 025import java.util.concurrent.Executors; 026import java.util.concurrent.Future; 027import org.apache.hadoop.hbase.DoNotRetryIOException; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HBaseTestingUtility; 030import org.apache.hadoop.hbase.ServerName; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.RegionInfo; 033import org.apache.hadoop.hbase.client.RegionInfoBuilder; 034import org.apache.hadoop.hbase.client.RetriesExhaustedException; 035import org.apache.hadoop.hbase.exceptions.UnexpectedStateException; 036import org.apache.hadoop.hbase.master.RegionState.State; 037import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 038import org.apache.hadoop.hbase.procedure2.util.StringUtils; 039import org.apache.hadoop.hbase.testclassification.LargeTests; 040import org.apache.hadoop.hbase.testclassification.MasterTests; 041import org.junit.ClassRule; 042import org.junit.Ignore; 043import org.junit.Test; 044import org.junit.experimental.categories.Category; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048@Category({MasterTests.class, LargeTests.class}) 049public class TestAssignmentManager extends TestAssignmentManagerBase { 050 private static final Logger LOG = LoggerFactory.getLogger(TestAssignmentManager.class); 051 052 @ClassRule 053 public static final HBaseClassTestRule CLASS_RULE = 054 HBaseClassTestRule.forClass(TestAssignmentManager.class); 055 056 @Test(expected = NullPointerException.class) 057 public void testWaitServerReportEventWithNullServer() throws UnexpectedStateException { 058 // Test what happens if we pass in null server. I'd expect it throws NPE. 059 if (this.am.waitServerReportEvent(null, null)) { 060 throw new UnexpectedStateException(); 061 } 062 } 063 064 @Test 065 public void testAssignWithGoodExec() throws Exception { 066 // collect AM metrics before test 067 collectAssignmentManagerMetrics(); 068 069 testAssign(new GoodRsExecutor()); 070 071 assertEquals(assignSubmittedCount + NREGIONS, 072 assignProcMetrics.getSubmittedCounter().getCount()); 073 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 074 } 075 076 @Test 077 public void testAssignAndCrashBeforeResponse() throws Exception { 078 final TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse"); 079 final RegionInfo hri = createRegionInfo(tableName, 1); 080 rsDispatcher.setMockRsExecutor(new HangThenRSCrashExecutor()); 081 AssignProcedure proc = am.createAssignProcedure(hri); 082 waitOnFuture(submitProcedure(proc)); 083 } 084 085 @Test 086 public void testUnassignAndCrashBeforeResponse() throws Exception { 087 final TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse"); 088 final RegionInfo hri = createRegionInfo(tableName, 1); 089 rsDispatcher.setMockRsExecutor(new HangOnCloseThenRSCrashExecutor()); 090 for (int i = 0; i < HangOnCloseThenRSCrashExecutor.TYPES_OF_FAILURE; i++) { 091 AssignProcedure assign = am.createAssignProcedure(hri); 092 waitOnFuture(submitProcedure(assign)); 093 UnassignProcedure unassign = am.createUnassignProcedure(hri, 094 am.getRegionStates().getRegionServerOfRegion(hri), false); 095 waitOnFuture(submitProcedure(unassign)); 096 } 097 } 098 099 @Test 100 public void testAssignWithRandExec() throws Exception { 101 final TableName tableName = TableName.valueOf("testAssignWithRandExec"); 102 final RegionInfo hri = createRegionInfo(tableName, 1); 103 104 rsDispatcher.setMockRsExecutor(new RandRsExecutor()); 105 // Loop a bunch of times so we hit various combos of exceptions. 106 for (int i = 0; i < 10; i++) { 107 LOG.info("ROUND=" + i); 108 AssignProcedure proc = am.createAssignProcedure(hri); 109 waitOnFuture(submitProcedure(proc)); 110 } 111 } 112 113 @Ignore @Test // Disabled for now. Since HBASE-18551, this mock is insufficient. 114 public void testSocketTimeout() throws Exception { 115 final TableName tableName = TableName.valueOf(this.name.getMethodName()); 116 final RegionInfo hri = createRegionInfo(tableName, 1); 117 118 // collect AM metrics before test 119 collectAssignmentManagerMetrics(); 120 121 rsDispatcher.setMockRsExecutor(new SocketTimeoutRsExecutor(20, 3)); 122 waitOnFuture(submitProcedure(am.createAssignProcedure(hri))); 123 124 rsDispatcher.setMockRsExecutor(new SocketTimeoutRsExecutor(20, 1)); 125 // exception.expect(ServerCrashException.class); 126 waitOnFuture(submitProcedure(am.createUnassignProcedure(hri, null, false))); 127 128 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 129 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 130 assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount()); 131 assertEquals(unassignFailedCount + 1, unassignProcMetrics.getFailedCounter().getCount()); 132 } 133 134 @Test 135 public void testServerNotYetRunning() throws Exception { 136 testRetriesExhaustedFailure(TableName.valueOf(this.name.getMethodName()), 137 new ServerNotYetRunningRsExecutor()); 138 } 139 140 private void testRetriesExhaustedFailure(final TableName tableName, 141 final MockRSExecutor executor) throws Exception { 142 final RegionInfo hri = createRegionInfo(tableName, 1); 143 144 // collect AM metrics before test 145 collectAssignmentManagerMetrics(); 146 147 // Test Assign operation failure 148 rsDispatcher.setMockRsExecutor(executor); 149 try { 150 waitOnFuture(submitProcedure(am.createAssignProcedure(hri))); 151 fail("unexpected assign completion"); 152 } catch (RetriesExhaustedException e) { 153 // expected exception 154 LOG.info("expected exception from assign operation: " + e.getMessage(), e); 155 } 156 157 // Assign the region (without problems) 158 rsDispatcher.setMockRsExecutor(new GoodRsExecutor()); 159 waitOnFuture(submitProcedure(am.createAssignProcedure(hri))); 160 161 // TODO: Currently unassign just keeps trying until it sees a server crash. 162 // There is no count on unassign. 163 /* 164 // Test Unassign operation failure 165 rsDispatcher.setMockRsExecutor(executor); 166 waitOnFuture(submitProcedure(am.createUnassignProcedure(hri, null, false))); 167 168 assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount()); 169 assertEquals(assignFailedCount + 1, assignProcMetrics.getFailedCounter().getCount()); 170 assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount()); 171 172 // TODO: We supposed to have 1 failed assign, 1 successful assign and a failed unassign 173 // operation. But ProcV2 framework marks aborted unassign operation as success. Fix it! 174 assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount()); 175 */ 176 } 177 178 179 @Test 180 public void testIOExceptionOnAssignment() throws Exception { 181 // collect AM metrics before test 182 collectAssignmentManagerMetrics(); 183 184 testFailedOpen(TableName.valueOf("testExceptionOnAssignment"), 185 new FaultyRsExecutor(new IOException("test fault"))); 186 187 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 188 assertEquals(assignFailedCount + 1, assignProcMetrics.getFailedCounter().getCount()); 189 } 190 191 @Test 192 public void testDoNotRetryExceptionOnAssignment() throws Exception { 193 // collect AM metrics before test 194 collectAssignmentManagerMetrics(); 195 196 testFailedOpen(TableName.valueOf("testDoNotRetryExceptionOnAssignment"), 197 new FaultyRsExecutor(new DoNotRetryIOException("test do not retry fault"))); 198 199 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 200 assertEquals(assignFailedCount + 1, assignProcMetrics.getFailedCounter().getCount()); 201 } 202 203 private void testFailedOpen(final TableName tableName, 204 final MockRSExecutor executor) throws Exception { 205 final RegionInfo hri = createRegionInfo(tableName, 1); 206 207 // Test Assign operation failure 208 rsDispatcher.setMockRsExecutor(executor); 209 try { 210 waitOnFuture(submitProcedure(am.createAssignProcedure(hri))); 211 fail("unexpected assign completion"); 212 } catch (RetriesExhaustedException e) { 213 // expected exception 214 LOG.info("REGION STATE " + am.getRegionStates().getRegionStateNode(hri)); 215 LOG.info("expected exception from assign operation: " + e.getMessage(), e); 216 assertEquals(true, am.getRegionStates().getRegionState(hri).isFailedOpen()); 217 } 218 } 219 220 private void testAssign(final MockRSExecutor executor) throws Exception { 221 testAssign(executor, NREGIONS); 222 } 223 224 private void testAssign(final MockRSExecutor executor, final int nregions) throws Exception { 225 rsDispatcher.setMockRsExecutor(executor); 226 227 AssignProcedure[] assignments = new AssignProcedure[nregions]; 228 229 long st = System.currentTimeMillis(); 230 bulkSubmit(assignments); 231 232 for (int i = 0; i < assignments.length; ++i) { 233 ProcedureTestingUtility.waitProcedure( 234 master.getMasterProcedureExecutor(), assignments[i]); 235 assertTrue(assignments[i].toString(), assignments[i].isSuccess()); 236 } 237 long et = System.currentTimeMillis(); 238 float sec = ((et - st) / 1000.0f); 239 LOG.info(String.format("[T] Assigning %dprocs in %s (%.2fproc/sec)", 240 assignments.length, StringUtils.humanTimeDiff(et - st), assignments.length / sec)); 241 } 242 243 @Test 244 public void testAssignAnAssignedRegion() throws Exception { 245 final TableName tableName = TableName.valueOf("testAssignAnAssignedRegion"); 246 final RegionInfo hri = createRegionInfo(tableName, 1); 247 248 // collect AM metrics before test 249 collectAssignmentManagerMetrics(); 250 251 rsDispatcher.setMockRsExecutor(new GoodRsExecutor()); 252 253 final Future<byte[]> futureA = submitProcedure(am.createAssignProcedure(hri)); 254 255 // wait first assign 256 waitOnFuture(futureA); 257 am.getRegionStates().isRegionInState(hri, State.OPEN); 258 // Second should be a noop. We should recognize region is already OPEN internally 259 // and skip out doing nothing. 260 // wait second assign 261 final Future<byte[]> futureB = submitProcedure(am.createAssignProcedure(hri)); 262 waitOnFuture(futureB); 263 am.getRegionStates().isRegionInState(hri, State.OPEN); 264 // TODO: What else can we do to ensure just a noop. 265 266 // TODO: Though second assign is noop, it's considered success, can noop be handled in a 267 // better way? 268 assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount()); 269 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 270 271 } 272 273 @Test 274 public void testUnassignAnUnassignedRegion() throws Exception { 275 final TableName tableName = TableName.valueOf("testUnassignAnUnassignedRegion"); 276 final RegionInfo hri = createRegionInfo(tableName, 1); 277 278 // collect AM metrics before test 279 collectAssignmentManagerMetrics(); 280 281 rsDispatcher.setMockRsExecutor(new GoodRsExecutor()); 282 283 // assign the region first 284 waitOnFuture(submitProcedure(am.createAssignProcedure(hri))); 285 286 final Future<byte[]> futureA = submitProcedure(am.createUnassignProcedure(hri, null, false)); 287 288 // Wait first unassign. 289 waitOnFuture(futureA); 290 am.getRegionStates().isRegionInState(hri, State.CLOSED); 291 // Second should be a noop. We should recognize region is already CLOSED internally 292 // and skip out doing nothing. 293 final Future<byte[]> futureB = 294 submitProcedure(am.createUnassignProcedure(hri, 295 ServerName.valueOf("example.org,1234,1"), false)); 296 waitOnFuture(futureB); 297 // Ensure we are still CLOSED. 298 am.getRegionStates().isRegionInState(hri, State.CLOSED); 299 // TODO: What else can we do to ensure just a noop. 300 301 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 302 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 303 // TODO: Though second unassign is noop, it's considered success, can noop be handled in a 304 // better way? 305 assertEquals(unassignSubmittedCount + 2, unassignProcMetrics.getSubmittedCounter().getCount()); 306 assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount()); 307 } 308 309 /** 310 * It is possible that when AM send assign meta request to a RS successfully, 311 * but RS can not send back any response, which cause master startup hangs forever 312 */ 313 @Test 314 public void testAssignMetaAndCrashBeforeResponse() throws Exception { 315 tearDown(); 316 // See setUp(), start HBase until set up meta 317 UTIL = new HBaseTestingUtility(); 318 this.executor = Executors.newSingleThreadScheduledExecutor(); 319 setupConfiguration(UTIL.getConfiguration()); 320 master = new MockMasterServices(UTIL.getConfiguration(), this.regionsToRegionServers); 321 rsDispatcher = new MockRSProcedureDispatcher(master); 322 master.start(NSERVERS, rsDispatcher); 323 am = master.getAssignmentManager(); 324 325 // Assign meta 326 rsDispatcher.setMockRsExecutor(new HangThenRSRestartExecutor()); 327 am.assign(RegionInfoBuilder.FIRST_META_REGIONINFO); 328 assertEquals(true, am.isMetaAssigned()); 329 330 // set it back as default, see setUpMeta() 331 am.wakeMetaLoadedEvent(); 332 } 333 334 @Test 335 public void testAssignQueueFullOnce() throws Exception { 336 TableName tableName = TableName.valueOf(this.name.getMethodName()); 337 RegionInfo hri = createRegionInfo(tableName, 1); 338 339 // collect AM metrics before test 340 collectAssignmentManagerMetrics(); 341 342 rsDispatcher.setMockRsExecutor(new CallQueueTooBigOnceRsExecutor()); 343 waitOnFuture(submitProcedure(am.createAssignProcedure(hri))); 344 345 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 346 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 347 } 348 349 @Test 350 public void testTimeoutThenQueueFull() throws Exception { 351 TableName tableName = TableName.valueOf(this.name.getMethodName()); 352 RegionInfo hri = createRegionInfo(tableName, 1); 353 354 // collect AM metrics before test 355 collectAssignmentManagerMetrics(); 356 357 rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(10)); 358 waitOnFuture(submitProcedure(am.createAssignProcedure(hri))); 359 rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(15)); 360 waitOnFuture(submitProcedure(am.createUnassignProcedure(hri))); 361 362 assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount()); 363 assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount()); 364 assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount()); 365 assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount()); 366 } 367}