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