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