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