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.assertNotNull; 024import static org.junit.jupiter.api.Assertions.assertTrue; 025import static org.junit.jupiter.api.Assertions.fail; 026 027import java.io.IOException; 028import java.util.List; 029import java.util.Map; 030import java.util.concurrent.atomic.AtomicReference; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.CatalogFamilyFormat; 035import org.apache.hadoop.hbase.ClientMetaTableAccessor; 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.jupiter.api.AfterAll; 062import org.junit.jupiter.api.BeforeAll; 063import org.junit.jupiter.api.BeforeEach; 064import org.junit.jupiter.api.Tag; 065import org.junit.jupiter.api.Test; 066import org.junit.jupiter.api.TestInfo; 067import org.slf4j.Logger; 068import org.slf4j.LoggerFactory; 069 070import org.apache.hbase.thirdparty.com.google.common.base.Joiner; 071 072@Tag(MasterTests.TAG) 073@Tag(MediumTests.TAG) 074public class TestMaster { 075 076 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 077 private static final Logger LOG = LoggerFactory.getLogger(TestMaster.class); 078 private static final TableName TABLENAME = TableName.valueOf("TestMaster"); 079 private static final byte[] FAMILYNAME = Bytes.toBytes("fam"); 080 private static Admin admin; 081 private String testMethodName; 082 083 @BeforeEach 084 public void setTestMethod(TestInfo testInfo) { 085 testMethodName = testInfo.getTestMethod().get().getName(); 086 } 087 088 @BeforeAll 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 @AfterAll 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(testMethodName); 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(testMethodName); 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 // Post HBASE-30335 the master seeds flushedSequenceIdByRegion on OPEN via 257 // merge(openSeqNum, Math::max). openSeqNum is monotonic across close/open cycles, so a region 258 // reopened after cluster restart may carry a strictly higher value than what was persisted. 259 // The preserved invariant is: every region persisted at shutdown is loaded on restart, and no 260 // watermark regresses. 261 Map<byte[], Long> regionMapAfter = 262 TEST_UTIL.getHBaseCluster().getMaster().getServerManager().getFlushedSequenceIdByRegion(); 263 assertEquals(regionMapBefore.size(), regionMapAfter.size()); 264 for (Map.Entry<byte[], Long> before : regionMapBefore.entrySet()) { 265 Long after = regionMapAfter.get(before.getKey()); 266 assertNotNull(after, 267 "region missing after restart: " + Bytes.toStringBinary(before.getKey())); 268 assertTrue(after >= before.getValue(), 269 "flushedSequenceId regressed across restart for region " 270 + Bytes.toStringBinary(before.getKey()) + " before=" + before.getValue() + " after=" 271 + after); 272 } 273 } 274 275 @Test 276 public void testBlockingHbkc1WithLockFile() throws IOException { 277 // This is how the patch to the lock file is created inside in HBaseFsck. Too hard to use its 278 // actual method without disturbing HBaseFsck... Do the below mimic instead. 279 Path hbckLockPath = 280 new Path(HBaseFsck.getTmpDir(TEST_UTIL.getConfiguration()), HBaseFsck.HBCK_LOCK_FILE); 281 FileSystem fs = TEST_UTIL.getTestFileSystem(); 282 assertTrue(fs.exists(hbckLockPath)); 283 TEST_UTIL.getMiniHBaseCluster() 284 .killMaster(TEST_UTIL.getMiniHBaseCluster().getMaster().getServerName()); 285 assertTrue(fs.exists(hbckLockPath)); 286 TEST_UTIL.getMiniHBaseCluster().startMaster(); 287 TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null 288 && TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized()); 289 assertTrue(fs.exists(hbckLockPath)); 290 // Start a second Master. Should be fine. 291 TEST_UTIL.getMiniHBaseCluster().startMaster(); 292 assertTrue(fs.exists(hbckLockPath)); 293 fs.delete(hbckLockPath, true); 294 assertFalse(fs.exists(hbckLockPath)); 295 // Kill all Masters. 296 TEST_UTIL.getMiniHBaseCluster().getLiveMasterThreads().stream() 297 .map(sn -> sn.getMaster().getServerName()).forEach(sn -> { 298 try { 299 TEST_UTIL.getMiniHBaseCluster().killMaster(sn); 300 } catch (IOException e) { 301 e.printStackTrace(); 302 } 303 }); 304 // Start a new one. 305 TEST_UTIL.getMiniHBaseCluster().startMaster(); 306 TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null 307 && TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized()); 308 // Assert lock gets put in place again. 309 assertTrue(fs.exists(hbckLockPath)); 310 } 311 312 @Test 313 public void testInstallShutdownHook() throws IOException { 314 // Test for HBASE-26951 315 assertTrue(TEST_UTIL.getHBaseCluster().getMaster().isShutdownHookInstalled()); 316 } 317}