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}