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}