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; 019 020import static org.junit.jupiter.api.Assertions.assertTrue; 021 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.List; 026import java.util.Map; 027import java.util.concurrent.CyclicBarrier; 028import java.util.concurrent.atomic.AtomicReference; 029import org.apache.hadoop.hbase.HBaseTestingUtil; 030import org.apache.hadoop.hbase.ServerName; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.RegionInfo; 033import org.apache.hadoop.hbase.master.assignment.AssignmentManager; 034import org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer; 035import org.apache.hadoop.hbase.testclassification.MasterTests; 036import org.apache.hadoop.hbase.testclassification.MediumTests; 037import org.apache.hadoop.hbase.util.Bytes; 038import org.junit.jupiter.api.AfterEach; 039import org.junit.jupiter.api.BeforeEach; 040import org.junit.jupiter.api.Tag; 041import org.junit.jupiter.api.Test; 042import org.junit.jupiter.api.TestInfo; 043import org.mockito.Mockito; 044import org.mockito.invocation.InvocationOnMock; 045 046@Tag(MasterTests.TAG) 047@Tag(MediumTests.TAG) 048public class TestMasterBalancerNPE { 049 050 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 051 private static final byte[] FAMILYNAME = Bytes.toBytes("fam"); 052 private String testMethodName; 053 054 @BeforeEach 055 public void setTestMethod(TestInfo testInfo) { 056 testMethodName = testInfo.getTestMethod().get().getName(); 057 } 058 059 @BeforeEach 060 public void setupConfiguration() { 061 /** 062 * Make {@link BalancerChore} not run,so does not disrupt the test. 063 */ 064 HMaster.setDisableBalancerChoreForTest(true); 065 } 066 067 @AfterEach 068 public void shutdown() throws Exception { 069 HMaster.setDisableBalancerChoreForTest(false); 070 TEST_UTIL.shutdownMiniCluster(); 071 } 072 073 /** 074 * This test is for HBASE-26712, to make the region is unassigned just before 075 * {@link AssignmentManager#balance} is invoked on the region. 076 */ 077 @Test 078 public void testBalancerNPE() throws Exception { 079 TEST_UTIL.startMiniCluster(2); 080 TEST_UTIL.getAdmin().balancerSwitch(false, true); 081 TableName tableName = createTable(testMethodName); 082 final HMaster master = TEST_UTIL.getHBaseCluster().getMaster(); 083 List<RegionInfo> regionInfos = TEST_UTIL.getAdmin().getRegions(tableName); 084 assertTrue(regionInfos.size() == 1); 085 final ServerName serverName1 = 086 TEST_UTIL.getMiniHBaseCluster().getRegionServer(0).getServerName(); 087 final ServerName serverName2 = 088 TEST_UTIL.getMiniHBaseCluster().getRegionServer(1).getServerName(); 089 090 final RegionInfo regionInfo = regionInfos.get(0); 091 092 RSGroupBasedLoadBalancer loadBalancer = master.getLoadBalancer(); 093 RSGroupBasedLoadBalancer spiedLoadBalancer = Mockito.spy(loadBalancer); 094 final AtomicReference<RegionPlan> regionPlanRef = new AtomicReference<RegionPlan>(); 095 096 /** 097 * Mock {@link RSGroupBasedLoadBalancer#balanceCluster} to return the {@link RegionPlan} to move 098 * the only region to the other RegionServer. 099 */ 100 Mockito.doAnswer((InvocationOnMock invocation) -> { 101 @SuppressWarnings("unchecked") 102 Map<TableName, Map<ServerName, List<RegionInfo>>> tableNameToRegionServerNameToRegionInfos = 103 (Map<TableName, Map<ServerName, List<RegionInfo>>>) invocation.getArgument(0); 104 Map<ServerName, List<RegionInfo>> regionServerNameToRegionInfos = 105 tableNameToRegionServerNameToRegionInfos.get(tableName); 106 assertTrue(regionServerNameToRegionInfos.size() == 2); 107 List<ServerName> assignedRegionServerNames = new ArrayList<ServerName>(); 108 for (Map.Entry<ServerName, List<RegionInfo>> entry : regionServerNameToRegionInfos 109 .entrySet()) { 110 if (entry.getValue().size() > 0) { 111 assignedRegionServerNames.add(entry.getKey()); 112 } 113 } 114 assertTrue(assignedRegionServerNames.size() == 1); 115 ServerName assignedRegionServerName = assignedRegionServerNames.get(0); 116 ServerName notAssignedRegionServerName = 117 assignedRegionServerName.equals(serverName1) ? serverName2 : serverName1; 118 RegionPlan regionPlan = 119 new RegionPlan(regionInfo, assignedRegionServerName, notAssignedRegionServerName); 120 regionPlanRef.set(regionPlan); 121 return Arrays.asList(regionPlan); 122 }).when(spiedLoadBalancer).balanceCluster(Mockito.anyMap()); 123 124 AssignmentManager assignmentManager = master.getAssignmentManager(); 125 final AssignmentManager spiedAssignmentManager = Mockito.spy(assignmentManager); 126 final CyclicBarrier cyclicBarrier = new CyclicBarrier(2); 127 128 /** 129 * Override {@link AssignmentManager#balance} to invoke real {@link AssignmentManager#balance} 130 * after the region is successfully unassigned. 131 */ 132 Mockito.doAnswer((InvocationOnMock invocation) -> { 133 RegionPlan regionPlan = invocation.getArgument(0); 134 RegionPlan referedRegionPlan = regionPlanRef.get(); 135 assertTrue(referedRegionPlan != null); 136 if (referedRegionPlan.equals(regionPlan)) { 137 /** 138 * To make {@link AssignmentManager#unassign} could be invoked just before 139 * {@link AssignmentManager#balance} is invoked. 140 */ 141 cyclicBarrier.await(); 142 /** 143 * After {@link AssignmentManager#unassign} is completed,we could invoke 144 * {@link AssignmentManager#balance}. 145 */ 146 cyclicBarrier.await(); 147 } 148 /** 149 * Before HBASE-26712,here may throw NPE. 150 */ 151 return invocation.callRealMethod(); 152 }).when(spiedAssignmentManager).balance(Mockito.any()); 153 154 try { 155 final AtomicReference<Throwable> exceptionRef = new AtomicReference<Throwable>(null); 156 Thread unassignThread = new Thread(() -> { 157 try { 158 /** 159 * To invoke {@link AssignmentManager#unassign} just before 160 * {@link AssignmentManager#balance} is invoked. 161 */ 162 cyclicBarrier.await(); 163 spiedAssignmentManager.unassign(regionInfo); 164 assertTrue(spiedAssignmentManager.getRegionStates().getRegionAssignments().get(regionInfo) 165 == null); 166 /** 167 * After {@link AssignmentManager#unassign} is completed,we could invoke 168 * {@link AssignmentManager#balance}. 169 */ 170 cyclicBarrier.await(); 171 } catch (Exception e) { 172 exceptionRef.set(e); 173 } 174 }); 175 unassignThread.setName("UnassignThread"); 176 unassignThread.start(); 177 178 master.setLoadBalancer(spiedLoadBalancer); 179 master.setAssignmentManager(spiedAssignmentManager); 180 /** 181 * enable balance 182 */ 183 TEST_UTIL.getAdmin().balancerSwitch(true, false); 184 /** 185 * Before HBASE-26712,here invokes {@link AssignmentManager#balance(RegionPlan)} which may 186 * throw NPE. 187 */ 188 master.balanceOrUpdateMetrics(); 189 190 unassignThread.join(); 191 assertTrue(exceptionRef.get() == null); 192 } finally { 193 master.setLoadBalancer(loadBalancer); 194 master.setAssignmentManager(assignmentManager); 195 } 196 } 197 198 private TableName createTable(String table) throws IOException { 199 TableName tableName = TableName.valueOf(table); 200 TEST_UTIL.createTable(tableName, FAMILYNAME); 201 return tableName; 202 } 203 204}