blob: d36274c5f043fb34d7e8d4ec879cfeee1bb3878e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.master.assignment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.MetaTableAccessor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionInfoBuilder;
import org.apache.hadoop.hbase.master.RegionState.State;
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
import org.apache.hadoop.hbase.procedure2.util.StringUtils;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Category({ MasterTests.class, LargeTests.class })
public class TestAssignmentManager extends TestAssignmentManagerBase {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestAssignmentManager.class);
private static final Logger LOG = LoggerFactory.getLogger(TestAssignmentManager.class);
@Test
public void testAssignWithGoodExec() throws Exception {
// collect AM metrics before test
collectAssignmentManagerMetrics();
testAssign(new GoodRsExecutor());
assertEquals(assignSubmittedCount + NREGIONS,
assignProcMetrics.getSubmittedCounter().getCount());
assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
}
@Test
public void testAssignAndCrashBeforeResponse() throws Exception {
TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse");
RegionInfo hri = createRegionInfo(tableName, 1);
rsDispatcher.setMockRsExecutor(new HangThenRSCrashExecutor());
TransitRegionStateProcedure proc = createAssignProcedure(hri);
waitOnFuture(submitProcedure(proc));
}
@Test
public void testUnassignAndCrashBeforeResponse() throws Exception {
TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse");
RegionInfo hri = createRegionInfo(tableName, 1);
rsDispatcher.setMockRsExecutor(new HangOnCloseThenRSCrashExecutor());
for (int i = 0; i < HangOnCloseThenRSCrashExecutor.TYPES_OF_FAILURE; i++) {
TransitRegionStateProcedure assign = createAssignProcedure(hri);
waitOnFuture(submitProcedure(assign));
TransitRegionStateProcedure unassign = createUnassignProcedure(hri);
waitOnFuture(submitProcedure(unassign));
}
}
@Test
public void testAssignSocketTimeout() throws Exception {
TableName tableName = TableName.valueOf(this.name.getMethodName());
RegionInfo hri = createRegionInfo(tableName, 1);
// collect AM metrics before test
collectAssignmentManagerMetrics();
rsDispatcher.setMockRsExecutor(new SocketTimeoutRsExecutor(20));
waitOnFuture(submitProcedure(createAssignProcedure(hri)));
// we crashed a rs, so it is possible that there are other regions on the rs which will also be
// reassigned, so here we just assert greater than, not the exact number.
assertTrue(assignProcMetrics.getSubmittedCounter().getCount() > assignSubmittedCount);
assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
}
@Test
public void testAssignQueueFullOnce() throws Exception {
TableName tableName = TableName.valueOf(this.name.getMethodName());
RegionInfo hri = createRegionInfo(tableName, 1);
// collect AM metrics before test
collectAssignmentManagerMetrics();
rsDispatcher.setMockRsExecutor(new CallQueueTooBigOnceRsExecutor());
waitOnFuture(submitProcedure(createAssignProcedure(hri)));
assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
}
@Test
public void testTimeoutThenQueueFull() throws Exception {
TableName tableName = TableName.valueOf(this.name.getMethodName());
RegionInfo hri = createRegionInfo(tableName, 1);
// collect AM metrics before test
collectAssignmentManagerMetrics();
rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(10));
waitOnFuture(submitProcedure(createAssignProcedure(hri)));
rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(15));
waitOnFuture(submitProcedure(createUnassignProcedure(hri)));
assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount());
assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
}
private void testAssign(final MockRSExecutor executor) throws Exception {
testAssign(executor, NREGIONS);
}
private void testAssign(MockRSExecutor executor, int nRegions) throws Exception {
rsDispatcher.setMockRsExecutor(executor);
TransitRegionStateProcedure[] assignments = new TransitRegionStateProcedure[nRegions];
long st = EnvironmentEdgeManager.currentTime();
bulkSubmit(assignments);
for (int i = 0; i < assignments.length; ++i) {
ProcedureTestingUtility.waitProcedure(master.getMasterProcedureExecutor(), assignments[i]);
assertTrue(assignments[i].toString(), assignments[i].isSuccess());
}
long et = EnvironmentEdgeManager.currentTime();
float sec = ((et - st) / 1000.0f);
LOG.info(String.format("[T] Assigning %dprocs in %s (%.2fproc/sec)", assignments.length,
StringUtils.humanTimeDiff(et - st), assignments.length / sec));
}
@Test
public void testAssignAnAssignedRegion() throws Exception {
final TableName tableName = TableName.valueOf("testAssignAnAssignedRegion");
final RegionInfo hri = createRegionInfo(tableName, 1);
// collect AM metrics before test
collectAssignmentManagerMetrics();
rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
Future<byte[]> futureA = submitProcedure(createAssignProcedure(hri));
// wait first assign
waitOnFuture(futureA);
am.getRegionStates().isRegionInState(hri, State.OPEN);
// Second should be a noop. We should recognize region is already OPEN internally
// and skip out doing nothing.
// wait second assign
Future<byte[]> futureB = submitProcedure(createAssignProcedure(hri));
waitOnFuture(futureB);
am.getRegionStates().isRegionInState(hri, State.OPEN);
// TODO: What else can we do to ensure just a noop.
// TODO: Though second assign is noop, it's considered success, can noop be handled in a
// better way?
assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount());
assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
}
@Test
public void testUnassignAnUnassignedRegion() throws Exception {
final TableName tableName = TableName.valueOf("testUnassignAnUnassignedRegion");
final RegionInfo hri = createRegionInfo(tableName, 1);
// collect AM metrics before test
collectAssignmentManagerMetrics();
rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
// assign the region first
waitOnFuture(submitProcedure(createAssignProcedure(hri)));
final Future<byte[]> futureA = submitProcedure(createUnassignProcedure(hri));
// Wait first unassign.
waitOnFuture(futureA);
am.getRegionStates().isRegionInState(hri, State.CLOSED);
// Second should be a noop. We should recognize region is already CLOSED internally
// and skip out doing nothing.
final Future<byte[]> futureB = submitProcedure(createUnassignProcedure(hri));
waitOnFuture(futureB);
// Ensure we are still CLOSED.
am.getRegionStates().isRegionInState(hri, State.CLOSED);
// TODO: What else can we do to ensure just a noop.
assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
// TODO: Though second unassign is noop, it's considered success, can noop be handled in a
// better way?
assertEquals(unassignSubmittedCount + 2, unassignProcMetrics.getSubmittedCounter().getCount());
assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
}
/**
* It is possible that when AM send assign meta request to a RS successfully, but RS can not send
* back any response, which cause master startup hangs forever
*/
@Test
public void testAssignMetaAndCrashBeforeResponse() throws Exception {
tearDown();
// See setUp(), start HBase until set up meta
util = new HBaseTestingUtil();
this.executor = Executors.newSingleThreadScheduledExecutor();
setupConfiguration(util.getConfiguration());
master = new MockMasterServices(util.getConfiguration());
rsDispatcher = new MockRSProcedureDispatcher(master);
master.start(NSERVERS, rsDispatcher);
am = master.getAssignmentManager();
// Assign meta
rsDispatcher.setMockRsExecutor(new HangThenRSRestartExecutor());
am.assign(RegionInfoBuilder.FIRST_META_REGIONINFO);
assertEquals(true, am.isMetaAssigned());
// set it back as default, see setUpMeta()
am.wakeMetaLoadedEvent();
}
private void assertCloseThenOpen() {
assertEquals(closeSubmittedCount + 1, closeProcMetrics.getSubmittedCounter().getCount());
assertEquals(closeFailedCount, closeProcMetrics.getFailedCounter().getCount());
assertEquals(openSubmittedCount + 1, openProcMetrics.getSubmittedCounter().getCount());
assertEquals(openFailedCount, openProcMetrics.getFailedCounter().getCount());
}
@Test
public void testMove() throws Exception {
TableName tableName = TableName.valueOf("testMove");
RegionInfo hri = createRegionInfo(tableName, 1);
rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
am.assign(hri);
// collect AM metrics before test
collectAssignmentManagerMetrics();
am.move(hri);
assertEquals(moveSubmittedCount + 1, moveProcMetrics.getSubmittedCounter().getCount());
assertEquals(moveFailedCount, moveProcMetrics.getFailedCounter().getCount());
assertCloseThenOpen();
}
@Test
public void testReopen() throws Exception {
TableName tableName = TableName.valueOf("testReopen");
RegionInfo hri = createRegionInfo(tableName, 1);
rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
am.assign(hri);
// collect AM metrics before test
collectAssignmentManagerMetrics();
TransitRegionStateProcedure proc =
TransitRegionStateProcedure.reopen(master.getMasterProcedureExecutor().getEnvironment(), hri);
am.getRegionStates().getRegionStateNode(hri).setProcedure(proc);
waitOnFuture(submitProcedure(proc));
assertEquals(reopenSubmittedCount + 1, reopenProcMetrics.getSubmittedCounter().getCount());
assertEquals(reopenFailedCount, reopenProcMetrics.getFailedCounter().getCount());
assertCloseThenOpen();
}
@Test
public void testLoadRegionFromMetaAfterRegionManuallyAdded() throws Exception {
try {
this.util.startMiniCluster();
final AssignmentManager am = this.util.getHBaseCluster().getMaster().getAssignmentManager();
final TableName tableName =
TableName.valueOf("testLoadRegionFromMetaAfterRegionManuallyAdded");
this.util.createTable(tableName, "f");
RegionInfo hri = createRegionInfo(tableName, 1);
assertNull("RegionInfo was just instantiated by the test, but "
+ "shouldn't be in AM regionStates yet.", am.getRegionStates().getRegionState(hri));
MetaTableAccessor.addRegionsToMeta(this.util.getConnection(), Collections.singletonList(hri),
1);
// TODO: is there a race here -- no other thread else will refresh the table states behind
// the scenes?
assertNull("RegionInfo was manually added in META, but shouldn't be in AM regionStates yet.",
am.getRegionStates().getRegionState(hri));
am.populateRegionStatesFromMeta(hri.getEncodedName());
assertNotNull(am.getRegionInfo(hri.getRegionName()));
assertNotNull(am.getRegionInfo(hri.getEncodedName()));
} finally {
this.util.killMiniHBaseCluster();
}
}
@Test
public void testLoadRegionFromMetaRegionNotInMeta() throws Exception {
try {
this.util.startMiniCluster();
final AssignmentManager am = this.util.getHBaseCluster().getMaster().getAssignmentManager();
final TableName tableName = TableName.valueOf("testLoadRegionFromMetaRegionNotInMeta");
this.util.createTable(tableName, "f");
final RegionInfo hri = createRegionInfo(tableName, 1);
assertNull("Bogus RegionInfo discovered in RegionStates.",
am.getRegionStates().getRegionState(hri));
am.populateRegionStatesFromMeta(hri.getEncodedName());
assertNull("RegionInfo was never added in META", am.getRegionStates().getRegionState(hri));
} finally {
this.util.killMiniHBaseCluster();
}
}
}