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.regionserver.wal; 019 020import static org.junit.Assert.*; 021import static org.junit.Assert.assertNotEquals; 022import static org.junit.Assert.assertNotNull; 023import static org.junit.Assert.assertNull; 024import static org.junit.Assert.assertTrue; 025import static org.junit.Assert.fail; 026import static org.hamcrest.CoreMatchers.*; 027 028import java.io.IOException; 029import java.util.ArrayList; 030import java.util.Comparator; 031import java.util.List; 032import java.util.NavigableMap; 033import java.util.Set; 034import java.util.TreeMap; 035import java.util.UUID; 036import java.util.concurrent.atomic.AtomicBoolean; 037import org.apache.hadoop.conf.Configuration; 038import org.apache.hadoop.fs.FileStatus; 039import org.apache.hadoop.fs.FileSystem; 040import org.apache.hadoop.fs.Path; 041import org.apache.hadoop.hbase.CellScanner; 042import org.apache.hadoop.hbase.Coprocessor; 043import org.apache.hadoop.hbase.HBaseConfiguration; 044import org.apache.hadoop.hbase.HBaseTestingUtility; 045import org.apache.hadoop.hbase.HConstants; 046import org.apache.hadoop.hbase.KeyValue; 047import org.apache.hadoop.hbase.TableName; 048import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 049import org.apache.hadoop.hbase.client.Get; 050import org.apache.hadoop.hbase.client.Put; 051import org.apache.hadoop.hbase.client.RegionInfo; 052import org.apache.hadoop.hbase.client.RegionInfoBuilder; 053import org.apache.hadoop.hbase.client.Result; 054import org.apache.hadoop.hbase.client.TableDescriptor; 055import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 056import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 057import org.apache.hadoop.hbase.coprocessor.SampleRegionWALCoprocessor; 058import org.apache.hadoop.hbase.regionserver.HRegion; 059import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl; 060import org.apache.hadoop.hbase.regionserver.SequenceId; 061import org.apache.hadoop.hbase.util.Bytes; 062import org.apache.hadoop.hbase.util.CommonFSUtils; 063import org.apache.hadoop.hbase.util.EnvironmentEdge; 064import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 065import org.apache.hadoop.hbase.util.Threads; 066import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 067import org.apache.hadoop.hbase.wal.WAL; 068import org.apache.hadoop.hbase.wal.WALEdit; 069import org.apache.hadoop.hbase.wal.WALKey; 070import org.apache.hadoop.hbase.wal.WALKeyImpl; 071import org.junit.AfterClass; 072import org.junit.Before; 073import org.junit.BeforeClass; 074import org.junit.Rule; 075import org.junit.Test; 076import org.junit.rules.TestName; 077import org.slf4j.Logger; 078import org.slf4j.LoggerFactory; 079 080public abstract class AbstractTestFSWAL { 081 082 protected static final Logger LOG = LoggerFactory.getLogger(AbstractTestFSWAL.class); 083 084 protected static Configuration CONF; 085 protected static FileSystem FS; 086 protected static Path DIR; 087 protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 088 089 @Rule 090 public final TestName currentTest = new TestName(); 091 092 @Before 093 public void setUp() throws Exception { 094 FileStatus[] entries = FS.listStatus(new Path("/")); 095 for (FileStatus dir : entries) { 096 FS.delete(dir.getPath(), true); 097 } 098 final Path hbaseDir = TEST_UTIL.createRootDir(); 099 final Path hbaseWALDir = TEST_UTIL.createWALRootDir(); 100 DIR = new Path(hbaseWALDir, currentTest.getMethodName()); 101 assertNotEquals(hbaseDir, hbaseWALDir); 102 } 103 104 @BeforeClass 105 public static void setUpBeforeClass() throws Exception { 106 // Make block sizes small. 107 TEST_UTIL.getConfiguration().setInt("dfs.blocksize", 1024 * 1024); 108 // quicker heartbeat interval for faster DN death notification 109 TEST_UTIL.getConfiguration().setInt("dfs.namenode.heartbeat.recheck-interval", 5000); 110 TEST_UTIL.getConfiguration().setInt("dfs.heartbeat.interval", 1); 111 TEST_UTIL.getConfiguration().setInt("dfs.client.socket-timeout", 5000); 112 113 // faster failover with cluster.shutdown();fs.close() idiom 114 TEST_UTIL.getConfiguration().setInt("hbase.ipc.client.connect.max.retries", 1); 115 TEST_UTIL.getConfiguration().setInt("dfs.client.block.recovery.retries", 1); 116 TEST_UTIL.getConfiguration().setInt("hbase.ipc.client.connection.maxidletime", 500); 117 TEST_UTIL.getConfiguration().set(CoprocessorHost.WAL_COPROCESSOR_CONF_KEY, 118 SampleRegionWALCoprocessor.class.getName()); 119 TEST_UTIL.startMiniDFSCluster(3); 120 121 CONF = TEST_UTIL.getConfiguration(); 122 FS = TEST_UTIL.getDFSCluster().getFileSystem(); 123 } 124 125 @AfterClass 126 public static void tearDownAfterClass() throws Exception { 127 TEST_UTIL.shutdownMiniCluster(); 128 } 129 130 protected abstract AbstractFSWAL<?> newWAL(FileSystem fs, Path rootDir, String WALDir, 131 String archiveDir, Configuration conf, List<WALActionsListener> listeners, 132 boolean failIfWALExists, String prefix, String suffix) throws IOException; 133 134 protected abstract AbstractFSWAL<?> newSlowWAL(FileSystem fs, Path rootDir, String WALDir, 135 String archiveDir, Configuration conf, List<WALActionsListener> listeners, 136 boolean failIfWALExists, String prefix, String suffix, Runnable action) throws IOException; 137 138 /** 139 * A loaded WAL coprocessor won't break existing WAL test cases. 140 */ 141 @Test 142 public void testWALCoprocessorLoaded() throws Exception { 143 // test to see whether the coprocessor is loaded or not. 144 AbstractFSWAL<?> wal = null; 145 try { 146 wal = newWAL(FS, CommonFSUtils.getWALRootDir(CONF), DIR.toString(), 147 HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, null); 148 WALCoprocessorHost host = wal.getCoprocessorHost(); 149 Coprocessor c = host.findCoprocessor(SampleRegionWALCoprocessor.class); 150 assertNotNull(c); 151 } finally { 152 if (wal != null) { 153 wal.close(); 154 } 155 } 156 } 157 158 protected void addEdits(WAL log, RegionInfo hri, TableDescriptor htd, int times, 159 MultiVersionConcurrencyControl mvcc, NavigableMap<byte[], Integer> scopes) 160 throws IOException { 161 final byte[] row = Bytes.toBytes("row"); 162 for (int i = 0; i < times; i++) { 163 long timestamp = System.currentTimeMillis(); 164 WALEdit cols = new WALEdit(); 165 cols.add(new KeyValue(row, row, row, timestamp, row)); 166 WALKeyImpl key = new WALKeyImpl(hri.getEncodedNameAsBytes(), htd.getTableName(), 167 SequenceId.NO_SEQUENCE_ID, timestamp, WALKey.EMPTY_UUIDS, HConstants.NO_NONCE, 168 HConstants.NO_NONCE, mvcc, scopes); 169 log.append(hri, key, cols, true); 170 } 171 log.sync(); 172 } 173 174 /** 175 * helper method to simulate region flush for a WAL. 176 * @param wal 177 * @param regionEncodedName 178 */ 179 protected void flushRegion(WAL wal, byte[] regionEncodedName, Set<byte[]> flushedFamilyNames) { 180 wal.startCacheFlush(regionEncodedName, flushedFamilyNames); 181 wal.completeCacheFlush(regionEncodedName); 182 } 183 184 /** 185 * tests the log comparator. Ensure that we are not mixing meta logs with non-meta logs (throws 186 * exception if we do). Comparison is based on the timestamp present in the wal name. 187 * @throws Exception 188 */ 189 @Test 190 public void testWALComparator() throws Exception { 191 AbstractFSWAL<?> wal1 = null; 192 AbstractFSWAL<?> walMeta = null; 193 try { 194 wal1 = newWAL(FS, CommonFSUtils.getWALRootDir(CONF), DIR.toString(), 195 HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, null); 196 LOG.debug("Log obtained is: " + wal1); 197 Comparator<Path> comp = wal1.LOG_NAME_COMPARATOR; 198 Path p1 = wal1.computeFilename(11); 199 Path p2 = wal1.computeFilename(12); 200 // comparing with itself returns 0 201 assertTrue(comp.compare(p1, p1) == 0); 202 // comparing with different filenum. 203 assertTrue(comp.compare(p1, p2) < 0); 204 walMeta = newWAL(FS, CommonFSUtils.getWALRootDir(CONF), DIR.toString(), 205 HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, 206 AbstractFSWALProvider.META_WAL_PROVIDER_ID); 207 Comparator<Path> compMeta = walMeta.LOG_NAME_COMPARATOR; 208 209 Path p1WithMeta = walMeta.computeFilename(11); 210 Path p2WithMeta = walMeta.computeFilename(12); 211 assertTrue(compMeta.compare(p1WithMeta, p1WithMeta) == 0); 212 assertTrue(compMeta.compare(p1WithMeta, p2WithMeta) < 0); 213 // mixing meta and non-meta logs gives error 214 boolean ex = false; 215 try { 216 comp.compare(p1WithMeta, p2); 217 } catch (IllegalArgumentException e) { 218 ex = true; 219 } 220 assertTrue("Comparator doesn't complain while checking meta log files", ex); 221 boolean exMeta = false; 222 try { 223 compMeta.compare(p1WithMeta, p2); 224 } catch (IllegalArgumentException e) { 225 exMeta = true; 226 } 227 assertTrue("Meta comparator doesn't complain while checking log files", exMeta); 228 } finally { 229 if (wal1 != null) { 230 wal1.close(); 231 } 232 if (walMeta != null) { 233 walMeta.close(); 234 } 235 } 236 } 237 238 /** 239 * On rolling a wal after reaching the threshold, {@link WAL#rollWriter()} returns the list of 240 * regions which should be flushed in order to archive the oldest wal file. 241 * <p> 242 * This method tests this behavior by inserting edits and rolling the wal enough times to reach 243 * the max number of logs threshold. It checks whether we get the "right regions" for flush on 244 * rolling the wal. 245 * @throws Exception 246 */ 247 @Test 248 public void testFindMemStoresEligibleForFlush() throws Exception { 249 LOG.debug("testFindMemStoresEligibleForFlush"); 250 Configuration conf1 = HBaseConfiguration.create(CONF); 251 conf1.setInt("hbase.regionserver.maxlogs", 1); 252 AbstractFSWAL<?> wal = newWAL(FS, CommonFSUtils.getWALRootDir(conf1), DIR.toString(), 253 HConstants.HREGION_OLDLOGDIR_NAME, conf1, null, true, null, null); 254 TableDescriptor t1 = TableDescriptorBuilder.newBuilder(TableName.valueOf("t1")) 255 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("row")).build(); 256 TableDescriptor t2 = TableDescriptorBuilder.newBuilder(TableName.valueOf("t2")) 257 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("row")).build(); 258 RegionInfo hri1 = RegionInfoBuilder.newBuilder(t1.getTableName()).build(); 259 RegionInfo hri2 = RegionInfoBuilder.newBuilder(t2.getTableName()).build(); 260 // add edits and roll the wal 261 MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(); 262 NavigableMap<byte[], Integer> scopes1 = new TreeMap<>(Bytes.BYTES_COMPARATOR); 263 for (byte[] fam : t1.getColumnFamilyNames()) { 264 scopes1.put(fam, 0); 265 } 266 NavigableMap<byte[], Integer> scopes2 = new TreeMap<>(Bytes.BYTES_COMPARATOR); 267 for (byte[] fam : t2.getColumnFamilyNames()) { 268 scopes2.put(fam, 0); 269 } 270 try { 271 addEdits(wal, hri1, t1, 2, mvcc, scopes1); 272 wal.rollWriter(); 273 // add some more edits and roll the wal. This would reach the log number threshold 274 addEdits(wal, hri1, t1, 2, mvcc, scopes1); 275 wal.rollWriter(); 276 // with above rollWriter call, the max logs limit is reached. 277 assertTrue(wal.getNumRolledLogFiles() == 2); 278 279 // get the regions to flush; since there is only one region in the oldest wal, it should 280 // return only one region. 281 byte[][] regionsToFlush = wal.findRegionsToForceFlush(); 282 assertEquals(1, regionsToFlush.length); 283 assertEquals(hri1.getEncodedNameAsBytes(), regionsToFlush[0]); 284 // insert edits in second region 285 addEdits(wal, hri2, t2, 2, mvcc, scopes2); 286 // get the regions to flush, it should still read region1. 287 regionsToFlush = wal.findRegionsToForceFlush(); 288 assertEquals(1, regionsToFlush.length); 289 assertEquals(hri1.getEncodedNameAsBytes(), regionsToFlush[0]); 290 // flush region 1, and roll the wal file. Only last wal which has entries for region1 should 291 // remain. 292 flushRegion(wal, hri1.getEncodedNameAsBytes(), t1.getColumnFamilyNames()); 293 wal.rollWriter(); 294 // only one wal should remain now (that is for the second region). 295 assertEquals(1, wal.getNumRolledLogFiles()); 296 // flush the second region 297 flushRegion(wal, hri2.getEncodedNameAsBytes(), t2.getColumnFamilyNames()); 298 wal.rollWriter(true); 299 // no wal should remain now. 300 assertEquals(0, wal.getNumRolledLogFiles()); 301 // add edits both to region 1 and region 2, and roll. 302 addEdits(wal, hri1, t1, 2, mvcc, scopes1); 303 addEdits(wal, hri2, t2, 2, mvcc, scopes2); 304 wal.rollWriter(); 305 // add edits and roll the writer, to reach the max logs limit. 306 assertEquals(1, wal.getNumRolledLogFiles()); 307 addEdits(wal, hri1, t1, 2, mvcc, scopes1); 308 wal.rollWriter(); 309 // it should return two regions to flush, as the oldest wal file has entries 310 // for both regions. 311 regionsToFlush = wal.findRegionsToForceFlush(); 312 assertEquals(2, regionsToFlush.length); 313 // flush both regions 314 flushRegion(wal, hri1.getEncodedNameAsBytes(), t1.getColumnFamilyNames()); 315 flushRegion(wal, hri2.getEncodedNameAsBytes(), t2.getColumnFamilyNames()); 316 wal.rollWriter(true); 317 assertEquals(0, wal.getNumRolledLogFiles()); 318 // Add an edit to region1, and roll the wal. 319 addEdits(wal, hri1, t1, 2, mvcc, scopes1); 320 // tests partial flush: roll on a partial flush, and ensure that wal is not archived. 321 wal.startCacheFlush(hri1.getEncodedNameAsBytes(), t1.getColumnFamilyNames()); 322 wal.rollWriter(); 323 wal.completeCacheFlush(hri1.getEncodedNameAsBytes()); 324 assertEquals(1, wal.getNumRolledLogFiles()); 325 } finally { 326 if (wal != null) { 327 wal.close(); 328 } 329 } 330 } 331 332 @Test(expected = IOException.class) 333 public void testFailedToCreateWALIfParentRenamed() throws IOException, 334 CommonFSUtils.StreamLacksCapabilityException { 335 final String name = "testFailedToCreateWALIfParentRenamed"; 336 AbstractFSWAL<?> wal = newWAL(FS, CommonFSUtils.getWALRootDir(CONF), name, 337 HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, null); 338 long filenum = System.currentTimeMillis(); 339 Path path = wal.computeFilename(filenum); 340 wal.createWriterInstance(path); 341 Path parent = path.getParent(); 342 path = wal.computeFilename(filenum + 1); 343 Path newPath = new Path(parent.getParent(), parent.getName() + "-splitting"); 344 FS.rename(parent, newPath); 345 wal.createWriterInstance(path); 346 fail("It should fail to create the new WAL"); 347 } 348 349 /** 350 * Test flush for sure has a sequence id that is beyond the last edit appended. We do this by 351 * slowing appends in the background ring buffer thread while in foreground we call flush. The 352 * addition of the sync over HRegion in flush should fix an issue where flush was returning before 353 * all of its appends had made it out to the WAL (HBASE-11109). 354 * @throws IOException 355 * @see <a href="https://issues.apache.org/jira/browse/HBASE-11109">HBASE-11109</a> 356 */ 357 @Test 358 public void testFlushSequenceIdIsGreaterThanAllEditsInHFile() throws IOException { 359 String testName = currentTest.getMethodName(); 360 final TableName tableName = TableName.valueOf(testName); 361 final RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).build(); 362 final byte[] rowName = tableName.getName(); 363 final TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName) 364 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build(); 365 HRegion r = HBaseTestingUtility.createRegionAndWAL(hri, TEST_UTIL.getDefaultRootDirPath(), 366 TEST_UTIL.getConfiguration(), htd); 367 HBaseTestingUtility.closeRegionAndWAL(r); 368 final int countPerFamily = 10; 369 final AtomicBoolean goslow = new AtomicBoolean(false); 370 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 371 for (byte[] fam : htd.getColumnFamilyNames()) { 372 scopes.put(fam, 0); 373 } 374 // subclass and doctor a method. 375 AbstractFSWAL<?> wal = newSlowWAL(FS, CommonFSUtils.getWALRootDir(CONF), DIR.toString(), 376 testName, CONF, null, true, null, null, new Runnable() { 377 378 @Override 379 public void run() { 380 if (goslow.get()) { 381 Threads.sleep(100); 382 LOG.debug("Sleeping before appending 100ms"); 383 } 384 } 385 }); 386 HRegion region = HRegion.openHRegion(TEST_UTIL.getConfiguration(), 387 TEST_UTIL.getTestFileSystem(), TEST_UTIL.getDefaultRootDirPath(), hri, htd, wal); 388 EnvironmentEdge ee = EnvironmentEdgeManager.getDelegate(); 389 try { 390 List<Put> puts = null; 391 for (byte[] fam : htd.getColumnFamilyNames()) { 392 puts = 393 TestWALReplay.addRegionEdits(rowName, fam, countPerFamily, ee, region, "x"); 394 } 395 396 // Now assert edits made it in. 397 final Get g = new Get(rowName); 398 Result result = region.get(g); 399 assertEquals(countPerFamily * htd.getColumnFamilyNames().size(), result.size()); 400 401 // Construct a WALEdit and add it a few times to the WAL. 402 WALEdit edits = new WALEdit(); 403 for (Put p : puts) { 404 CellScanner cs = p.cellScanner(); 405 while (cs.advance()) { 406 edits.add(cs.current()); 407 } 408 } 409 // Add any old cluster id. 410 List<UUID> clusterIds = new ArrayList<>(1); 411 clusterIds.add(TEST_UTIL.getRandomUUID()); 412 // Now make appends run slow. 413 goslow.set(true); 414 for (int i = 0; i < countPerFamily; i++) { 415 final RegionInfo info = region.getRegionInfo(); 416 final WALKeyImpl logkey = new WALKeyImpl(info.getEncodedNameAsBytes(), tableName, 417 System.currentTimeMillis(), clusterIds, -1, -1, region.getMVCC(), scopes); 418 wal.append(info, logkey, edits, true); 419 region.getMVCC().completeAndWait(logkey.getWriteEntry()); 420 } 421 region.flush(true); 422 // FlushResult.flushSequenceId is not visible here so go get the current sequence id. 423 long currentSequenceId = region.getReadPoint(null); 424 // Now release the appends 425 goslow.set(false); 426 assertTrue(currentSequenceId >= region.getReadPoint(null)); 427 } finally { 428 region.close(true); 429 wal.close(); 430 } 431 } 432 433 @Test 434 public void testSyncNoAppend() throws IOException { 435 String testName = currentTest.getMethodName(); 436 AbstractFSWAL<?> wal = newWAL(FS, CommonFSUtils.getWALRootDir(CONF), DIR.toString(), testName, 437 CONF, null, true, null, null); 438 try { 439 wal.sync(); 440 } finally { 441 wal.close(); 442 } 443 } 444 445 @Test 446 public void testWriteEntryCanBeNull() throws IOException { 447 String testName = currentTest.getMethodName(); 448 AbstractFSWAL<?> wal = newWAL(FS, CommonFSUtils.getWALRootDir(CONF), DIR.toString(), testName, 449 CONF, null, true, null, null); 450 wal.close(); 451 TableDescriptor td = TableDescriptorBuilder.newBuilder(TableName.valueOf("table")) 452 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("row")).build(); 453 RegionInfo ri = RegionInfoBuilder.newBuilder(td.getTableName()).build(); 454 MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(); 455 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 456 for (byte[] fam : td.getColumnFamilyNames()) { 457 scopes.put(fam, 0); 458 } 459 long timestamp = System.currentTimeMillis(); 460 byte[] row = Bytes.toBytes("row"); 461 WALEdit cols = new WALEdit(); 462 cols.add(new KeyValue(row, row, row, timestamp, row)); 463 WALKeyImpl key = 464 new WALKeyImpl(ri.getEncodedNameAsBytes(), td.getTableName(), SequenceId.NO_SEQUENCE_ID, 465 timestamp, WALKey.EMPTY_UUIDS, HConstants.NO_NONCE, HConstants.NO_NONCE, mvcc, scopes); 466 try { 467 wal.append(ri, key, cols, true); 468 fail("Should fail since the wal has already been closed"); 469 } catch (IOException e) { 470 // expected 471 assertThat(e.getMessage(), containsString("log is closed")); 472 // the WriteEntry should be null since we fail before setting it. 473 assertNull(key.getWriteEntry()); 474 } 475 } 476}