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.assertArrayEquals;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.util.List;
028import java.util.concurrent.atomic.AtomicReference;
029import org.apache.hadoop.fs.FileSystem;
030import org.apache.hadoop.fs.Path;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.HBaseTestingUtility;
033import org.apache.hadoop.hbase.HColumnDescriptor;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HTableDescriptor;
036import org.apache.hadoop.hbase.MetaTableAccessor;
037import org.apache.hadoop.hbase.MiniHBaseCluster;
038import org.apache.hadoop.hbase.PleaseHoldException;
039import org.apache.hadoop.hbase.ServerName;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.UnknownRegionException;
042import org.apache.hadoop.hbase.client.Admin;
043import org.apache.hadoop.hbase.client.RegionInfo;
044import org.apache.hadoop.hbase.client.RegionInfoBuilder;
045import org.apache.hadoop.hbase.client.Result;
046import org.apache.hadoop.hbase.client.Table;
047import org.apache.hadoop.hbase.client.TableState;
048import org.apache.hadoop.hbase.testclassification.MasterTests;
049import org.apache.hadoop.hbase.testclassification.MediumTests;
050import org.apache.hadoop.hbase.util.Bytes;
051import org.apache.hadoop.hbase.util.HBaseFsck;
052import org.apache.hadoop.hbase.util.Pair;
053import org.apache.hadoop.util.StringUtils;
054import org.junit.AfterClass;
055import org.junit.BeforeClass;
056import org.junit.ClassRule;
057import org.junit.Rule;
058import org.junit.Test;
059import org.junit.experimental.categories.Category;
060import org.junit.rules.TestName;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
065
066@Category({ MasterTests.class, MediumTests.class })
067public class TestMaster {
068
069  @ClassRule
070  public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestMaster.class);
071
072  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
073  private static final Logger LOG = LoggerFactory.getLogger(TestMaster.class);
074  private static final TableName TABLENAME = TableName.valueOf("TestMaster");
075  private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
076  private static Admin admin;
077
078  @Rule
079  public TestName name = new TestName();
080
081  @BeforeClass
082  public static void beforeAllTests() throws Exception {
083    // we will retry operations when PleaseHoldException is thrown
084    TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3);
085    // Set hbase.min.version.move.system.tables as version 0 so that
086    // testMoveRegionWhenNotInitialized never fails even if hbase-default has valid default
087    // value present for production use-case.
088    // See HBASE-22923 for details.
089    TEST_UTIL.getConfiguration().set("hbase.min.version.move.system.tables", "0.0.0");
090    // Start a cluster of two regionservers.
091    TEST_UTIL.startMiniCluster(2);
092    admin = TEST_UTIL.getAdmin();
093  }
094
095  @AfterClass
096  public static void afterAllTests() throws Exception {
097    TEST_UTIL.shutdownMiniCluster();
098  }
099
100  /**
101   * Return the region and current deployment for the region containing the given row. If the region
102   * cannot be found, returns null. If it is found, but not currently deployed, the second element
103   * of the pair may be null.
104   */
105  private Pair<RegionInfo, ServerName> getTableRegionForRow(HMaster master, TableName tableName,
106    byte[] rowKey) throws IOException {
107    final AtomicReference<Pair<RegionInfo, ServerName>> result = new AtomicReference<>(null);
108
109    MetaTableAccessor.Visitor visitor = new MetaTableAccessor.Visitor() {
110      @Override
111      public boolean visit(Result data) throws IOException {
112        if (data == null || data.size() <= 0) {
113          return true;
114        }
115        Pair<RegionInfo, ServerName> pair = new Pair<>(MetaTableAccessor.getRegionInfo(data),
116          MetaTableAccessor.getServerName(data, 0));
117        if (!pair.getFirst().getTable().equals(tableName)) {
118          return false;
119        }
120        result.set(pair);
121        return true;
122      }
123    };
124
125    MetaTableAccessor.scanMeta(master.getConnection(), visitor, tableName, rowKey, 1);
126    return result.get();
127  }
128
129  @Test
130  @SuppressWarnings("deprecation")
131  public void testMasterOpsWhileSplitting() throws Exception {
132    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
133    HMaster m = cluster.getMaster();
134
135    try (Table ht = TEST_UTIL.createTable(TABLENAME, FAMILYNAME)) {
136      assertTrue(m.getTableStateManager().isTableState(TABLENAME, TableState.State.ENABLED));
137      TEST_UTIL.loadTable(ht, FAMILYNAME, false);
138    }
139
140    List<Pair<RegionInfo, ServerName>> tableRegions =
141      MetaTableAccessor.getTableRegionsAndLocations(m.getConnection(), TABLENAME);
142    LOG.info("Regions after load: " + Joiner.on(',').join(tableRegions));
143    assertEquals(1, tableRegions.size());
144    assertArrayEquals(HConstants.EMPTY_START_ROW, tableRegions.get(0).getFirst().getStartKey());
145    assertArrayEquals(HConstants.EMPTY_END_ROW, tableRegions.get(0).getFirst().getEndKey());
146
147    // Now trigger a split and stop when the split is in progress
148    LOG.info("Splitting table");
149    TEST_UTIL.getAdmin().split(TABLENAME);
150
151    LOG.info("Making sure we can call getTableRegions while opening");
152    while (tableRegions.size() < 3) {
153      tableRegions =
154        MetaTableAccessor.getTableRegionsAndLocations(m.getConnection(), TABLENAME, false);
155      Thread.sleep(100);
156    }
157    LOG.info("Regions: " + Joiner.on(',').join(tableRegions));
158    // We have three regions because one is split-in-progress
159    assertEquals(3, tableRegions.size());
160    LOG.info("Making sure we can call getTableRegionClosest while opening");
161    Pair<RegionInfo, ServerName> pair = getTableRegionForRow(m, TABLENAME, Bytes.toBytes("cde"));
162    LOG.info("Result is: " + pair);
163    Pair<RegionInfo, ServerName> tableRegionFromName =
164      MetaTableAccessor.getRegion(m.getConnection(), pair.getFirst().getRegionName());
165    assertTrue(RegionInfo.COMPARATOR.compare(tableRegionFromName.getFirst(), pair.getFirst()) == 0);
166  }
167
168  @Test
169  public void testMoveRegionWhenNotInitialized() {
170    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
171    HMaster m = cluster.getMaster();
172    try {
173      m.setInitialized(false); // fake it, set back later
174      RegionInfo meta = RegionInfoBuilder.FIRST_META_REGIONINFO;
175      m.move(meta.getEncodedNameAsBytes(), null);
176      fail("Region should not be moved since master is not initialized");
177    } catch (IOException ioe) {
178      assertTrue(ioe instanceof PleaseHoldException);
179    } finally {
180      m.setInitialized(true);
181    }
182  }
183
184  @Test
185  public void testMoveThrowsUnknownRegionException() throws IOException {
186    final TableName tableName = TableName.valueOf(name.getMethodName());
187    HTableDescriptor htd = new HTableDescriptor(tableName);
188    HColumnDescriptor hcd = new HColumnDescriptor("value");
189    htd.addFamily(hcd);
190
191    admin.createTable(htd, null);
192    try {
193      RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("A"))
194        .setEndKey(Bytes.toBytes("Z")).build();
195      admin.move(hri.getEncodedNameAsBytes());
196      fail("Region should not be moved since it is fake");
197    } catch (IOException ioe) {
198      assertTrue(ioe instanceof UnknownRegionException);
199    } finally {
200      TEST_UTIL.deleteTable(tableName);
201    }
202  }
203
204  @Test
205  public void testMoveThrowsPleaseHoldException() throws IOException {
206    final TableName tableName = TableName.valueOf(name.getMethodName());
207    HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
208    HTableDescriptor htd = new HTableDescriptor(tableName);
209    HColumnDescriptor hcd = new HColumnDescriptor("value");
210    htd.addFamily(hcd);
211
212    admin.createTable(htd, null);
213    try {
214      List<RegionInfo> tableRegions = admin.getRegions(tableName);
215
216      master.setInitialized(false); // fake it, set back later
217      admin.move(tableRegions.get(0).getEncodedNameAsBytes());
218      fail("Region should not be moved since master is not initialized");
219    } catch (IOException ioe) {
220      assertTrue(StringUtils.stringifyException(ioe).contains("PleaseHoldException"));
221    } finally {
222      master.setInitialized(true);
223      TEST_UTIL.deleteTable(tableName);
224    }
225  }
226
227  @Test
228  public void testBlockingHbkc1WithLockFile() throws IOException {
229    // This is how the patch to the lock file is created inside in HBaseFsck. Too hard to use its
230    // actual method without disturbing HBaseFsck... Do the below mimic instead.
231    Path hbckLockPath =
232      new Path(HBaseFsck.getTmpDir(TEST_UTIL.getConfiguration()), HBaseFsck.HBCK_LOCK_FILE);
233    FileSystem fs = TEST_UTIL.getTestFileSystem();
234    assertTrue(fs.exists(hbckLockPath));
235    TEST_UTIL.getMiniHBaseCluster()
236      .killMaster(TEST_UTIL.getMiniHBaseCluster().getMaster().getServerName());
237    assertTrue(fs.exists(hbckLockPath));
238    TEST_UTIL.getMiniHBaseCluster().startMaster();
239    TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null
240      && TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized());
241    assertTrue(fs.exists(hbckLockPath));
242    // Start a second Master. Should be fine.
243    TEST_UTIL.getMiniHBaseCluster().startMaster();
244    assertTrue(fs.exists(hbckLockPath));
245    fs.delete(hbckLockPath, true);
246    assertFalse(fs.exists(hbckLockPath));
247    // Kill all Masters.
248    TEST_UTIL.getMiniHBaseCluster().getLiveMasterThreads().stream()
249      .map(sn -> sn.getMaster().getServerName()).forEach(sn -> {
250        try {
251          TEST_UTIL.getMiniHBaseCluster().killMaster(sn);
252        } catch (IOException e) {
253          e.printStackTrace();
254        }
255      });
256    // Start a new one.
257    TEST_UTIL.getMiniHBaseCluster().startMaster();
258    TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null
259      && TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized());
260    // Assert lock gets put in place again.
261    assertTrue(fs.exists(hbckLockPath));
262  }
263
264  @Test
265  public void testInstallShutdownHook() {
266    // Test for HBASE-26977
267    assertTrue(TEST_UTIL.getMiniHBaseCluster().getMaster().isShutdownHookInstalled());
268  }
269}