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.apache.hadoop.hbase.master.assignment.AssignmentTestingUtil.insertData; 021import static org.junit.jupiter.api.Assertions.assertEquals; 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.io.IOException; 027import java.util.Collections; 028import java.util.List; 029import org.apache.hadoop.hbase.HBaseTestingUtil; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.client.Hbck; 032import org.apache.hadoop.hbase.client.RegionInfo; 033import org.apache.hadoop.hbase.client.RegionInfoBuilder; 034import org.apache.hadoop.hbase.client.TableDescriptor; 035import org.apache.hadoop.hbase.master.RegionState; 036import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 037import org.apache.hadoop.hbase.master.procedure.MasterProcedureTestingUtility; 038import org.apache.hadoop.hbase.procedure2.Procedure; 039import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; 040import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 041import org.apache.hadoop.hbase.testclassification.MasterTests; 042import org.apache.hadoop.hbase.testclassification.MediumTests; 043import org.apache.hadoop.hbase.util.Bytes; 044import org.junit.jupiter.api.AfterAll; 045import org.junit.jupiter.api.AfterEach; 046import org.junit.jupiter.api.BeforeAll; 047import org.junit.jupiter.api.BeforeEach; 048import org.junit.jupiter.api.Tag; 049import org.junit.jupiter.api.Test; 050import org.junit.jupiter.api.TestInfo; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * Regression test for HBASE-30353: a split parent region must never be re-opened after master 056 * failover. 057 * <p> 058 * The fix has two parts. {@code RegionStateStore.splitRegion} now writes {@code SPLIT} into 059 * {@code info:state} for the parent; that is covered by {@code TestRegionStateStore}. This class 060 * covers the second part, the {@code isSplit()} check in 061 * {@code AssignmentManager.createAssignProcedure}. It matters for parents loaded from meta rows 062 * written before the fix (state {@code CLOSED} with {@code regionInfo.isSplit()=true}), which pass 063 * the expected-state check, and for forced assigns, which skip it. The tests go through the client 064 * entry points, {@code Admin.assign} and {@code Hbck.assigns}. 065 */ 066@Tag(MasterTests.TAG) 067@Tag(MediumTests.TAG) 068public class TestSplitParentAssignment { 069 070 private static final Logger LOG = LoggerFactory.getLogger(TestSplitParentAssignment.class); 071 072 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 073 private static final String CF = "cf"; 074 private static final int ROW_COUNT = 60; 075 private static final int START_ROW = 11; 076 077 private String testMethodName; 078 079 @BeforeAll 080 public static void setupCluster() throws Exception { 081 UTIL.startMiniCluster(3); 082 } 083 084 @AfterAll 085 public static void tearDownCluster() throws IOException { 086 try { 087 UTIL.shutdownMiniCluster(); 088 } catch (IOException e) { 089 LOG.warn("failure shutting down cluster", e); 090 } 091 } 092 093 @BeforeEach 094 public void setup(TestInfo testInfo) throws Exception { 095 testMethodName = testInfo.getTestMethod().get().getName(); 096 // Prevent CatalogJanitor from garbage collecting the split parent before the test can use it. 097 UTIL.getHBaseCluster().getMaster().setCatalogJanitorEnabled(false); 098 // Prevent compaction. If daughters compact away reference files, the parent becomes 099 // eligible for garbage collection even with CatalogJanitor disabled. 100 for (int i = 0; i < UTIL.getHBaseCluster().getLiveRegionServerThreads().size(); i++) { 101 UTIL.getHBaseCluster().getRegionServer(i).getCompactSplitThread().switchCompaction(false); 102 } 103 } 104 105 @AfterEach 106 public void tearDown() throws Exception { 107 UTIL.getHBaseCluster().getMaster().setCatalogJanitorEnabled(true); 108 for (TableDescriptor htd : UTIL.getAdmin().listTableDescriptors()) { 109 UTIL.deleteTable(htd.getTableName()); 110 } 111 } 112 113 @Test 114 public void testAdminAssignRejectsSplitParentLoadedAsClosed() throws Exception { 115 TableName tableName = TableName.valueOf(testMethodName); 116 AssignmentManager am = UTIL.getHBaseCluster().getMaster().getAssignmentManager(); 117 RegionInfo parentInfo = splitTableAndGetParent(tableName); 118 RegionStateNode rsn = simulatePostFailover(am, parentInfo, RegionState.State.CLOSED); 119 RegionInfo splitParentInfo = rsn.getRegionInfo(); 120 121 Exception ex = 122 assertThrows(Exception.class, () -> UTIL.getAdmin().assign(splitParentInfo.getRegionName()), 123 "Admin.assign must reject a split parent even when its state is CLOSED"); 124 125 assertTrue(ex.toString().contains("is a split parent and cannot be assigned"), 126 "Expected the split parent guard to reject the assign but got: " + ex); 127 assertNull(rsn.getProcedure(), "No assign procedure may be attached to a split parent"); 128 assertEquals(RegionState.State.CLOSED, rsn.getState()); 129 } 130 131 @Test 132 public void testHbckForceAssignRejectsSplitParentLoadedAsClosed() throws Exception { 133 TableName tableName = TableName.valueOf(testMethodName); 134 AssignmentManager am = UTIL.getHBaseCluster().getMaster().getAssignmentManager(); 135 RegionInfo parentInfo = splitTableAndGetParent(tableName); 136 RegionStateNode rsn = simulatePostFailover(am, parentInfo, RegionState.State.CLOSED); 137 RegionInfo splitParentInfo = rsn.getRegionInfo(); 138 139 List<Long> pids; 140 try (Hbck hbck = UTIL.getConnection().getHbck()) { 141 pids = hbck.assigns(Collections.singletonList(splitParentInfo.getEncodedName()), true, true); 142 } 143 144 assertEquals(Collections.singletonList(Procedure.NO_PROC_ID), pids, 145 "Forced HBCK assign of a split parent must not submit a procedure"); 146 assertNull(rsn.getProcedure(), "No assign procedure may be attached to a split parent"); 147 assertEquals(RegionState.State.CLOSED, rsn.getState()); 148 } 149 150 private RegionInfo splitTableAndGetParent(TableName tableName) throws Exception { 151 ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 152 RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null, CF); 153 insertData(UTIL, tableName, ROW_COUNT, START_ROW, CF); 154 byte[] splitKey = Bytes.toBytes("" + (START_ROW + ROW_COUNT / 2)); 155 long procId = procExec.submitProcedure( 156 new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey)); 157 ProcedureTestingUtility.waitProcedure(procExec, procId); 158 ProcedureTestingUtility.assertProcNotFailed(procExec, procId); 159 return regions[0]; 160 } 161 162 /** 163 * Replaces the parent's {@link RegionStateNode} with one shaped like {@code loadMeta} would 164 * rebuild it after a master failover: {@code regionInfo.isSplit()=true} and the given state. Pass 165 * {@code CLOSED} to reproduce a meta row written before HBASE-30353, where the parent's 166 * {@code info:state} was not {@code SPLIT}. 167 */ 168 private RegionStateNode simulatePostFailover(AssignmentManager am, RegionInfo parentInfo, 169 RegionState.State state) { 170 am.getRegionStates().deleteRegion(parentInfo); 171 RegionInfo splitParentInfo = RegionInfoBuilder.newBuilder(parentInfo.getTable()) 172 .setStartKey(parentInfo.getStartKey()).setEndKey(parentInfo.getEndKey()) 173 .setRegionId(parentInfo.getRegionId()).setSplit(true).setOffline(true).build(); 174 RegionStateNode freshRsn = am.getRegionStates().getOrCreateRegionStateNode(splitParentInfo); 175 freshRsn.setState(state); 176 return freshRsn; 177 } 178 179 private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() { 180 return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor(); 181 } 182}