HBASE-11903. Directly invoking split & merge of replica regions should be disallowed

This commit is contained in:
Devaraj Das 2014-12-07 11:56:53 -08:00
parent bb15fd5fe0
commit 9fd6db3703
5 changed files with 179 additions and 9 deletions

View File

@ -148,6 +148,7 @@ import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.util.StringUtils;
import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.ServiceException; import com.google.protobuf.ServiceException;
@ -2019,7 +2020,12 @@ public class HBaseAdmin implements Admin {
public void mergeRegions(final byte[] encodedNameOfRegionA, public void mergeRegions(final byte[] encodedNameOfRegionA,
final byte[] encodedNameOfRegionB, final boolean forcible) final byte[] encodedNameOfRegionB, final boolean forcible)
throws IOException { throws IOException {
Pair<HRegionInfo, ServerName> pair = getRegion(encodedNameOfRegionA);
if (pair != null && pair.getFirst().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID)
throw new IllegalArgumentException("Can't invoke merge on non-default regions directly");
pair = getRegion(encodedNameOfRegionB);
if (pair != null && pair.getFirst().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID)
throw new IllegalArgumentException("Can't invoke merge on non-default regions directly");
executeCallable(new MasterCallable<Void>(getConnection()) { executeCallable(new MasterCallable<Void>(getConnection()) {
@Override @Override
public Void call(int callTimeout) throws ServiceException { public Void call(int callTimeout) throws ServiceException {
@ -2098,7 +2104,8 @@ public class HBaseAdmin implements Admin {
// check for parents // check for parents
if (r.isSplitParent()) continue; if (r.isSplitParent()) continue;
// if a split point given, only split that particular region // if a split point given, only split that particular region
if (splitPoint != null && !r.containsRow(splitPoint)) continue; if (r.getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID ||
(splitPoint != null && !r.containsRow(splitPoint))) continue;
// call out to region server to do split now // call out to region server to do split now
split(pair.getSecond(), pair.getFirst(), splitPoint); split(pair.getSecond(), pair.getFirst(), splitPoint);
} }
@ -2119,6 +2126,11 @@ public class HBaseAdmin implements Admin {
if (regionServerPair == null) { if (regionServerPair == null) {
throw new IllegalArgumentException("Invalid region: " + Bytes.toStringBinary(regionName)); throw new IllegalArgumentException("Invalid region: " + Bytes.toStringBinary(regionName));
} }
if (regionServerPair.getFirst() != null &&
regionServerPair.getFirst().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) {
throw new IllegalArgumentException("Can't split replicas directly. "
+ "Replicas are auto-split when their primary is split.");
}
if (regionServerPair.getSecond() == null) { if (regionServerPair.getSecond() == null) {
throw new NoServerForRegionException(Bytes.toStringBinary(regionName)); throw new NoServerForRegionException(Bytes.toStringBinary(regionName));
} }
@ -2150,7 +2162,8 @@ public class HBaseAdmin implements Admin {
} }
} }
private void split(final ServerName sn, final HRegionInfo hri, @VisibleForTesting
public void split(final ServerName sn, final HRegionInfo hri,
byte[] splitPoint) throws IOException { byte[] splitPoint) throws IOException {
if (hri.getStartKey() != null && splitPoint != null && if (hri.getStartKey() != null && splitPoint != null &&
Bytes.compareTo(hri.getStartKey(), splitPoint) == 0) { Bytes.compareTo(hri.getStartKey(), splitPoint) == 0) {
@ -2225,8 +2238,17 @@ public class HBaseAdmin implements Admin {
LOG.warn("No serialized HRegionInfo in " + data); LOG.warn("No serialized HRegionInfo in " + data);
return true; return true;
} }
if (!encodedName.equals(info.getEncodedName())) return true; RegionLocations rl = MetaTableAccessor.getRegionLocations(data);
ServerName sn = HRegionInfo.getServerName(data); boolean matched = false;
ServerName sn = null;
for (HRegionLocation h : rl.getRegionLocations()) {
if (h != null && encodedName.equals(h.getRegionInfo().getEncodedName())) {
sn = h.getServerName();
info = h.getRegionInfo();
matched = true;
}
}
if (!matched) return true;
result.set(new Pair<HRegionInfo, ServerName>(info, sn)); result.set(new Pair<HRegionInfo, ServerName>(info, sn));
return false; // found the region, stop return false; // found the region, stop
} }

View File

@ -529,6 +529,10 @@ public class MasterRpcServices extends RSRpcServices
HRegionInfo regionInfoA = regionStateA.getRegion(); HRegionInfo regionInfoA = regionStateA.getRegion();
HRegionInfo regionInfoB = regionStateB.getRegion(); HRegionInfo regionInfoB = regionStateB.getRegion();
if (regionInfoA.getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID ||
regionInfoB.getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) {
throw new ServiceException(new MergeRegionException("Can't merge non-default replicas"));
}
if (regionInfoA.compareTo(regionInfoB) == 0) { if (regionInfoA.compareTo(regionInfoB) == 0) {
throw new ServiceException(new MergeRegionException( throw new ServiceException(new MergeRegionException(
"Unable to merge a region to itself " + regionInfoA + ", " + regionInfoB)); "Unable to merge a region to itself " + regionInfoA + ", " + regionInfoB));

View File

@ -66,6 +66,7 @@ import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.RowMutations; import org.apache.hadoop.hbase.client.RowMutations;
import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException; import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException;
import org.apache.hadoop.hbase.exceptions.MergeRegionException;
import org.apache.hadoop.hbase.exceptions.OperationConflictException; import org.apache.hadoop.hbase.exceptions.OperationConflictException;
import org.apache.hadoop.hbase.exceptions.OutOfOrderScannerNextException; import org.apache.hadoop.hbase.exceptions.OutOfOrderScannerNextException;
import org.apache.hadoop.hbase.filter.ByteArrayComparable; import org.apache.hadoop.hbase.filter.ByteArrayComparable;
@ -1212,6 +1213,10 @@ public class RSRpcServices implements HBaseRPCErrorHandler,
boolean forcible = request.getForcible(); boolean forcible = request.getForcible();
regionA.startRegionOperation(Operation.MERGE_REGION); regionA.startRegionOperation(Operation.MERGE_REGION);
regionB.startRegionOperation(Operation.MERGE_REGION); regionB.startRegionOperation(Operation.MERGE_REGION);
if (regionA.getRegionInfo().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID ||
regionB.getRegionInfo().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) {
throw new ServiceException(new MergeRegionException("Can't merge non-default replicas"));
}
LOG.info("Receiving merging request for " + regionA + ", " + regionB LOG.info("Receiving merging request for " + regionA + ", " + regionB
+ ",forcible=" + forcible); + ",forcible=" + forcible);
long startTime = EnvironmentEdgeManager.currentTime(); long startTime = EnvironmentEdgeManager.currentTime();
@ -1556,6 +1561,10 @@ public class RSRpcServices implements HBaseRPCErrorHandler,
requestCount.increment(); requestCount.increment();
HRegion region = getRegion(request.getRegion()); HRegion region = getRegion(request.getRegion());
region.startRegionOperation(Operation.SPLIT_REGION); region.startRegionOperation(Operation.SPLIT_REGION);
if (region.getRegionInfo().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) {
throw new IOException("Can't split replicas directly. "
+ "Replicas are auto-split when their primary is split.");
}
LOG.info("Splitting " + region.getRegionNameAsString()); LOG.info("Splitting " + region.getRegionNameAsString());
long startTime = EnvironmentEdgeManager.currentTime(); long startTime = EnvironmentEdgeManager.currentTime();
HRegion.FlushResult flushResult = region.flushcache(); HRegion.FlushResult flushResult = region.flushcache();

View File

@ -40,14 +40,25 @@ import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.InvalidFamilyOperationException; import org.apache.hadoop.hbase.InvalidFamilyOperationException;
import org.apache.hadoop.hbase.MasterNotRunningException;
import org.apache.hadoop.hbase.MetaTableAccessor;
import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.TableNotDisabledException; import org.apache.hadoop.hbase.TableNotDisabledException;
import org.apache.hadoop.hbase.TableNotEnabledException; import org.apache.hadoop.hbase.TableNotEnabledException;
import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.ZooKeeperConnectionException;
import org.apache.hadoop.hbase.exceptions.MergeRegionException;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.protobuf.RequestConverter;
import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.AdminService;
import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.DispatchMergingRegionsRequest;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.testclassification.ClientTests; import org.apache.hadoop.hbase.testclassification.ClientTests;
import org.apache.hadoop.hbase.testclassification.LargeTests; import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
@ -56,6 +67,8 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
import com.google.protobuf.ServiceException;
/** /**
* Class to test HBaseAdmin. * Class to test HBaseAdmin.
* Spins up the minicluster once at test start and then takes it down afterward. * Spins up the minicluster once at test start and then takes it down afterward.
@ -1069,6 +1082,126 @@ public class TestAdmin1 {
table.close(); table.close();
} }
@Test
public void testSplitAndMergeWithReplicaTable() throws Exception {
// The test tries to directly split replica regions and directly merge replica regions. These
// are not allowed. The test validates that. Then the test does a valid split/merge of allowed
// regions.
// Set up a table with 3 regions and replication set to 3
TableName tableName = TableName.valueOf("testSplitAndMergeWithReplicaTable");
HTableDescriptor desc = new HTableDescriptor(tableName);
desc.setRegionReplication(3);
byte[] cf = "f".getBytes();
HColumnDescriptor hcd = new HColumnDescriptor(cf);
desc.addFamily(hcd);
byte[][] splitRows = new byte[2][];
splitRows[0] = new byte[]{(byte)'4'};
splitRows[1] = new byte[]{(byte)'7'};
TEST_UTIL.getHBaseAdmin().createTable(desc, splitRows);
List<HRegion> oldRegions;
do {
oldRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
Thread.sleep(10);
} while (oldRegions.size() != 9); //3 regions * 3 replicas
// write some data to the table
HTable ht = new HTable(TEST_UTIL.getConfiguration(), tableName);
List<Put> puts = new ArrayList<Put>();
byte[] qualifier = "c".getBytes();
Put put = new Put(new byte[]{(byte)'1'});
put.add(cf, qualifier, "100".getBytes());
puts.add(put);
put = new Put(new byte[]{(byte)'6'});
put.add(cf, qualifier, "100".getBytes());
puts.add(put);
put = new Put(new byte[]{(byte)'8'});
put.add(cf, qualifier, "100".getBytes());
puts.add(put);
ht.put(puts);
ht.flushCommits();
ht.close();
List<Pair<HRegionInfo, ServerName>> regions =
MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tableName);
boolean gotException = false;
// the element at index 1 would be a replica (since the metareader gives us ordered
// regions). Try splitting that region via the split API . Should fail
try {
TEST_UTIL.getHBaseAdmin().split(regions.get(1).getFirst().getRegionName());
} catch (IllegalArgumentException ex) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
// the element at index 1 would be a replica (since the metareader gives us ordered
// regions). Try splitting that region via a different split API (the difference is
// this API goes direct to the regionserver skipping any checks in the admin). Should fail
try {
TEST_UTIL.getHBaseAdmin().split(regions.get(1).getSecond(), regions.get(1).getFirst(),
new byte[]{(byte)'1'});
} catch (IOException ex) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
// Try merging a replica with another. Should fail.
try {
TEST_UTIL.getHBaseAdmin().mergeRegions(regions.get(1).getFirst().getEncodedNameAsBytes(),
regions.get(2).getFirst().getEncodedNameAsBytes(), true);
} catch (IllegalArgumentException m) {
gotException = true;
}
assertTrue(gotException);
// Try going to the master directly (that will skip the check in admin)
try {
DispatchMergingRegionsRequest request = RequestConverter
.buildDispatchMergingRegionsRequest(regions.get(1).getFirst().getEncodedNameAsBytes(),
regions.get(2).getFirst().getEncodedNameAsBytes(), true);
TEST_UTIL.getHBaseAdmin().getConnection().getMaster().dispatchMergingRegions(null, request);
} catch (ServiceException m) {
Throwable t = m.getCause();
do {
if (t instanceof MergeRegionException) {
gotException = true;
break;
}
t = t.getCause();
} while (t != null);
}
assertTrue(gotException);
gotException = false;
// Try going to the regionservers directly
// first move the region to the same regionserver
if (!regions.get(2).getSecond().equals(regions.get(1).getSecond())) {
moveRegionAndWait(regions.get(2).getFirst(), regions.get(1).getSecond());
}
try {
AdminService.BlockingInterface admin = TEST_UTIL.getHBaseAdmin().getConnection()
.getAdmin(regions.get(1).getSecond());
ProtobufUtil.mergeRegions(admin, regions.get(1).getFirst(), regions.get(2).getFirst(), true);
} catch (MergeRegionException mm) {
gotException = true;
}
assertTrue(gotException);
}
private void moveRegionAndWait(HRegionInfo destRegion, ServerName destServer)
throws InterruptedException, MasterNotRunningException,
ZooKeeperConnectionException, IOException {
HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
TEST_UTIL.getHBaseAdmin().move(
destRegion.getEncodedNameAsBytes(),
Bytes.toBytes(destServer.getServerName()));
while (true) {
ServerName serverName = master.getAssignmentManager()
.getRegionStates().getRegionServerOfRegion(destRegion);
if (serverName != null && serverName.equals(destServer)) {
TEST_UTIL.assertRegionOnServer(
destRegion, serverName, 200);
break;
}
Thread.sleep(10);
}
}
/** /**
* HADOOP-2156 * HADOOP-2156
* @throws IOException * @throws IOException

View File

@ -48,6 +48,7 @@ import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.RunCatalogScanReq
import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetBalancerRunningRequest; import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetBalancerRunningRequest;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
import org.mockito.Matchers;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@ -260,7 +261,6 @@ public class TestHBaseAdminNoCluster {
(IsCatalogJanitorEnabledRequest)Mockito.any()); (IsCatalogJanitorEnabledRequest)Mockito.any());
} }
}); });
// Admin.mergeRegions() // Admin.mergeRegions()
testMasterOperationIsRetried(new MethodCaller() { testMasterOperationIsRetried(new MethodCaller() {
@Override @Override
@ -304,8 +304,10 @@ public class TestHBaseAdminNoCluster {
Admin admin = null; Admin admin = null;
try { try {
admin = new HBaseAdmin(connection); admin = Mockito.spy(new HBaseAdmin(connection));
// mock the call to getRegion since in the absence of a cluster (which means the meta
// is not assigned), getRegion can't function
Mockito.doReturn(null).when(((HBaseAdmin)admin)).getRegion(Matchers.<byte[]>any());
try { try {
caller.call(admin); // invoke the HBaseAdmin method caller.call(admin); // invoke the HBaseAdmin method
fail(); fail();