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; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertNotNull; 022import static org.junit.jupiter.api.Assertions.assertNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024import static org.mockito.ArgumentMatchers.any; 025import static org.mockito.Mockito.doReturn; 026import static org.mockito.Mockito.mock; 027import static org.mockito.Mockito.reset; 028import static org.mockito.Mockito.times; 029import static org.mockito.Mockito.verify; 030 031import java.io.IOException; 032import java.util.Collections; 033import java.util.List; 034import java.util.Random; 035import java.util.concurrent.ThreadLocalRandom; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.client.Connection; 038import org.apache.hadoop.hbase.client.ConnectionFactory; 039import org.apache.hadoop.hbase.client.Get; 040import org.apache.hadoop.hbase.client.RegionInfo; 041import org.apache.hadoop.hbase.client.RegionInfoBuilder; 042import org.apache.hadoop.hbase.client.RegionLocator; 043import org.apache.hadoop.hbase.client.Result; 044import org.apache.hadoop.hbase.client.Scan; 045import org.apache.hadoop.hbase.client.Table; 046import org.apache.hadoop.hbase.filter.PrefixFilter; 047import org.apache.hadoop.hbase.ipc.CallRunner; 048import org.apache.hadoop.hbase.ipc.DelegatingRpcScheduler; 049import org.apache.hadoop.hbase.ipc.PriorityFunction; 050import org.apache.hadoop.hbase.ipc.RpcScheduler; 051import org.apache.hadoop.hbase.master.HMaster; 052import org.apache.hadoop.hbase.regionserver.HRegion; 053import org.apache.hadoop.hbase.regionserver.HRegionServer; 054import org.apache.hadoop.hbase.regionserver.SimpleRpcSchedulerFactory; 055import org.apache.hadoop.hbase.testclassification.MediumTests; 056import org.apache.hadoop.hbase.testclassification.MiscTests; 057import org.apache.hadoop.hbase.util.Bytes; 058import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 059import org.apache.hadoop.hbase.util.Pair; 060import org.junit.jupiter.api.AfterAll; 061import org.junit.jupiter.api.BeforeAll; 062import org.junit.jupiter.api.Tag; 063import org.junit.jupiter.api.Test; 064import org.junit.jupiter.api.TestInfo; 065import org.slf4j.Logger; 066import org.slf4j.LoggerFactory; 067 068import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 069 070/** 071 * Test {@link org.apache.hadoop.hbase.MetaTableAccessor}. 072 */ 073@Tag(MiscTests.TAG) 074@Tag(MediumTests.TAG) 075@SuppressWarnings("deprecation") 076public class TestMetaTableAccessor { 077 078 private static final Logger LOG = LoggerFactory.getLogger(TestMetaTableAccessor.class); 079 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 080 private static Connection connection; 081 082 @BeforeAll 083 public static void beforeClass() throws Exception { 084 UTIL.startMiniCluster(3); 085 086 Configuration c = new Configuration(UTIL.getConfiguration()); 087 // Tests to 4 retries every 5 seconds. Make it try every 1 second so more 088 // responsive. 1 second is default as is ten retries. 089 c.setLong("hbase.client.pause", 1000); 090 c.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 10); 091 connection = ConnectionFactory.createConnection(c); 092 } 093 094 @AfterAll 095 public static void afterClass() throws Exception { 096 connection.close(); 097 UTIL.shutdownMiniCluster(); 098 } 099 100 @Test 101 public void testIsMetaWhenAllHealthy() throws InterruptedException { 102 HMaster m = UTIL.getMiniHBaseCluster().getMaster(); 103 assertTrue(m.waitForMetaOnline()); 104 } 105 106 @Test 107 public void testIsMetaWhenMetaGoesOffline() throws InterruptedException { 108 HMaster m = UTIL.getMiniHBaseCluster().getMaster(); 109 int index = UTIL.getMiniHBaseCluster().getServerWithMeta(); 110 HRegionServer rsWithMeta = UTIL.getMiniHBaseCluster().getRegionServer(index); 111 rsWithMeta.abort("TESTING"); 112 assertTrue(m.waitForMetaOnline()); 113 } 114 115 /** 116 * Does {@link MetaTableAccessor#getRegion(Connection, byte[])} and a write against hbase:meta 117 * while its hosted server is restarted to prove our retrying works. 118 */ 119 @Test 120 public void testRetrying(TestInfo testInfo) throws IOException, InterruptedException { 121 final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 122 LOG.info("Started " + tableName); 123 Table t = UTIL.createMultiRegionTable(tableName, HConstants.CATALOG_FAMILY); 124 int regionCount = -1; 125 try (RegionLocator r = UTIL.getConnection().getRegionLocator(tableName)) { 126 regionCount = r.getStartKeys().length; 127 } 128 // Test it works getting a region from just made user table. 129 final List<RegionInfo> regions = testGettingTableRegions(connection, tableName, regionCount); 130 MetaTask reader = new MetaTask(connection, "reader") { 131 @Override 132 void metaTask() throws Throwable { 133 testGetRegion(connection, regions.get(0)); 134 LOG.info("Read " + regions.get(0).getEncodedName()); 135 } 136 }; 137 MetaTask writer = new MetaTask(connection, "writer") { 138 139 @Override 140 void metaTask() throws IOException { 141 MetaTableAccessor.addRegionsToMeta(connection, Collections.singletonList(regions.get(0)), 142 1); 143 LOG.info("Wrote " + regions.get(0).getEncodedName()); 144 } 145 }; 146 reader.start(); 147 writer.start(); 148 149 // We're gonna check how it takes. If it takes too long, we will consider 150 // it as a fail. We can't put that in the @Test tag as we want to close 151 // the threads nicely 152 final long timeOut = 180000; 153 long startTime = EnvironmentEdgeManager.currentTime(); 154 155 try { 156 // Make sure reader and writer are working. 157 assertTrue(reader.isProgressing()); 158 assertTrue(writer.isProgressing()); 159 160 // Kill server hosting meta -- twice . See if our reader/writer ride over the 161 // meta moves. They'll need to retry. 162 for (int i = 0; i < 2; i++) { 163 LOG.info("Restart=" + i); 164 UTIL.ensureSomeRegionServersAvailable(2); 165 int index = -1; 166 do { 167 index = UTIL.getMiniHBaseCluster().getServerWithMeta(); 168 } while (index == -1 && startTime + timeOut < EnvironmentEdgeManager.currentTime()); 169 170 if (index != -1) { 171 UTIL.getMiniHBaseCluster().abortRegionServer(index); 172 UTIL.getMiniHBaseCluster().waitOnRegionServer(index); 173 } 174 } 175 176 assertTrue(reader.isProgressing(), "reader: " + reader.toString()); 177 assertTrue(writer.isProgressing(), "writer: " + writer.toString()); 178 } catch (IOException e) { 179 throw e; 180 } finally { 181 reader.stop = true; 182 writer.stop = true; 183 reader.join(); 184 writer.join(); 185 t.close(); 186 } 187 long exeTime = EnvironmentEdgeManager.currentTime() - startTime; 188 assertTrue(exeTime < timeOut, "Timeout: test took " + exeTime / 1000 + " sec"); 189 } 190 191 /** 192 * Thread that runs a MetaTableAccessor task until asked stop. 193 */ 194 abstract static class MetaTask extends Thread { 195 boolean stop = false; 196 int count = 0; 197 Throwable t = null; 198 final Connection connection; 199 200 MetaTask(final Connection connection, final String name) { 201 super(name); 202 this.connection = connection; 203 } 204 205 @Override 206 public void run() { 207 try { 208 while (!this.stop) { 209 LOG.info("Before " + this.getName() + ", count=" + this.count); 210 metaTask(); 211 this.count += 1; 212 LOG.info("After " + this.getName() + ", count=" + this.count); 213 Thread.sleep(100); 214 } 215 } catch (Throwable t) { 216 LOG.info(this.getName() + " failed", t); 217 this.t = t; 218 } 219 } 220 221 boolean isProgressing() throws InterruptedException { 222 int currentCount = this.count; 223 while (currentCount == this.count) { 224 if (!isAlive()) return false; 225 if (this.t != null) return false; 226 Thread.sleep(10); 227 } 228 return true; 229 } 230 231 @Override 232 public String toString() { 233 return "count=" + this.count + ", t=" + (this.t == null ? "null" : this.t.toString()); 234 } 235 236 abstract void metaTask() throws Throwable; 237 } 238 239 @Test 240 public void testGetRegion(TestInfo testInfo) throws IOException, InterruptedException { 241 final String name = testInfo.getTestMethod().get().getName(); 242 LOG.info("Started " + name); 243 // Test get on non-existent region. 244 Pair<RegionInfo, ServerName> pair = 245 MetaTableAccessor.getRegion(connection, Bytes.toBytes("nonexistent-region")); 246 assertNull(pair); 247 LOG.info("Finished " + name); 248 } 249 250 // Test for the optimization made in HBASE-3650 251 @Test 252 public void testScanMetaForTable(TestInfo testInfo) throws IOException, InterruptedException { 253 final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 254 LOG.info("Started " + tableName); 255 256 /** 257 * Create 2 tables - testScanMetaForTable - testScanMetaForTablf 258 **/ 259 260 UTIL.createTable(tableName, HConstants.CATALOG_FAMILY); 261 // name that is +1 greater than the first one (e+1=f) 262 TableName greaterName = TableName.valueOf("testScanMetaForTablf"); 263 UTIL.createTable(greaterName, HConstants.CATALOG_FAMILY); 264 265 // Now make sure we only get the regions from 1 of the tables at a time 266 267 assertEquals(1, MetaTableAccessor.getTableRegions(connection, tableName).size()); 268 assertEquals(1, MetaTableAccessor.getTableRegions(connection, greaterName).size()); 269 } 270 271 private static List<RegionInfo> testGettingTableRegions(final Connection connection, 272 final TableName name, final int regionCount) throws IOException, InterruptedException { 273 List<RegionInfo> regions = MetaTableAccessor.getTableRegions(connection, name); 274 assertEquals(regionCount, regions.size()); 275 Pair<RegionInfo, ServerName> pair = 276 MetaTableAccessor.getRegion(connection, regions.get(0).getRegionName()); 277 assertEquals(regions.get(0).getEncodedName(), pair.getFirst().getEncodedName()); 278 return regions; 279 } 280 281 private static void testGetRegion(final Connection connection, final RegionInfo region) 282 throws IOException, InterruptedException { 283 Pair<RegionInfo, ServerName> pair = 284 MetaTableAccessor.getRegion(connection, region.getRegionName()); 285 assertEquals(region.getEncodedName(), pair.getFirst().getEncodedName()); 286 } 287 288 @Test 289 public void testMetaLocationsForRegionReplicas(TestInfo testInfo) throws IOException { 290 Random rand = ThreadLocalRandom.current(); 291 292 ServerName serverName0 = ServerName.valueOf("foo", 60010, rand.nextLong()); 293 ServerName serverName1 = ServerName.valueOf("bar", 60010, rand.nextLong()); 294 ServerName serverName100 = ServerName.valueOf("baz", 60010, rand.nextLong()); 295 296 long regionId = EnvironmentEdgeManager.currentTime(); 297 RegionInfo primary = 298 RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName())) 299 .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false) 300 .setRegionId(regionId).setReplicaId(0).build(); 301 RegionInfo replica1 = 302 RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName())) 303 .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false) 304 .setRegionId(regionId).setReplicaId(1).build(); 305 RegionInfo replica100 = 306 RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName())) 307 .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false) 308 .setRegionId(regionId).setReplicaId(100).build(); 309 310 long seqNum0 = rand.nextLong(); 311 long seqNum1 = rand.nextLong(); 312 long seqNum100 = rand.nextLong(); 313 314 try (Table meta = MetaTableAccessor.getMetaHTable(connection)) { 315 MetaTableAccessor.updateRegionLocation(connection, primary, serverName0, seqNum0, 316 EnvironmentEdgeManager.currentTime()); 317 318 // assert that the server, startcode and seqNum columns are there for the primary region 319 assertMetaLocation(meta, primary.getRegionName(), serverName0, seqNum0, 0, true); 320 321 // add replica = 1 322 MetaTableAccessor.updateRegionLocation(connection, replica1, serverName1, seqNum1, 323 EnvironmentEdgeManager.currentTime()); 324 // check whether the primary is still there 325 assertMetaLocation(meta, primary.getRegionName(), serverName0, seqNum0, 0, true); 326 // now check for replica 1 327 assertMetaLocation(meta, primary.getRegionName(), serverName1, seqNum1, 1, true); 328 329 // add replica = 1 330 MetaTableAccessor.updateRegionLocation(connection, replica100, serverName100, seqNum100, 331 EnvironmentEdgeManager.currentTime()); 332 // check whether the primary is still there 333 assertMetaLocation(meta, primary.getRegionName(), serverName0, seqNum0, 0, true); 334 // check whether the replica 1 is still there 335 assertMetaLocation(meta, primary.getRegionName(), serverName1, seqNum1, 1, true); 336 // now check for replica 1 337 assertMetaLocation(meta, primary.getRegionName(), serverName100, seqNum100, 100, true); 338 } 339 } 340 341 public static void assertMetaLocation(Table meta, byte[] row, ServerName serverName, long seqNum, 342 int replicaId, boolean checkSeqNum) throws IOException { 343 Get get = new Get(row); 344 Result result = meta.get(get); 345 assertTrue(Bytes.equals( 346 result.getValue(HConstants.CATALOG_FAMILY, CatalogFamilyFormat.getServerColumn(replicaId)), 347 Bytes.toBytes(serverName.getAddress().toString()))); 348 assertTrue(Bytes.equals( 349 result.getValue(HConstants.CATALOG_FAMILY, CatalogFamilyFormat.getStartCodeColumn(replicaId)), 350 Bytes.toBytes(serverName.getStartcode()))); 351 if (checkSeqNum) { 352 assertTrue(Bytes.equals( 353 result.getValue(HConstants.CATALOG_FAMILY, CatalogFamilyFormat.getSeqNumColumn(replicaId)), 354 Bytes.toBytes(seqNum))); 355 } 356 } 357 358 public static void assertEmptyMetaLocation(Table meta, byte[] row, int replicaId) 359 throws IOException { 360 Get get = new Get(row); 361 Result result = meta.get(get); 362 Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, 363 CatalogFamilyFormat.getServerColumn(replicaId)); 364 Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, 365 CatalogFamilyFormat.getStartCodeColumn(replicaId)); 366 assertNotNull(serverCell); 367 assertNotNull(startCodeCell); 368 assertEquals(0, serverCell.getValueLength()); 369 assertEquals(0, startCodeCell.getValueLength()); 370 } 371 372 @Test 373 public void testMetaLocationForRegionReplicasIsAddedAtTableCreation(TestInfo testInfo) 374 throws IOException { 375 long regionId = EnvironmentEdgeManager.currentTime(); 376 RegionInfo primary = 377 RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName())) 378 .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false) 379 .setRegionId(regionId).setReplicaId(0).build(); 380 381 Table meta = MetaTableAccessor.getMetaHTable(connection); 382 try { 383 List<RegionInfo> regionInfos = Lists.newArrayList(primary); 384 MetaTableAccessor.addRegionsToMeta(connection, regionInfos, 3); 385 386 assertEmptyMetaLocation(meta, primary.getRegionName(), 1); 387 assertEmptyMetaLocation(meta, primary.getRegionName(), 2); 388 } finally { 389 meta.close(); 390 } 391 } 392 393 @Test 394 public void testMetaScanner(TestInfo testInfo) throws Exception { 395 LOG.info("Starting " + testInfo.getTestMethod().get().getName()); 396 397 final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 398 final byte[] FAMILY = Bytes.toBytes("family"); 399 final byte[][] SPLIT_KEYS = 400 new byte[][] { Bytes.toBytes("region_a"), Bytes.toBytes("region_b") }; 401 402 UTIL.createTable(tableName, FAMILY, SPLIT_KEYS); 403 Table table = connection.getTable(tableName); 404 // Make sure all the regions are deployed 405 HBaseTestingUtil.countRows(table); 406 407 ClientMetaTableAccessor.Visitor visitor = mock(ClientMetaTableAccessor.Visitor.class); 408 doReturn(true).when(visitor).visit(any()); 409 410 // Scanning the entire table should give us three rows 411 MetaTableAccessor.scanMetaForTableRegions(connection, visitor, tableName); 412 verify(visitor, times(3)).visit(any()); 413 414 // Scanning the table with a specified empty start row should also 415 // give us three hbase:meta rows 416 reset(visitor); 417 doReturn(true).when(visitor).visit(any()); 418 MetaTableAccessor.scanMeta(connection, visitor, tableName, null, 1000); 419 verify(visitor, times(3)).visit(any()); 420 421 // Scanning the table starting in the middle should give us two rows: 422 // region_a and region_b 423 reset(visitor); 424 doReturn(true).when(visitor).visit(any()); 425 MetaTableAccessor.scanMeta(connection, visitor, tableName, Bytes.toBytes("region_ac"), 1000); 426 verify(visitor, times(2)).visit(any()); 427 428 // Scanning with a limit of 1 should only give us one row 429 reset(visitor); 430 doReturn(true).when(visitor).visit(any()); 431 MetaTableAccessor.scanMeta(connection, visitor, tableName, Bytes.toBytes("region_ac"), 1); 432 verify(visitor, times(1)).visit(any()); 433 434 byte[] prefix = Bytes.add(tableName.getName(), new byte[] { HConstants.DELIMITER }); 435 try (Table metaTable = connection.getTable(TableName.META_TABLE_NAME)) { 436 Scan rangeScan = new Scan().withStartRow(prefix) 437 .withStopRow(Bytes.add(tableName.getName(), new byte[] { HConstants.DELIMITER + 1 })); 438 assertEquals(3, HBaseTestingUtil.countRows(metaTable, rangeScan)); 439 440 Scan prefixScan = new Scan(); 441 prefixScan.setFilter(new PrefixFilter(prefix)); 442 assertEquals(3, HBaseTestingUtil.countRows(metaTable, prefixScan)); 443 } 444 table.close(); 445 } 446 447 /** 448 * Tests whether maximum of masters system time versus RSs local system time is used 449 */ 450 @Test 451 public void testMastersSystemTimeIsUsedInUpdateLocations(TestInfo testInfo) throws IOException { 452 long regionId = EnvironmentEdgeManager.currentTime(); 453 RegionInfo regionInfo = 454 RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName())) 455 .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false) 456 .setRegionId(regionId).setReplicaId(0).build(); 457 458 ServerName sn = ServerName.valueOf("bar", 0, 0); 459 try (Table meta = MetaTableAccessor.getMetaHTable(connection)) { 460 List<RegionInfo> regionInfos = Lists.newArrayList(regionInfo); 461 MetaTableAccessor.addRegionsToMeta(connection, regionInfos, 1); 462 463 long masterSystemTime = EnvironmentEdgeManager.currentTime() + 123456789; 464 MetaTableAccessor.updateRegionLocation(connection, regionInfo, sn, 1, masterSystemTime); 465 466 Get get = new Get(regionInfo.getRegionName()); 467 Result result = meta.get(get); 468 Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, 469 CatalogFamilyFormat.getServerColumn(0)); 470 Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, 471 CatalogFamilyFormat.getStartCodeColumn(0)); 472 Cell seqNumCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, 473 CatalogFamilyFormat.getSeqNumColumn(0)); 474 assertNotNull(serverCell); 475 assertNotNull(startCodeCell); 476 assertNotNull(seqNumCell); 477 assertTrue(serverCell.getValueLength() > 0); 478 assertTrue(startCodeCell.getValueLength() > 0); 479 assertTrue(seqNumCell.getValueLength() > 0); 480 assertEquals(masterSystemTime, serverCell.getTimestamp()); 481 assertEquals(masterSystemTime, startCodeCell.getTimestamp()); 482 assertEquals(masterSystemTime, seqNumCell.getTimestamp()); 483 } 484 } 485 486 public static class SpyingRpcSchedulerFactory extends SimpleRpcSchedulerFactory { 487 @Override 488 public RpcScheduler create(Configuration conf, PriorityFunction priority, Abortable server) { 489 final RpcScheduler delegate = super.create(conf, priority, server); 490 return new SpyingRpcScheduler(delegate); 491 } 492 } 493 494 public static class SpyingRpcScheduler extends DelegatingRpcScheduler { 495 long numPriorityCalls = 0; 496 497 public SpyingRpcScheduler(RpcScheduler delegate) { 498 super(delegate); 499 } 500 501 @Override 502 public boolean dispatch(CallRunner task) { 503 int priority = task.getRpcCall().getPriority(); 504 505 if (priority > HConstants.QOS_THRESHOLD) { 506 numPriorityCalls++; 507 } 508 return super.dispatch(task); 509 } 510 } 511 512 @Test 513 public void testScanByRegionEncodedNameExistingRegion() throws Exception { 514 final TableName tableName = TableName.valueOf("testScanByRegionEncodedNameExistingRegion"); 515 UTIL.createTable(tableName, "cf"); 516 final List<HRegion> regions = UTIL.getHBaseCluster().getRegions(tableName); 517 final String encodedName = regions.get(0).getRegionInfo().getEncodedName(); 518 final Result result = 519 MetaTableAccessor.scanByRegionEncodedName(UTIL.getConnection(), encodedName); 520 assertNotNull(result); 521 assertTrue(result.advance()); 522 final String resultingRowKey = CellUtil.getCellKeyAsString(result.current()); 523 assertTrue(resultingRowKey.contains(encodedName)); 524 UTIL.deleteTable(tableName); 525 } 526 527 @Test 528 public void testScanByRegionEncodedNameNonExistingRegion() throws Exception { 529 final String encodedName = "nonexistingregion"; 530 final Result result = 531 MetaTableAccessor.scanByRegionEncodedName(UTIL.getConnection(), encodedName); 532 assertNull(result); 533 } 534}