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