diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java index 5baf30846e08..cce8cb85973d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java @@ -786,6 +786,10 @@ private TransitRegionStateProcedure createAssignProcedure(RegionInfo regionInfo, RegionStateNode regionNode = regionStates.getOrCreateRegionStateNode(regionInfo); regionNode.lock(); try { + if (regionNode.isSplit()) { + throw new DoNotRetryRegionException(regionNode.getRegionInfo().getEncodedName() + + " is a split parent and cannot be assigned"); + } if (override) { if (!force) { preTransitCheck(regionNode, STATES_EXPECTED_ON_ASSIGN); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java index 6dac4c37c1b3..8b2003bfb207 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java @@ -376,6 +376,8 @@ public void splitRegion(RegionInfo parent, RegionInfo splitA, RegionInfo splitB, Put putParent = MetaTableAccessor.makePutFromRegionInfo( RegionInfoBuilder.newBuilder(parent).setOffline(true).setSplit(true).build(), time); MetaTableAccessor.addDaughtersToPut(putParent, splitA, splitB); + MetaTableAccessor.addRegionStateToPut(putParent, RegionInfo.DEFAULT_REPLICA_ID, + RegionState.State.SPLIT); // Puts for daughters Put putA = MetaTableAccessor.makePutFromRegionInfo(splitA, time); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestAssignmentManager.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestAssignmentManager.java index 01d051779058..e53ff50ea0b8 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestAssignmentManager.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestAssignmentManager.java @@ -20,11 +20,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collections; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.HBaseTestingUtil; import org.apache.hadoop.hbase.MetaTableAccessor; import org.apache.hadoop.hbase.TableName; @@ -217,6 +219,31 @@ public void testUnassignAnUnassignedRegion() throws Exception { assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount()); } + @Test + public void testAssignThrowsWithASplitParent() throws Exception { + RegionInfo splitParent = RegionInfoBuilder.newBuilder(TableName.valueOf("test-split-split")) + .setSplit(true).setOffline(true).build(); + RegionStateNode rsn = am.getRegionStates().getOrCreateRegionStateNode(splitParent); + rsn.setState(State.SPLIT); + + assertThrows(DoNotRetryIOException.class, () -> am.assign(splitParent)); + assertNull(am.createOneAssignProcedure(splitParent, true, false)); + } + + // Simulate the pre-fix failover scenario: regionInfo.isSplit()=true but state=OFFLINE because + // the old code path did not write SPLIT to info:state, causing loadMeta to fall back to OFFLINE. + // See HBASE-30353. + @Test + public void testAssignThrowsWithASplitParentInOfflineState() throws Exception { + RegionInfo splitParent = RegionInfoBuilder.newBuilder(TableName.valueOf("test-split-offline")) + .setSplit(true).setOffline(true).build(); + RegionStateNode rsn = am.getRegionStates().getOrCreateRegionStateNode(splitParent); + rsn.setState(State.OFFLINE); + + assertThrows(DoNotRetryIOException.class, () -> am.assign(splitParent)); + assertNull(am.createOneAssignProcedure(splitParent, true, false)); + } + /** * 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 diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionStateStore.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionStateStore.java index b85b7c606063..d0fdb09503b8 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionStateStore.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionStateStore.java @@ -221,6 +221,37 @@ public void testEmptyMetaDaughterLocationDuringSplit() throws IOException { } } + @Test + public void testSplitRegionWritesSplitStateForParentInMeta() throws IOException { + long regionId = EnvironmentEdgeManager.currentTime(); + ServerName serverName = + ServerName.valueOf("foo", 60010, ThreadLocalRandom.current().nextLong()); + TableName tableName = name.getTableName(); + RegionInfo parent = RegionInfoBuilder.newBuilder(tableName) + .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false) + .setRegionId(regionId).setReplicaId(0).build(); + RegionInfo splitA = RegionInfoBuilder.newBuilder(tableName) + .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(Bytes.toBytes("a")).setSplit(false) + .setRegionId(regionId + 1).setReplicaId(0).build(); + RegionInfo splitB = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("a")) + .setEndKey(HConstants.EMPTY_END_ROW).setSplit(false).setRegionId(regionId + 1).setReplicaId(0) + .build(); + MetaTableAccessor.addRegionsToMeta(UTIL.getConnection(), Lists.newArrayList(parent), 1); + final RegionStateStore regionStateStore = + UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore(); + regionStateStore.splitRegion(parent, splitA, splitB, serverName, + TableDescriptorBuilder.newBuilder(tableName).build()); + + try (Table meta = MetaTableAccessor.getMetaHTable(UTIL.getConnection())) { + Result result = meta.get(new Get(parent.getRegionName())); + Cell stateCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, + CatalogFamilyFormat.getRegionStateColumn(RegionInfo.DEFAULT_REPLICA_ID)); + assertNotNull(stateCell); + assertEquals(RegionState.State.SPLIT.name(), Bytes.toString(stateCell.getValueArray(), + stateCell.getValueOffset(), stateCell.getValueLength())); + } + } + @Test public void testMetaLocationForRegionReplicasIsAddedAtRegionMerge() throws IOException { long regionId = EnvironmentEdgeManager.currentTime();