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