/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.master; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.CatalogFamilyFormat; import org.apache.hadoop.hbase.ClientMetaTableAccessor; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.MetaTableAccessor; import org.apache.hadoop.hbase.MiniHBaseCluster; import org.apache.hadoop.hbase.PleaseHoldException; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.UnknownRegionException; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.RegionInfoBuilder; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.TableDescriptorBuilder; import org.apache.hadoop.hbase.client.TableState; import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.HBaseFsck; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.Threads; import org.apache.hadoop.util.StringUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hbase.thirdparty.com.google.common.base.Joiner; @Category({MasterTests.class, MediumTests.class}) public class TestMaster { @ClassRule public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestMaster.class); private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static final Logger LOG = LoggerFactory.getLogger(TestMaster.class); private static final TableName TABLENAME = TableName.valueOf("TestMaster"); private static final byte[] FAMILYNAME = Bytes.toBytes("fam"); private static Admin admin; @Rule public TestName name = new TestName(); @BeforeClass public static void beforeAllTests() throws Exception { // we will retry operations when PleaseHoldException is thrown TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3); // Start a cluster of two regionservers. TEST_UTIL.startMiniCluster(2); admin = TEST_UTIL.getAdmin(); } @AfterClass public static void afterAllTests() throws Exception { TEST_UTIL.shutdownMiniCluster(); } /** * Return the region and current deployment for the region containing the given row. If the region * cannot be found, returns null. If it is found, but not currently deployed, the second element * of the pair may be null. */ private Pair<RegionInfo, ServerName> getTableRegionForRow(HMaster master, TableName tableName, byte[] rowKey) throws IOException { final AtomicReference<Pair<RegionInfo, ServerName>> result = new AtomicReference<>(null); ClientMetaTableAccessor.Visitor visitor = new ClientMetaTableAccessor.Visitor() { @Override public boolean visit(Result data) throws IOException { if (data == null || data.size() <= 0) { return true; } Pair<RegionInfo, ServerName> pair = new Pair<>(CatalogFamilyFormat.getRegionInfo(data), CatalogFamilyFormat.getServerName(data, 0)); if (!pair.getFirst().getTable().equals(tableName)) { return false; } result.set(pair); return true; } }; MetaTableAccessor.scanMeta(master.getConnection(), visitor, tableName, rowKey, 1); return result.get(); } @Test @SuppressWarnings("deprecation") public void testMasterOpsWhileSplitting() throws Exception { MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); HMaster m = cluster.getMaster(); try (Table ht = TEST_UTIL.createTable(TABLENAME, FAMILYNAME)) { assertTrue(m.getTableStateManager().isTableState(TABLENAME, TableState.State.ENABLED)); TEST_UTIL.loadTable(ht, FAMILYNAME, false); } List<Pair<RegionInfo, ServerName>> tableRegions = MetaTableAccessor.getTableRegionsAndLocations( m.getConnection(), TABLENAME); LOG.info("Regions after load: " + Joiner.on(',').join(tableRegions)); assertEquals(1, tableRegions.size()); assertArrayEquals(HConstants.EMPTY_START_ROW, tableRegions.get(0).getFirst().getStartKey()); assertArrayEquals(HConstants.EMPTY_END_ROW, tableRegions.get(0).getFirst().getEndKey()); // Now trigger a split and stop when the split is in progress LOG.info("Splitting table"); TEST_UTIL.getAdmin().split(TABLENAME); LOG.info("Making sure we can call getTableRegions while opening"); while (tableRegions.size() < 3) { tableRegions = MetaTableAccessor.getTableRegionsAndLocations(m.getConnection(), TABLENAME, false); Thread.sleep(100); } LOG.info("Regions: " + Joiner.on(',').join(tableRegions)); // We have three regions because one is split-in-progress assertEquals(3, tableRegions.size()); LOG.info("Making sure we can call getTableRegionClosest while opening"); Pair<RegionInfo, ServerName> pair = getTableRegionForRow(m, TABLENAME, Bytes.toBytes("cde")); LOG.info("Result is: " + pair); Pair<RegionInfo, ServerName> tableRegionFromName = MetaTableAccessor.getRegion(m.getConnection(), pair.getFirst().getRegionName()); assertTrue(RegionInfo.COMPARATOR.compare(tableRegionFromName.getFirst(), pair.getFirst()) == 0); } @Test public void testMoveRegionWhenNotInitialized() { MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); HMaster m = cluster.getMaster(); try { m.setInitialized(false); // fake it, set back later RegionInfo meta = RegionInfoBuilder.FIRST_META_REGIONINFO; m.move(meta.getEncodedNameAsBytes(), null); fail("Region should not be moved since master is not initialized"); } catch (IOException ioe) { assertTrue(ioe instanceof PleaseHoldException); } finally { m.setInitialized(true); } } @Test public void testMoveThrowsUnknownRegionException() throws IOException { final TableName tableName = TableName.valueOf(name.getMethodName()); TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tableName); ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("value")).build(); tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); admin.createTable(tableDescriptorBuilder.build()); try { RegionInfo hri = RegionInfoBuilder.newBuilder(tableName) .setStartKey(Bytes.toBytes("A")) .setEndKey(Bytes.toBytes("Z")) .build(); admin.move(hri.getEncodedNameAsBytes()); fail("Region should not be moved since it is fake"); } catch (IOException ioe) { assertTrue(ioe instanceof UnknownRegionException); } finally { TEST_UTIL.deleteTable(tableName); } } @Test public void testMoveThrowsPleaseHoldException() throws IOException { final TableName tableName = TableName.valueOf(name.getMethodName()); HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster(); TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tableName); ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("value")).build(); tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); admin.createTable(tableDescriptorBuilder.build()); try { List<RegionInfo> tableRegions = admin.getRegions(tableName); master.setInitialized(false); // fake it, set back later admin.move(tableRegions.get(0).getEncodedNameAsBytes()); fail("Region should not be moved since master is not initialized"); } catch (IOException ioe) { assertTrue(StringUtils.stringifyException(ioe).contains("PleaseHoldException")); } finally { master.setInitialized(true); TEST_UTIL.deleteTable(tableName); } } @Test public void testFlushedSequenceIdPersistLoad() throws Exception { Configuration conf = TEST_UTIL.getConfiguration(); int msgInterval = conf.getInt("hbase.regionserver.msginterval", 100); // insert some data into META TableName tableName = TableName.valueOf("testFlushSeqId"); TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor = new TableDescriptorBuilder.ModifyableTableDescriptor(tableName); tableDescriptor.setColumnFamily( new ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor(Bytes.toBytes("cf"))); Table table = TEST_UTIL.createTable(tableDescriptor, null); // flush META region TEST_UTIL.flush(TableName.META_TABLE_NAME); // wait for regionserver report Threads.sleep(msgInterval * 2); // record flush seqid before cluster shutdown Map<byte[], Long> regionMapBefore = TEST_UTIL.getHBaseCluster().getMaster().getServerManager() .getFlushedSequenceIdByRegion(); // restart hbase cluster which will cause flushed sequence id persist and reload TEST_UTIL.getMiniHBaseCluster().shutdown(); TEST_UTIL.restartHBaseCluster(2); TEST_UTIL.waitUntilNoRegionsInTransition(); // check equality after reloading flushed sequence id map Map<byte[], Long> regionMapAfter = TEST_UTIL.getHBaseCluster().getMaster().getServerManager() .getFlushedSequenceIdByRegion(); assertTrue(regionMapBefore.equals(regionMapAfter)); } @Test public void testBlockingHbkc1WithLockFile() throws IOException { // This is how the patch to the lock file is created inside in HBaseFsck. Too hard to use its // actual method without disturbing HBaseFsck... Do the below mimic instead. Path hbckLockPath = new Path(HBaseFsck.getTmpDir(TEST_UTIL.getConfiguration()), HBaseFsck.HBCK_LOCK_FILE); FileSystem fs = TEST_UTIL.getTestFileSystem(); assertTrue(fs.exists(hbckLockPath)); TEST_UTIL.getMiniHBaseCluster(). killMaster(TEST_UTIL.getMiniHBaseCluster().getMaster().getServerName()); assertTrue(fs.exists(hbckLockPath)); TEST_UTIL.getMiniHBaseCluster().startMaster(); TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null && TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized()); assertTrue(fs.exists(hbckLockPath)); // Start a second Master. Should be fine. TEST_UTIL.getMiniHBaseCluster().startMaster(); assertTrue(fs.exists(hbckLockPath)); fs.delete(hbckLockPath, true); assertFalse(fs.exists(hbckLockPath)); // Kill all Masters. TEST_UTIL.getMiniHBaseCluster().getLiveMasterThreads().stream(). map(sn -> sn.getMaster().getServerName()).forEach(sn -> { try { TEST_UTIL.getMiniHBaseCluster().killMaster(sn); } catch (IOException e) { e.printStackTrace(); } }); // Start a new one. TEST_UTIL.getMiniHBaseCluster().startMaster(); TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null && TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized()); // Assert lock gets put in place again. assertTrue(fs.exists(hbckLockPath)); } }